Merge branch 'refactor-transport' into refactor-next

Forward-merge the transport neighbor-beacon rename (ethernet + BLE
discovery -> neighbor module/buffer rename; ethernet config discovery
flag -> listen with serde alias). Hand-resolved the module files against
next's identity-out-of-beacon beacon rewrite by starting from next's
bodies and re-applying the rename by identifier, so next's beacon logic
(no-arg build_beacon, bool parse_beacon, computed BEACON_SIZE, one-arg
add_peer, deleted BLE add_peer_with_pubkey) is preserved unchanged.
This commit is contained in:
Johnathan Corgan
2026-07-11 23:20:50 +00:00
15 changed files with 129 additions and 100 deletions
+10
View File
@@ -129,6 +129,13 @@ with v0.4.x or earlier peers.
dialing, e.g. configured `auto_connect` peers — is handled by that
late check, since those peers return earlier via the cross-connection
paths and are not subject to the cap.
- The Ethernet transport's per-interface `discovery` flag was renamed to
`listen` (`transports.ethernet.*`) to match the symmetric `announce`
(transmit) / `listen` (receive) neighbor-beacon vocabulary. The old
`discovery:` key is still accepted via a serde alias, so deployed configs
continue to load unchanged; `Config::to_yaml()` re-emits it under the
canonical `listen:` name. Update your `fips.yaml` to `listen:`.
- The mesh-lookup control-metrics family is now emitted under the key
`lookup` in `fipsctl stats metrics` and `show routing`. The former key
`discovery` is still emitted as a deprecated alias carrying identical
@@ -150,6 +157,9 @@ with v0.4.x or earlier peers.
(mesh-lookup) and `node.rendezvous.*` (peer rendezvous). A legacy
`node.discovery:` block still applies for now with a deprecation warning and
will be removed; migrate to `node.lookup.*` / `node.rendezvous.*`.
- The Ethernet `transports.ethernet.discovery` flag, renamed to
`transports.ethernet.listen`. The old key is still accepted via a serde
alias and will be removed at the v2 cutover; migrate to `listen`.
### Fixed
+6 -6
View File
@@ -262,7 +262,7 @@ 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
- **Broadcast neighbor detection**: 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
@@ -290,7 +290,7 @@ that would otherwise corrupt AEAD verification. Frame types: `0x00` (data),
| Addressing | 6-byte MAC address |
| Platform | Linux only (`CAP_NET_RAW` required) |
### Beacon Discovery
### Neighbor Beacons
Ethernet nodes discover peers via broadcast beacons sent to
ff:ff:ff:ff:ff:ff. Beacons are minimal 5-byte frames (4-byte header +
@@ -299,7 +299,7 @@ learned from the Noise XX handshake after the connection is established.
Receiving nodes extract the MAC source address from the frame and report
the discovered address to FMP.
Four configuration flags control discovery behavior — `discovery`
Four configuration flags control neighbor behavior — `listen`
(listen for beacons), `announce` (broadcast beacons), `auto_connect`
(initiate handshakes to discovered peers), and `accept_connections`
(accept inbound handshakes). The flag table and per-flag defaults
@@ -308,13 +308,13 @@ under `transports.ethernet.*`.
A typical discoverable node sets `announce`, `auto_connect`, and
`accept_connections` all true. A passive listener uses just
`discovery: true` to observe the network without announcing itself.
`listen: 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
and 802.3. Broadcast neighbor detection is unreliable in managed mode because
access points commonly isolate clients from each other's broadcast traffic.
Startup logging:
@@ -893,7 +893,7 @@ transitions through `Starting` to `Up` (operational). `stop()` moves to
| --------- | ------ | ----- |
| UDP/IP | **Implemented** | Primary transport, AsyncFd/recvmsg, SO_RXQ_OVFL kernel drop detection |
| TCP/IP | **Implemented** | FMP header-based framing, non-blocking connect, per-connection MSS MTU |
| Ethernet | **Implemented** | AF_PACKET SOCK_DGRAM, EtherType 0x2121, beacon discovery, Linux only |
| Ethernet | **Implemented** | AF_PACKET SOCK_DGRAM, EtherType 0x2121, neighbor beacons, Linux only |
| WiFi | **Implemented** (via Ethernet transport, infrastructure mode) | mac80211 translates 802.11↔802.3; broadcast beacons unreliable through APs |
| Tor | **Implemented** | Outbound SOCKS5, inbound via onion service, .onion and clearnet addressing |
| Nym | **Implemented** | Outbound-only SOCKS5 through nym-socks5-client, mixnet anonymity, IP/hostname addressing |
+6 -6
View File
@@ -437,7 +437,7 @@ Requires `CAP_NET_RAW` or running as root. Linux only.
| `mtu` | u16 | *(auto)* | Override MTU. Default: interface MTU minus 3 (for frame type + length 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 |
| `listen` | bool | `true` | Listen for neighbor 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 |
@@ -451,7 +451,7 @@ transports:
ethernet:
lan:
interface: "eth0"
discovery: true
listen: true
announce: true
backbone:
interface: "eth1"
@@ -459,7 +459,7 @@ transports:
```
Each named instance operates independently with its own socket and
discovery state. The instance name is used in log messages and the
neighbor state. The instance name is used in log messages and the
`name()` method on the Transport trait.
### TCP (`transports.tcp.*`)
@@ -841,7 +841,7 @@ peers:
### Mixed UDP + Ethernet Example
A node bridging internet peers (UDP) and a local Ethernet segment with
beacon discovery:
neighbor beacons:
```yaml
node:
@@ -857,7 +857,7 @@ transports:
mtu: 1472
ethernet:
interface: "eth0"
discovery: true
listen: true
announce: true
auto_connect: true
accept_connections: true
@@ -1001,7 +1001,7 @@ transports:
# mtu: null # null = interface MTU - 3 (typically 1497)
# recv_buf_size: 2097152 # 2 MB
# send_buf_size: 2097152 # 2 MB
# discovery: true # listen for beacons
# listen: true # listen for beacons
# announce: false # broadcast beacons
# auto_connect: false # connect to discovered peers
# accept_connections: false # accept inbound handshakes
+1 -1
View File
@@ -209,7 +209,7 @@ for the metadata-privacy model and the rejection of onion routing.
| --------- | --------------- | ------------ | ------ |
| UDP | None until `bind_addr` set | `0.0.0.0:2121` typical | Operator sets `transports.udp.bind_addr` |
| TCP | None until `bind_addr` set | None — outbound-only without bind | Operator sets `transports.tcp.bind_addr` |
| Ethernet | Listens on configured interface (raw `AF_PACKET`) | EtherType 0x2121 on selected interface | Per-flag `discovery`, `announce`, `auto_connect`, `accept_connections` |
| Ethernet | Listens on configured interface (raw `AF_PACKET`) | EtherType 0x2121 on selected interface | Per-flag `listen`, `announce`, `auto_connect`, `accept_connections` |
| Tor | None until `directory_service` configured | `127.0.0.1:8443` (loopback only) | Operator sets `transports.tor.directory_service` and configures `HiddenServiceDir` in `torrc` |
| BLE | Off by default | n/a | Operator enables `transports.ble.*` |
| Nostr discovery | Off by default | n/a (relay client, not a listener) | Operator sets `node.discovery.nostr.enabled: true` |
+9 -9
View File
@@ -71,7 +71,7 @@ supplies the rest:
- **Addressing**: the `fips0` adapter takes an `fd97:...` ULA
derived from the npub. No DHCP. No SLAAC. The address is
cryptographically tied to the identity.
- **Discovery**: each daemon broadcasts a small beacon on the
- **Neighbor detection**: each daemon broadcasts a small beacon on the
link advertising its npub; the other daemon's listener picks
it up and dials in over the same link.
- **Routing**: the FIPS mesh layer builds its own spanning tree
@@ -177,7 +177,7 @@ different interface names — that is normal.
Edit `/etc/fips/fips.yaml` on **both** nodes. Under
`transports:`, add an `ethernet:` block. The key settings are
the four discovery flags — both nodes must opt in to all four,
the four neighbor flags — both nodes must opt in to all four,
and they default to off:
```yaml
@@ -185,7 +185,7 @@ transports:
ethernet:
interface: "<eth>" # the name from Step 1
announce: true # broadcast our beacon on the link
discovery: true # listen for beacons (default; shown for clarity)
listen: true # listen for beacons (default; shown for clarity)
auto_connect: true # dial peers we discover
accept_connections: true # accept dial-ins from peers we discover
```
@@ -194,7 +194,7 @@ Each flag does one thing:
- `announce: true` — emit a small beacon every
`beacon_interval_secs` (default 30s) carrying our npub.
- `discovery: true` — listen for incoming beacons; populate a
- `listen: true` — listen for incoming beacons; populate a
candidate-peer list keyed by source MAC and observed npub.
- `auto_connect: true` — when we see a beacon from an npub
we have not yet peered with, initiate the outbound Noise
@@ -218,7 +218,7 @@ is "all four flags on both ends."
> lan:
> interface: "eth0"
> announce: true
> discovery: true
> listen: true
> auto_connect: true
> accept_connections: true
> dongle:
@@ -227,7 +227,7 @@ is "all four flags on both ends."
> # ...
> ```
>
> Each named instance runs its own socket and discovery state.
> Each named instance runs its own socket and neighbor state.
> A single ground-up link only needs the flat form shown
> first; named instances become useful when the same node
> bridges multiple physical segments.
@@ -390,7 +390,7 @@ What you do need on the AP side:
networks and "secure" enterprise APs ship with it on.
When client isolation is on, the AP refuses to forward
station-to-station frames — the broadcast beacons never
arrive at the other node, and discovery fails silently.
arrive at the other node, and neighbor detection fails silently.
If beacons aren't crossing, this is the first thing to
check.
@@ -401,7 +401,7 @@ adapter name.
### Bluetooth LE (experimental but works)
BLE is a separate transport (`transports.ble.*`) with its own
discovery model — L2CAP advertisements rather than raw L2
neighbor-detection model — L2CAP advertisements rather than raw L2
broadcasts. The shape of the tutorial is the same (advertise +
scan + auto-connect + accept), but the prerequisites are
different: BlueZ, `bluetoothd`, an HCI adapter, and the
@@ -424,7 +424,7 @@ Windows builds skip it.
a radio link), `CAP_NET_RAW`, and a few config flags on each
end are sufficient. The mesh supplies its own identity,
addressing, discovery, and routing.
- **Discovery is a four-flag opt-in.** `announce`, `discovery`,
- **Neighbor detection is a four-flag opt-in.** `announce`, `listen`,
`auto_connect`, and `accept_connections` each control one
thing; both ends must agree before a link will form.
- **The two modes coexist.** Overlay peers and ground-up peers
+1 -1
View File
@@ -98,7 +98,7 @@ transports:
# Ethernet transport — uncomment and set your interface name.
# ethernet:
# interface: "eth0"
# discovery: true
# listen: true
# announce: true
# auto_connect: true
# accept_connections: true
@@ -83,19 +83,19 @@ transports:
ethernet:
wan:
interface: "eth0"
discovery: true
listen: true
announce: true
auto_connect: true
accept_connections: true
wwan:
interface: "phy0-sta0"
discovery: true
listen: true
announce: true
auto_connect: true
accept_connections: true
lan:
interface: "br-lan"
discovery: true
listen: true
announce: true
auto_connect: true
accept_connections: true
+1 -1
View File
@@ -68,7 +68,7 @@ and set the interface name:
transports:
ethernet:
interface: "eth0"
discovery: true
listen: true
announce: true
auto_connect: true
accept_connections: true
+25 -6
View File
@@ -282,9 +282,10 @@ pub struct EthernetConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub send_buf_size: Option<usize>,
/// Listen for discovery beacons from other nodes. Default: true.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub discovery: Option<bool>,
/// Listen for neighbor beacons from other nodes. Default: true.
/// (Renamed from `discovery`; the old key is still accepted.)
#[serde(default, alias = "discovery", skip_serializing_if = "Option::is_none")]
pub listen: Option<bool>,
/// Broadcast announcement beacons on the LAN. Default: false.
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -319,9 +320,9 @@ impl EthernetConfig {
self.send_buf_size.unwrap_or(DEFAULT_ETHERNET_SEND_BUF)
}
/// Whether to listen for discovery beacons. Default: true.
pub fn discovery(&self) -> bool {
self.discovery.unwrap_or(true)
/// Whether to listen for neighbor beacons. Default: true.
pub fn listen(&self) -> bool {
self.listen.unwrap_or(true)
}
/// Whether to broadcast announcement beacons. Default: false.
@@ -1046,4 +1047,22 @@ mod tests {
assert_eq!(parse_bind_port("[::]:443"), Some(443));
assert_eq!(parse_bind_port("not-a-socket-addr"), None);
}
#[test]
fn ethernet_listen_accepts_legacy_discovery_alias_and_rejects_unknown() {
// (a) The legacy `discovery:` key is still accepted via serde alias.
let legacy: EthernetConfig =
serde_yaml::from_str("interface: eth0\ndiscovery: true\n").unwrap();
assert_eq!(legacy.listen, Some(true));
// (b) The new canonical `listen:` key parses into the renamed field.
let renamed: EthernetConfig =
serde_yaml::from_str("interface: eth0\nlisten: true\n").unwrap();
assert_eq!(renamed.listen, Some(true));
// (c) `deny_unknown_fields` still rejects an unknown ethernet key.
let bogus: Result<EthernetConfig, _> =
serde_yaml::from_str("interface: eth0\nbogus: true\n");
assert!(bogus.is_err());
}
}
+1 -1
View File
@@ -83,7 +83,7 @@ async fn make_test_node_ethernet(interface: &str) -> TestNode {
let config = EthernetConfig {
interface: interface.to_string(),
discovery: Some(false),
listen: Some(false),
announce: Some(false),
accept_connections: Some(true),
..Default::default()
+17 -17
View File
@@ -7,7 +7,7 @@
//!
//! ## Architecture
//!
//! Transport logic (pool, discovery, lifecycle) is separated from the
//! Transport logic (pool, neighbor, lifecycle) is separated from the
//! BlueZ/bluer stack via the `BleIo` trait. `BluerIo` provides the real
//! implementation (behind `cfg(bluer_available)`); `MockBleIo` provides
//! an in-memory test double for CI without hardware.
@@ -19,8 +19,8 @@
//! static (configured) peers get priority over discovered peers.
pub mod addr;
pub mod discovery;
pub mod io;
pub mod neighbor;
pub mod pool;
pub mod stats;
@@ -30,8 +30,8 @@ use super::{
};
use crate::config::BleConfig;
use addr::BleAddr;
use discovery::DiscoveryBuffer;
use io::{BleIo, BleScanner, BleStream};
use neighbor::NeighborBuffer;
use pool::{BleConnection, ConnectionPool};
use stats::BleStats;
@@ -86,8 +86,8 @@ pub struct BleTransport<I: BleIo> {
accept_task: Option<JoinHandle<()>>,
/// Combined scan + probe loop task handle.
scan_probe_task: Option<JoinHandle<()>>,
/// Discovery buffer for discovered peers.
discovery_buffer: Arc<DiscoveryBuffer>,
/// Neighbor buffer for discovered peers.
neighbor_buffer: Arc<NeighborBuffer>,
/// Transport statistics.
stats: Arc<BleStats>,
}
@@ -118,7 +118,7 @@ impl<I: BleIo> BleTransport<I> {
packet_tx,
accept_task: None,
scan_probe_task: None,
discovery_buffer: Arc::new(DiscoveryBuffer::new(transport_id)),
neighbor_buffer: Arc::new(NeighborBuffer::new(transport_id)),
stats: Arc::new(BleStats::new()),
}
}
@@ -165,7 +165,7 @@ impl<I: BleIo> BleTransport<I> {
transport_id,
stats,
max_conns,
Arc::clone(&self.discovery_buffer),
Arc::clone(&self.neighbor_buffer),
)));
debug!(adapter = %adapter, psm = psm, "BLE accept loop started");
}
@@ -195,7 +195,7 @@ impl<I: BleIo> BleTransport<I> {
scanner,
Arc::clone(&self.io),
Arc::clone(&self.pool),
Arc::clone(&self.discovery_buffer),
Arc::clone(&self.neighbor_buffer),
Arc::clone(&self.stats),
self.config.psm(),
self.config.connect_timeout_ms(),
@@ -575,7 +575,7 @@ impl<I: BleIo> Transport for BleTransport<I> {
}
fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
Ok(self.discovery_buffer.take())
Ok(self.neighbor_buffer.take())
}
fn auto_connect(&self) -> bool {
@@ -604,7 +604,7 @@ async fn accept_loop<A>(
transport_id: TransportId,
stats: Arc<BleStats>,
_max_conns: usize,
discovery_buffer: Arc<DiscoveryBuffer>,
neighbor_buffer: Arc<NeighborBuffer>,
) where
A: io::BleAcceptor,
A::Stream: 'static,
@@ -627,7 +627,7 @@ async fn accept_loop<A>(
let send_mtu = stream.send_mtu();
let recv_mtu = stream.recv_mtu();
discovery_buffer.add_peer(&addr);
neighbor_buffer.add_peer(&addr);
let stream = Arc::new(stream);
@@ -721,7 +721,7 @@ async fn receive_loop<S: BleStream>(
/// Each scan result is probed immediately unless the address is in cooldown
/// (recently probed) or already connected. On successful probe, the
/// connection is promoted directly into the pool (no second L2CAP connect
/// needed) and the peer is reported to the discovery buffer for the node
/// needed) and the peer is reported to the neighbor buffer for the node
/// layer to auto-connect.
///
/// Cooldown prevents rapid re-probing of the same address: after any probe
@@ -732,7 +732,7 @@ async fn scan_probe_loop<I: io::BleIo>(
mut scanner: I::Scanner,
io: Arc<I>,
pool: Arc<Mutex<ConnectionPool<Arc<I::Stream>>>>,
buffer: Arc<DiscoveryBuffer>,
buffer: Arc<NeighborBuffer>,
stats: Arc<BleStats>,
psm: u16,
connect_timeout_ms: u64,
@@ -938,7 +938,7 @@ mod tests {
#[tokio::test(start_paused = true)]
async fn test_scan_discovers_peers() {
let io = MockBleIo::new("hci0", test_addr(1));
// Probe connect must succeed for peers to reach the discovery buffer
// Probe connect must succeed for peers to reach the neighbor buffer
let local = test_addr(1);
io.set_connect_handler(move |addr, _psm| {
let (stream, _peer) = io::MockBleStream::pair(local.clone(), addr.clone(), 2048);
@@ -958,8 +958,8 @@ mod tests {
// Let the expired entries get processed
tokio::task::yield_now().await;
// Scan results go to discovery buffer as bare addresses after probe
let peers = transport.discovery_buffer.take();
// Scan results go to neighbor buffer as bare addresses after probe
let peers = transport.neighbor_buffer.take();
assert_eq!(peers.len(), 2);
}
@@ -983,7 +983,7 @@ mod tests {
tokio::time::advance(std::time::Duration::from_secs(6)).await;
tokio::task::yield_now().await;
let peers = transport.discovery_buffer.take();
let peers = transport.neighbor_buffer.take();
assert_eq!(peers.len(), 1);
}
@@ -1,4 +1,4 @@
//! BLE discovery via advertising and scanning.
//! BLE neighbor detection via advertising and scanning.
//!
//! BLE advertisements carry a 128-bit FIPS service UUID for identification.
//! Post-forklift, advertisements are UUID-only (no identity material);
@@ -11,15 +11,15 @@ use super::addr::BleAddr;
/// Buffer for discovered BLE peers, drained by `discover()`.
///
/// Follows the same pattern as Ethernet's `DiscoveryBuffer`: peers are
/// added from the scan loop and drained by the node's discovery polling.
pub struct DiscoveryBuffer {
/// Follows the same pattern as Ethernet's `NeighborBuffer`: peers are
/// added from the scan loop and drained by the node's neighbor polling.
pub struct NeighborBuffer {
transport_id: TransportId,
peers: Mutex<Vec<DiscoveredPeer>>,
}
impl DiscoveryBuffer {
/// Create a new empty discovery buffer.
impl NeighborBuffer {
/// Create a new empty neighbor buffer.
pub fn new(transport_id: TransportId) -> Self {
Self {
transport_id,
@@ -64,8 +64,8 @@ mod tests {
}
#[test]
fn test_discovery_buffer_add_take() {
let buffer = DiscoveryBuffer::new(TransportId::new(1));
fn test_neighbor_buffer_add_take() {
let buffer = NeighborBuffer::new(TransportId::new(1));
buffer.add_peer(&test_addr(1));
let peers = buffer.take();
@@ -77,8 +77,8 @@ mod tests {
}
#[test]
fn test_discovery_buffer_dedup() {
let buffer = DiscoveryBuffer::new(TransportId::new(1));
fn test_neighbor_buffer_dedup() {
let buffer = NeighborBuffer::new(TransportId::new(1));
buffer.add_peer(&test_addr(1));
buffer.add_peer(&test_addr(1)); // same address again
@@ -87,8 +87,8 @@ mod tests {
}
#[test]
fn test_discovery_buffer_multiple_peers() {
let buffer = DiscoveryBuffer::new(TransportId::new(1));
fn test_neighbor_buffer_multiple_peers() {
let buffer = NeighborBuffer::new(TransportId::new(1));
buffer.add_peer(&test_addr(1));
buffer.add_peer(&test_addr(2));
buffer.add_peer(&test_addr(3));
@@ -98,8 +98,8 @@ mod tests {
}
#[test]
fn test_discovery_buffer_transport_addr_format() {
let buffer = DiscoveryBuffer::new(TransportId::new(1));
fn test_neighbor_buffer_transport_addr_format() {
let buffer = NeighborBuffer::new(TransportId::new(1));
buffer.add_peer(&test_addr(0x42));
let peers = buffer.take();
+17 -17
View File
@@ -6,8 +6,8 @@
//! 802.11 transparently on Linux).
pub mod addr;
pub mod discovery;
pub mod io;
pub mod neighbor;
pub mod stats;
pub use addr::parse_mac_string;
@@ -17,8 +17,8 @@ use super::{
TransportId, TransportState, TransportType,
};
use crate::config::EthernetConfig;
use discovery::{DiscoveryBuffer, FRAME_TYPE_BEACON, FRAME_TYPE_DATA, build_beacon, parse_beacon};
use io::{AsyncPacketSocket, ETHERNET_BROADCAST, PacketSocket};
use neighbor::{FRAME_TYPE_BEACON, FRAME_TYPE_DATA, NeighborBuffer, build_beacon, parse_beacon};
use stats::EthernetStats;
use std::sync::Arc;
@@ -53,8 +53,8 @@ pub struct EthernetTransport {
interface: String,
/// Effective MTU (interface MTU - 4 for frame header).
effective_mtu: u16,
/// Discovery buffer for discovered peers.
discovery_buffer: Arc<DiscoveryBuffer>,
/// Neighbor buffer for discovered peers.
neighbor_buffer: Arc<NeighborBuffer>,
/// Transport-level statistics.
stats: Arc<EthernetStats>,
}
@@ -68,7 +68,7 @@ impl EthernetTransport {
packet_tx: PacketTx,
) -> Self {
let interface = config.interface.clone();
let discovery_buffer = Arc::new(DiscoveryBuffer::new(transport_id));
let neighbor_buffer = Arc::new(NeighborBuffer::new(transport_id));
let stats = Arc::new(EthernetStats::new());
Self {
@@ -83,7 +83,7 @@ impl EthernetTransport {
local_mac: None,
interface,
effective_mtu: 1496, // default, updated on start
discovery_buffer,
neighbor_buffer,
stats,
}
}
@@ -150,8 +150,8 @@ impl EthernetTransport {
let transport_id = self.transport_id;
let packet_tx = self.packet_tx.clone();
let mtu = self.effective_mtu;
let discovery_enabled = self.config.discovery();
let discovery_buffer = self.discovery_buffer.clone();
let listen_enabled = self.config.listen();
let neighbor_buffer = self.neighbor_buffer.clone();
let stats = self.stats.clone();
let recv_socket = socket.clone();
@@ -161,8 +161,8 @@ impl EthernetTransport {
transport_id,
packet_tx,
mtu,
discovery_enabled,
discovery_buffer,
listen_enabled,
neighbor_buffer,
stats,
)
.await;
@@ -346,7 +346,7 @@ impl Transport for EthernetTransport {
}
fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
Ok(self.discovery_buffer.take())
Ok(self.neighbor_buffer.take())
}
fn auto_connect(&self) -> bool {
@@ -368,8 +368,8 @@ async fn ethernet_receive_loop(
transport_id: TransportId,
packet_tx: PacketTx,
mtu: u16,
discovery_enabled: bool,
discovery_buffer: Arc<DiscoveryBuffer>,
listen_enabled: bool,
neighbor_buffer: Arc<NeighborBuffer>,
stats: Arc<EthernetStats>,
) {
// Buffer with headroom: frame type prefix + MTU + some extra
@@ -426,12 +426,12 @@ async fn ethernet_receive_loop(
FRAME_TYPE_BEACON => {
stats.record_beacon_recv();
if discovery_enabled && parse_beacon(&buf[..len]) {
discovery_buffer.add_peer(src_mac);
if listen_enabled && parse_beacon(&buf[..len]) {
neighbor_buffer.add_peer(src_mac);
trace!(
transport_id = %transport_id,
remote_mac = %format_mac(&src_mac),
"Discovery beacon received"
"Neighbor beacon received"
);
}
}
@@ -715,7 +715,7 @@ mod tests {
#[test]
fn test_beacon_size() {
assert_eq!(discovery::BEACON_SIZE, 5);
assert_eq!(neighbor::BEACON_SIZE, 5);
}
#[test]
@@ -1,16 +1,16 @@
//! Ethernet LAN discovery via broadcast beacons.
//! Ethernet LAN neighbor detection via broadcast beacons.
//!
//! Beacon format (5 bytes total):
//! - Unified header (4 bytes): `[type:1][flags:1][length:2 LE]`
//! - Version (1 byte): discovery protocol version
//! - Version (1 byte): beacon protocol version
use crate::transport::{DiscoveredPeer, TransportAddr, TransportId};
use std::sync::Mutex;
/// Discovery protocol version.
pub const DISCOVERY_VERSION: u8 = 0x01;
/// Beacon protocol version.
pub const BEACON_VERSION: u8 = 0x01;
/// Frame type prefix for discovery announcement beacons.
/// Frame type prefix for neighbor beacon frames.
pub const FRAME_TYPE_BEACON: u8 = 0x01;
/// Frame type prefix for FIPS data frames.
@@ -25,17 +25,17 @@ pub const BEACON_PAYLOAD_SIZE: usize = 1;
/// Total beacon size: header(4) + payload(1).
pub const BEACON_SIZE: usize = ETHERNET_HEADER_SIZE + BEACON_PAYLOAD_SIZE;
/// Build a discovery announcement beacon payload.
/// Build a neighbor beacon payload.
pub fn build_beacon() -> [u8; BEACON_SIZE] {
let mut buf = [0u8; BEACON_SIZE];
buf[0] = FRAME_TYPE_BEACON;
buf[1] = 0x00; // flags (reserved)
buf[2..4].copy_from_slice(&(BEACON_PAYLOAD_SIZE as u16).to_le_bytes());
buf[4] = DISCOVERY_VERSION;
buf[4] = BEACON_VERSION;
buf
}
/// Parse a discovery announcement beacon payload.
/// Parse a neighbor beacon payload.
///
/// Returns true if the payload is a valid beacon, false otherwise.
pub fn parse_beacon(data: &[u8]) -> bool {
@@ -50,17 +50,17 @@ pub fn parse_beacon(data: &[u8]) -> bool {
if length < 1 {
return false;
}
data[4] == DISCOVERY_VERSION
data[4] == BEACON_VERSION
}
/// Buffer for discovered peers, drained by `discover()`.
pub struct DiscoveryBuffer {
pub struct NeighborBuffer {
transport_id: TransportId,
peers: Mutex<Vec<DiscoveredPeer>>,
}
impl DiscoveryBuffer {
/// Create a new empty discovery buffer.
impl NeighborBuffer {
/// Create a new empty neighbor buffer.
pub fn new(transport_id: TransportId) -> Self {
Self {
transport_id,
@@ -100,7 +100,7 @@ mod tests {
assert_eq!(beacon[0], FRAME_TYPE_BEACON);
assert_eq!(beacon[1], 0x00); // flags
assert_eq!(u16::from_le_bytes([beacon[2], beacon[3]]), 1); // length
assert_eq!(beacon[4], DISCOVERY_VERSION);
assert_eq!(beacon[4], BEACON_VERSION);
assert!(parse_beacon(&beacon));
}
@@ -139,8 +139,8 @@ mod tests {
}
#[test]
fn test_discovery_buffer() {
let buffer = DiscoveryBuffer::new(TransportId::new(1));
fn test_neighbor_buffer() {
let buffer = NeighborBuffer::new(TransportId::new(1));
let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
buffer.add_peer(mac);
@@ -156,8 +156,8 @@ mod tests {
}
#[test]
fn test_discovery_buffer_dedup() {
let buffer = DiscoveryBuffer::new(TransportId::new(1));
fn test_neighbor_buffer_dedup() {
let buffer = NeighborBuffer::new(TransportId::new(1));
let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
buffer.add_peer(mac);
+1 -1
View File
@@ -67,7 +67,7 @@ def _build_ethernet_config(iface: str) -> dict:
"""Build an Ethernet transport config dict for a single interface."""
return {
"interface": iface,
"discovery": True,
"listen": True,
"announce": True,
"auto_connect": True,
"accept_connections": True,