mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 00:04:54 +00:00
Add Ethernet transport with beacon discovery
Implement raw Ethernet transport using AF_PACKET SOCK_DGRAM on Linux with EtherType 0x88B5 (IEEE experimental range) and 1-byte frame type prefix (0x00=data, 0x01=beacon). Transport implementation: - EthernetConfig with interface, ethertype, MTU, buffer sizes, and four independent discovery knobs (discovery, announce, auto_connect, accept_connections) - PacketSocket/AsyncPacketSocket wrappers with ioctl helpers for interface index, MAC address, and MTU queries - EthernetTransport with Transport trait impl, async start/stop/send, receive loop dispatching data frames and discovery beacons - Discovery beacons (34 bytes: type + version + x-only pubkey) with DiscoveryBuffer for peer accumulation and dedup - Atomic statistics counters (frames, bytes, errors, beacons) - Platform-gated with #[cfg(target_os = "linux")] Transport-layer discovery integration: - Promote auto_connect() and accept_connections() to Transport trait with default implementations and TransportHandle dispatch - Extract initiate_connection() so both static peer config and discovery auto-connect share the same handshake initiation path - Add poll_transport_discovery() to the tick handler to drain discovery buffers and auto-connect to discovered peers - Enforce accept_connections() in handle_msg1() — transports with accept_connections=false silently drop inbound handshakes Node integration: - create_transports() handles Ethernet named instances - resolve_ethernet_addr() parses "interface/mac" address format - transport_mtu() generalized for multi-transport operation Test harness: - VethPair RAII struct for veth pair lifecycle management - Three #[ignore] integration tests requiring root/CAP_NET_RAW: two-node handshake, data exchange, mixed transport coexistence - Chaos harness: transport-aware topology model, VethManager for veth pairs between Docker containers, Ethernet-aware config gen, netem split (HTB+u32 for UDP, root netem for veth), transport-aware link flaps and node churn with veth re-setup - Container entrypoint waits for configured Ethernet interfaces before starting FIPS (handles veth creation timing) - New scenarios: ethernet-only (4-node ring), ethernet-mesh (6-node mixed UDP+Ethernet with netem and link flaps) Documentation: - fips-transport-layer.md: Ethernet section, beacon discovery, WiFi compatibility, updated discovery state, trait surface additions, implementation status table - fips-configuration.md: Ethernet parameter table, named instances, peer address format, mixed UDP+Ethernet example, complete reference - fips-wire-formats.md: Ethernet frame type prefix note
This commit is contained in:
@@ -45,7 +45,7 @@ The configuration is organized into five top-level sections:
|
||||
node: # Node behavior, protocol parameters, and tuning
|
||||
tun: # TUN virtual interface
|
||||
dns: # DNS responder for .fips domain
|
||||
transports: # Network transports (UDP, future: TCP, Tor)
|
||||
transports: # Network transports (UDP, Ethernet, Bluetooth, Tor, ...)
|
||||
peers: # Static peer list
|
||||
```
|
||||
|
||||
@@ -248,6 +248,43 @@ stale address mappings.
|
||||
| `transports.udp.recv_buf_size` | usize | `2097152` | UDP socket receive buffer size in bytes (2 MB). Linux kernel doubles the requested value internally. Host `net.core.rmem_max` must be >= this value. |
|
||||
| `transports.udp.send_buf_size` | usize | `2097152` | UDP socket send buffer size in bytes (2 MB). Host `net.core.wmem_max` must be >= this value. |
|
||||
|
||||
### Ethernet (`transports.ethernet.*`)
|
||||
|
||||
Ethernet transport sends raw frames via AF_PACKET SOCK_DGRAM sockets.
|
||||
Requires `CAP_NET_RAW` or running as root. Linux only.
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `interface` | string | *(required)* | Network interface name (e.g., `"eth0"`, `"enp3s0"`) |
|
||||
| `ethertype` | u16 | `0x88B5` | IEEE EtherType (802 experimental range) |
|
||||
| `mtu` | u16 | *(auto)* | Override MTU. Default: interface MTU minus 1 (for frame type prefix) |
|
||||
| `recv_buf_size` | usize | `2097152` | Socket receive buffer size in bytes (2 MB) |
|
||||
| `send_buf_size` | usize | `2097152` | Socket send buffer size in bytes (2 MB) |
|
||||
| `discovery` | bool | `true` | Listen for discovery beacons from other nodes |
|
||||
| `announce` | bool | `false` | Broadcast announcement beacons on the LAN |
|
||||
| `auto_connect` | bool | `false` | Auto-connect to discovered peers |
|
||||
| `accept_connections` | bool | `false` | Accept incoming connection attempts from discovered peers |
|
||||
| `beacon_interval_secs` | u64 | `30` | Announcement beacon interval in seconds (minimum 10) |
|
||||
|
||||
**Named instances.** Multiple Ethernet interfaces can be configured by
|
||||
using named sub-keys instead of flat parameters:
|
||||
|
||||
```yaml
|
||||
transports:
|
||||
ethernet:
|
||||
lan:
|
||||
interface: "eth0"
|
||||
discovery: true
|
||||
announce: true
|
||||
backbone:
|
||||
interface: "eth1"
|
||||
announce: false
|
||||
```
|
||||
|
||||
Each named instance operates independently with its own socket and
|
||||
discovery state. The instance name is used in log messages and the
|
||||
`name()` method on the Transport trait.
|
||||
|
||||
## Peers (`peers[]`)
|
||||
|
||||
Static peer list. Each entry defines a peer to connect to.
|
||||
@@ -256,8 +293,8 @@ Static peer list. Each entry defines a peer to connect to.
|
||||
|-----------|------|---------|-------------|
|
||||
| `peers[].npub` | string | *(required)* | Peer's Nostr public key (npub-encoded) |
|
||||
| `peers[].alias` | string | *(none)* | Human-readable name for logging |
|
||||
| `peers[].addresses[].transport` | string | *(required)* | Transport type (`udp`) |
|
||||
| `peers[].addresses[].addr` | string | *(required)* | Transport address (e.g., `"10.0.0.2:4000"`) |
|
||||
| `peers[].addresses[].transport` | string | *(required)* | Transport type: `udp` or `ethernet` |
|
||||
| `peers[].addresses[].addr` | string | *(required)* | Transport address. UDP: `"ip:port"`. Ethernet: `"interface/mac"` (e.g., `"eth0/aa:bb:cc:dd:ee:ff"`) |
|
||||
| `peers[].addresses[].priority` | u8 | `100` | Address priority (lower = preferred) |
|
||||
| `peers[].connect_policy` | string | `"auto_connect"` | Connection policy: `auto_connect`, `on_demand`, or `manual` |
|
||||
| `peers[].auto_reconnect` | bool | `true` | Automatically reconnect after MMP link-dead removal (exponential backoff, unlimited retries) |
|
||||
@@ -295,6 +332,43 @@ peers:
|
||||
connect_policy: auto_connect
|
||||
```
|
||||
|
||||
### Mixed UDP + Ethernet Example
|
||||
|
||||
A node bridging internet peers (UDP) and a local Ethernet segment with
|
||||
beacon discovery:
|
||||
|
||||
```yaml
|
||||
node:
|
||||
identity:
|
||||
nsec: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:4000"
|
||||
mtu: 1472
|
||||
ethernet:
|
||||
interface: "eth0"
|
||||
discovery: true
|
||||
announce: true
|
||||
auto_connect: true
|
||||
accept_connections: true
|
||||
|
||||
peers:
|
||||
- npub: "npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le"
|
||||
alias: "internet-peer"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "203.0.113.5:4000"
|
||||
connect_policy: auto_connect
|
||||
```
|
||||
|
||||
Ethernet peers on the local segment are discovered automatically via
|
||||
beacons — no static peer entries needed. Internet peers still require
|
||||
explicit configuration.
|
||||
|
||||
All `node.*` parameters use their defaults. To override specific values, add
|
||||
only the relevant sections:
|
||||
|
||||
@@ -398,6 +472,17 @@ transports:
|
||||
mtu: 1280
|
||||
recv_buf_size: 2097152 # 2 MB (kernel doubles to 4 MB actual)
|
||||
send_buf_size: 2097152 # 2 MB
|
||||
# ethernet: # uncomment to enable (requires CAP_NET_RAW)
|
||||
# interface: "eth0" # required: network interface name
|
||||
# ethertype: 0x88B5 # IEEE 802 experimental EtherType
|
||||
# mtu: null # null = interface MTU - 1 (typically 1499)
|
||||
# recv_buf_size: 2097152 # 2 MB
|
||||
# send_buf_size: 2097152 # 2 MB
|
||||
# discovery: true # listen for beacons
|
||||
# announce: false # broadcast beacons
|
||||
# auto_connect: false # connect to discovered peers
|
||||
# accept_connections: false # accept inbound handshakes
|
||||
# beacon_interval_secs: 30 # beacon interval (min 10)
|
||||
|
||||
peers: # static peer list
|
||||
# - npub: "npub1..."
|
||||
|
||||
@@ -242,6 +242,76 @@ Actual buffer sizes are logged at startup:
|
||||
UDP transport started local_addr=0.0.0.0:4000 recv_buf=4194304 send_buf=4194304
|
||||
```
|
||||
|
||||
## Ethernet: The Local Network Transport
|
||||
|
||||
For nodes on the same LAN segment, raw Ethernet provides a direct transport
|
||||
without IP/UDP overhead — 28 bytes more FIPS payload per frame compared to
|
||||
UDP (1500 vs 1472 MTU).
|
||||
|
||||
- **No IP dependency**: Operates below the IP layer. Nodes on the same
|
||||
Ethernet segment can communicate without IP addresses or routing
|
||||
infrastructure
|
||||
- **Broadcast discovery**: Nodes discover each other via periodic beacon
|
||||
broadcasts on the shared medium, with no static peer configuration required
|
||||
- **Higher MTU**: Standard Ethernet frames carry 1500 bytes of payload,
|
||||
yielding an effective FIPS MTU of 1499 after the frame type prefix
|
||||
- **Matches FIPS model**: Like UDP, Ethernet is connectionless and
|
||||
unreliable — datagrams flow immediately to any MAC address on the segment
|
||||
|
||||
### Implementation
|
||||
|
||||
The Ethernet transport uses Linux AF_PACKET sockets in SOCK_DGRAM mode with
|
||||
EtherType 0x88B5 (IEEE 802 experimental/local use range). SOCK_DGRAM mode
|
||||
lets the kernel handle Ethernet header construction and parsing — the
|
||||
transport deals only with payloads and MAC addresses.
|
||||
|
||||
A 1-byte frame type prefix disambiguates data frames (0x00) from discovery
|
||||
beacons (0x01) on the receive path. This costs one byte of MTU but allows
|
||||
beacons and data to share the same EtherType and socket.
|
||||
|
||||
| Property | Value |
|
||||
| -------- | ----- |
|
||||
| EtherType | 0x88B5 (IEEE 802 experimental) |
|
||||
| Socket type | AF_PACKET SOCK_DGRAM |
|
||||
| Frame type prefix | 0x00 = data, 0x01 = beacon |
|
||||
| Effective MTU | Interface MTU - 1 (typically 1499) |
|
||||
| Addressing | 6-byte MAC address |
|
||||
| Platform | Linux only (`CAP_NET_RAW` required) |
|
||||
|
||||
### Beacon Discovery
|
||||
|
||||
Ethernet nodes discover peers via broadcast beacons sent to
|
||||
ff:ff:ff:ff:ff:ff. Each beacon is a 34-byte frame containing the sender's
|
||||
x-only public key. Receiving nodes extract the MAC source address from the
|
||||
frame and the public key from the payload, then report the discovered peer
|
||||
to FMP.
|
||||
|
||||
Four configuration flags control discovery behavior:
|
||||
|
||||
| Flag | Default | Description |
|
||||
| ---- | ------- | ----------- |
|
||||
| `discovery` | true | Listen for beacons from other nodes |
|
||||
| `announce` | false | Broadcast beacons periodically |
|
||||
| `auto_connect` | false | Initiate handshakes to discovered peers |
|
||||
| `accept_connections` | false | Accept inbound handshake attempts |
|
||||
|
||||
A typical discoverable node sets `announce: true`, `auto_connect: true`, and
|
||||
`accept_connections: true`. A passive listener uses just `discovery: true` to
|
||||
observe the network without announcing itself.
|
||||
|
||||
### WiFi Compatibility
|
||||
|
||||
WiFi interfaces in infrastructure (managed) mode work transparently for
|
||||
unicast — the mac80211 subsystem handles frame translation between 802.11
|
||||
and 802.3. Broadcast beacon discovery is unreliable in managed mode because
|
||||
access points commonly isolate clients from each other's broadcast traffic.
|
||||
|
||||
Startup logging:
|
||||
|
||||
```text
|
||||
Ethernet transport started name=eth0 interface=eth0 mac=aa:bb:cc:dd:ee:ff mtu=1499 if_mtu=1500
|
||||
```
|
||||
|
||||
## Discovery
|
||||
|
||||
Discovery determines that a FIPS-capable endpoint is reachable at a given
|
||||
@@ -255,7 +325,7 @@ FMP handles both cases uniformly: with discovery, it waits for events then
|
||||
initiates link setup; without discovery, it initiates link setup directly to
|
||||
configured addresses.
|
||||
|
||||
### Local/Medium Discovery *(future direction)*
|
||||
### Local/Medium Discovery
|
||||
|
||||
For transports where endpoints share a physical or link-layer medium — LAN
|
||||
broadcast, radio, BLE — discovery uses beacon and query mechanisms:
|
||||
@@ -289,6 +359,7 @@ is reachable at UDP 1.2.3.4:9735, then establishes the link over the UDP
|
||||
transport.
|
||||
|
||||
Key properties:
|
||||
|
||||
- Identity is built in — Nostr events are signed, so discovery information
|
||||
is authenticated
|
||||
- Relay selection acts as scoping — which relays a node publishes to and
|
||||
@@ -298,10 +369,12 @@ Key properties:
|
||||
|
||||
### Current State
|
||||
|
||||
> **Implemented**: Peer addresses come from YAML configuration. The
|
||||
> transport trait's `discover()` method exists but returns an empty list for
|
||||
> UDP. Transport-level discovery (beacon/query, Nostr relay) is not yet
|
||||
> implemented.
|
||||
> **Implemented**: UDP peers are configured via YAML. Ethernet peers are
|
||||
> discovered via beacon broadcast — the `discover()` trait method returns
|
||||
> newly seen endpoints, and per-transport `auto_connect()` /
|
||||
> `accept_connections()` policies control whether discovered peers are
|
||||
> connected automatically or require explicit configuration. Nostr relay
|
||||
> discovery is not yet implemented.
|
||||
|
||||
## Transport Interface
|
||||
|
||||
@@ -312,6 +385,7 @@ The transport interface defines what every transport driver must provide.
|
||||
```text
|
||||
transport_id() → TransportId Unique identifier for this transport instance
|
||||
transport_type() → &TransportType Static metadata (name, connection-oriented, reliable)
|
||||
name() → Option<&str> Instance name (for multi-instance transports)
|
||||
state() → TransportState Current lifecycle state
|
||||
mtu() → u16 Transport-wide default MTU
|
||||
link_mtu(addr) → u16 Per-link MTU (defaults to mtu())
|
||||
@@ -319,6 +393,8 @@ start() → lifecycle Bring transport up (bind socket, o
|
||||
stop() → lifecycle Bring transport down
|
||||
send(addr, data) → delivery Send datagram to transport address
|
||||
discover() → Vec<DiscoveredPeer> Report discovered FIPS endpoints (optional)
|
||||
auto_connect() → bool Auto-connect discovered peers (default: false)
|
||||
accept_connections() → bool Accept inbound handshakes (default: true)
|
||||
```
|
||||
|
||||
### Receive Path
|
||||
@@ -330,6 +406,7 @@ node's main event loop reads from the corresponding receiver, which
|
||||
aggregates datagrams from all active transports into a single stream.
|
||||
|
||||
Each inbound datagram carries:
|
||||
|
||||
- **transport_id** — which transport it arrived on
|
||||
- **remote_addr** — the transport address of the sender
|
||||
- **data** — the raw datagram bytes
|
||||
@@ -373,7 +450,7 @@ transitions through `Starting` to `Up` (operational). `stop()` moves to
|
||||
| --------- | ------ | ----- |
|
||||
| UDP/IP | **Implemented** | Primary transport, async send/receive, configurable MTU |
|
||||
| TCP/IP | Future direction | Requires stream framing, TCP-over-TCP concern |
|
||||
| Ethernet | Future direction | AF_PACKET raw frames, EtherType TBD |
|
||||
| Ethernet | **Implemented** | AF_PACKET SOCK_DGRAM, EtherType 0x88B5, beacon discovery, Linux only |
|
||||
| WiFi | Future direction | Infrastructure mode = Ethernet driver |
|
||||
| Tor | Future direction | High latency, .onion addressing |
|
||||
| BLE | Future direction | ATT_MTU negotiation, per-link MTU |
|
||||
|
||||
@@ -23,6 +23,12 @@ transports (TCP, WebSocket, Tor) must delineate FIPS packets within the
|
||||
byte stream; the common prefix `payload_len` field provides this
|
||||
framing directly.
|
||||
|
||||
**Ethernet frame type prefix.** The Ethernet transport prepends a 1-byte
|
||||
frame type before the FMP payload: `0x00` for data frames and `0x01` for
|
||||
beacon (discovery) frames. This byte is consumed by the transport layer
|
||||
and is not visible to FMP. The effective MTU for FMP is the interface
|
||||
MTU minus one byte (typically 1499).
|
||||
|
||||
## Link-Layer Formats
|
||||
|
||||
All FMP packets begin with a **4-byte common prefix** that identifies the
|
||||
|
||||
Reference in New Issue
Block a user