From e362ab67a6937ab7bdd015798487741e00c5227c Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Sat, 11 Jul 2026 22:56:05 +0000 Subject: [PATCH 1/3] transport/ethernet: rename discovery module to neighbor Rename the ethernet link-local neighbor-beacon subsystem from the overloaded "discovery" vocabulary to a neighbor umbrella. The beacon frame vocabulary is retained (build_beacon/parse_beacon/BEACON_SIZE/ FRAME_TYPE_BEACON/FRAME_TYPE_DATA); only the subsystem and buffer identifiers change: DiscoveryBuffer becomes NeighborBuffer, discovery_buffer becomes neighbor_buffer, and DISCOVERY_VERSION becomes BEACON_VERSION (wire value 0x01 unchanged). Config toggles are left for a follow-up commit. Behavior-neutral. --- src/transport/ethernet/mod.rs | 26 +++++++------- .../ethernet/{discovery.rs => neighbor.rs} | 36 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) rename src/transport/ethernet/{discovery.rs => neighbor.rs} (83%) diff --git a/src/transport/ethernet/mod.rs b/src/transport/ethernet/mod.rs index b5862a2..e0ebca7 100644 --- a/src/transport/ethernet/mod.rs +++ b/src/transport/ethernet/mod.rs @@ -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 secp256k1::XOnlyPublicKey; @@ -56,8 +56,8 @@ pub struct EthernetTransport { /// (`[type:1][length:2 LE][payload]`). The 2-byte length field is required /// to trim NIC minimum-frame padding before AEAD verification. effective_mtu: u16, - /// Discovery buffer for discovered peers. - discovery_buffer: Arc, + /// Neighbor buffer for discovered peers. + neighbor_buffer: Arc, /// Transport-level statistics. stats: Arc, /// Node's public key for beacon construction. @@ -73,7 +73,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 { @@ -88,7 +88,7 @@ impl EthernetTransport { local_mac: None, interface, effective_mtu: 1499, // default, updated on start - discovery_buffer, + neighbor_buffer, stats, local_pubkey: None, } @@ -164,7 +164,7 @@ impl EthernetTransport { 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 neighbor_buffer = self.neighbor_buffer.clone(); let stats = self.stats.clone(); let recv_socket = socket.clone(); @@ -175,7 +175,7 @@ impl EthernetTransport { packet_tx, mtu, discovery_enabled, - discovery_buffer, + neighbor_buffer, stats, ) .await; @@ -368,7 +368,7 @@ impl Transport for EthernetTransport { } fn discover(&self) -> Result, TransportError> { - Ok(self.discovery_buffer.take()) + Ok(self.neighbor_buffer.take()) } fn auto_connect(&self) -> bool { @@ -391,7 +391,7 @@ async fn ethernet_receive_loop( packet_tx: PacketTx, mtu: u16, discovery_enabled: bool, - discovery_buffer: Arc, + neighbor_buffer: Arc, stats: Arc, ) { // Buffer with headroom: frame type prefix + MTU + some extra @@ -449,11 +449,11 @@ async fn ethernet_receive_loop( stats.record_beacon_recv(); if discovery_enabled && let Some(pubkey) = parse_beacon(&buf[..len]) { - discovery_buffer.add_peer(src_mac, pubkey); + neighbor_buffer.add_peer(src_mac, pubkey); trace!( transport_id = %transport_id, remote_mac = %format_mac(&src_mac), - "Discovery beacon received" + "Neighbor beacon received" ); } } @@ -735,6 +735,6 @@ mod tests { #[test] fn test_beacon_size() { - assert_eq!(discovery::BEACON_SIZE, 34); + assert_eq!(neighbor::BEACON_SIZE, 34); } } diff --git a/src/transport/ethernet/discovery.rs b/src/transport/ethernet/neighbor.rs similarity index 83% rename from src/transport/ethernet/discovery.rs rename to src/transport/ethernet/neighbor.rs index 26ff75e..a778d2c 100644 --- a/src/transport/ethernet/discovery.rs +++ b/src/transport/ethernet/neighbor.rs @@ -1,18 +1,18 @@ -//! Ethernet LAN discovery via broadcast beacons. +//! Ethernet LAN neighbor detection via broadcast beacons. //! //! Beacon format (34 bytes total): -//! - `0x01` (1 byte): frame type = discovery announcement -//! - `0x01` (1 byte): discovery protocol version +//! - `0x01` (1 byte): frame type = neighbor beacon +//! - `0x01` (1 byte): beacon protocol version //! - x-only public key (32 bytes): node's Nostr identity use crate::transport::{DiscoveredPeer, TransportAddr, TransportId}; use secp256k1::XOnlyPublicKey; 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. @@ -21,16 +21,16 @@ pub const FRAME_TYPE_DATA: u8 = 0x00; /// Total beacon payload size: type(1) + version(1) + pubkey(32). pub const BEACON_SIZE: usize = 34; -/// Build a discovery announcement beacon payload. +/// Build a neighbor beacon payload. pub fn build_beacon(pubkey: &XOnlyPublicKey) -> [u8; BEACON_SIZE] { let mut buf = [0u8; BEACON_SIZE]; buf[0] = FRAME_TYPE_BEACON; - buf[1] = DISCOVERY_VERSION; + buf[1] = BEACON_VERSION; buf[2..BEACON_SIZE].copy_from_slice(&pubkey.serialize()); buf } -/// Parse a discovery announcement beacon payload. +/// Parse a neighbor beacon payload. /// /// Returns the sender's public key, or None if the payload is invalid. pub fn parse_beacon(data: &[u8]) -> Option { @@ -40,20 +40,20 @@ pub fn parse_beacon(data: &[u8]) -> Option { if data[0] != FRAME_TYPE_BEACON { return None; } - if data[1] != DISCOVERY_VERSION { + if data[1] != BEACON_VERSION { return None; } XOnlyPublicKey::from_slice(&data[2..34]).ok() } /// Buffer for discovered peers, drained by `discover()`. -pub struct DiscoveryBuffer { +pub struct NeighborBuffer { transport_id: TransportId, peers: Mutex>, } -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, @@ -101,7 +101,7 @@ mod tests { assert_eq!(beacon.len(), BEACON_SIZE); assert_eq!(beacon[0], FRAME_TYPE_BEACON); - assert_eq!(beacon[1], DISCOVERY_VERSION); + assert_eq!(beacon[1], BEACON_VERSION); let parsed = parse_beacon(&beacon).unwrap(); assert_eq!(parsed, pubkey); @@ -134,8 +134,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 pubkey = test_pubkey(); let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; @@ -152,8 +152,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 pubkey = test_pubkey(); let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; From 4c95be0000110eac0064e51411904ee928e835c1 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Sat, 11 Jul 2026 23:01:48 +0000 Subject: [PATCH 2/3] transport/ble: rename discovery module to neighbor Mirror the ethernet neighbor rename in the BLE transport: the internal peer-detection buffer becomes NeighborBuffer (from DiscoveryBuffer) and the module is renamed discovery -> neighbor. The BlueR/bluez API terms (DiscoveryFilter, set_discovery_filter) and the BLE advertise/scan mechanism vocabulary are unchanged, as is BleConfig. Behavior-neutral. --- src/transport/ble/mod.rs | 38 +++++++++---------- .../ble/{discovery.rs => neighbor.rs} | 28 +++++++------- 2 files changed, 33 insertions(+), 33 deletions(-) rename src/transport/ble/{discovery.rs => neighbor.rs} (82%) diff --git a/src/transport/ble/mod.rs b/src/transport/ble/mod.rs index 9561dae..098868f 100644 --- a/src/transport/ble/mod.rs +++ b/src/transport/ble/mod.rs @@ -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; @@ -31,8 +31,8 @@ use super::{ use crate::config::BleConfig; use crate::identity::NodeAddr; use addr::BleAddr; -use discovery::DiscoveryBuffer; use io::{BleIo, BleScanner, BleStream}; +use neighbor::NeighborBuffer; use pool::{BleConnection, ConnectionPool}; use stats::BleStats; @@ -88,8 +88,8 @@ pub struct BleTransport { accept_task: Option>, /// Combined scan + probe loop task handle. scan_probe_task: Option>, - /// Discovery buffer for discovered peers. - discovery_buffer: Arc, + /// Neighbor buffer for discovered peers. + neighbor_buffer: Arc, /// Transport statistics. stats: Arc, /// Our public key for pre-handshake identity exchange. @@ -127,7 +127,7 @@ impl BleTransport { 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()), local_pubkey: None, } @@ -192,7 +192,7 @@ impl BleTransport { stats, max_conns, self.local_pubkey, - Arc::clone(&self.discovery_buffer), + Arc::clone(&self.neighbor_buffer), local_node_addr, ))); debug!(adapter = %adapter, psm = psm, "BLE accept loop started"); @@ -223,7 +223,7 @@ impl BleTransport { scanner, Arc::clone(&self.io), Arc::clone(&self.pool), - Arc::clone(&self.discovery_buffer), + Arc::clone(&self.neighbor_buffer), Arc::clone(&self.stats), self.local_pubkey, self.config.psm(), @@ -369,7 +369,7 @@ impl BleTransport { match pubkey_exchange(&stream, our_pubkey).await { Ok(peer_pubkey) => { debug!(addr = %addr, "BLE outbound pubkey exchange complete"); - self.discovery_buffer + self.neighbor_buffer .add_peer_with_pubkey(&ble_addr, peer_pubkey); } Err(e) => { @@ -470,7 +470,7 @@ impl BleTransport { let timeout_ms = self.config.connect_timeout_ms(); let addr_clone = addr.clone(); let local_pubkey = self.local_pubkey; - let discovery_buffer = Arc::clone(&self.discovery_buffer); + let neighbor_buffer = Arc::clone(&self.neighbor_buffer); let task = tokio::spawn(async move { let result = tokio::time::timeout( @@ -489,7 +489,7 @@ impl BleTransport { match pubkey_exchange(&stream, our_pubkey).await { Ok(peer_pubkey) => { debug!(addr = %addr_clone, "BLE outbound pubkey exchange complete"); - discovery_buffer.add_peer_with_pubkey(&ble_addr, peer_pubkey); + neighbor_buffer.add_peer_with_pubkey(&ble_addr, peer_pubkey); } Err(e) => { warn!( @@ -639,7 +639,7 @@ impl Transport for BleTransport { } fn discover(&self) -> Result, TransportError> { - Ok(self.discovery_buffer.take()) + Ok(self.neighbor_buffer.take()) } fn auto_connect(&self) -> bool { @@ -728,7 +728,7 @@ async fn accept_loop( stats: Arc, _max_conns: usize, local_pubkey: Option<[u8; 32]>, - discovery_buffer: Arc, + neighbor_buffer: Arc, local_node_addr: Option, ) where A: io::BleAcceptor, @@ -757,7 +757,7 @@ async fn accept_loop( match pubkey_exchange(&stream, our_pubkey).await { Ok(peer_pubkey) => { debug!(addr = %ta, "BLE inbound pubkey exchange complete"); - discovery_buffer.add_peer_with_pubkey(&addr, peer_pubkey); + neighbor_buffer.add_peer_with_pubkey(&addr, peer_pubkey); // Cross-probe tie-breaker: smaller NodeAddr's // outbound wins. If we're smaller, our outbound @@ -872,7 +872,7 @@ async fn receive_loop( /// 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 @@ -883,7 +883,7 @@ async fn scan_probe_loop( mut scanner: I::Scanner, io: Arc, pool: Arc>>>, - buffer: Arc, + buffer: Arc, stats: Arc, local_pubkey: Option<[u8; 32]>, psm: u16, @@ -1136,8 +1136,8 @@ mod tests { // Let the expired entries get processed tokio::task::yield_now().await; - // Without pubkey set, scan results go to discovery buffer as bare MACs - let peers = transport.discovery_buffer.take(); + // Without pubkey set, scan results go to neighbor buffer as bare MACs + let peers = transport.neighbor_buffer.take(); assert_eq!(peers.len(), 2); } @@ -1156,7 +1156,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); } diff --git a/src/transport/ble/discovery.rs b/src/transport/ble/neighbor.rs similarity index 82% rename from src/transport/ble/discovery.rs rename to src/transport/ble/neighbor.rs index 8db9736..e0b588c 100644 --- a/src/transport/ble/discovery.rs +++ b/src/transport/ble/neighbor.rs @@ -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); @@ -12,15 +12,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>, } -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, @@ -79,8 +79,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(); @@ -92,8 +92,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 @@ -102,8 +102,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)); @@ -113,8 +113,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(); From cbc089b82030b657328eea640b9a19723dd47558 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Sat, 11 Jul 2026 23:13:28 +0000 Subject: [PATCH 3/3] transport/ethernet: rename config discovery flag to listen Rename the Ethernet per-interface config flag from discovery to listen, so the receive/transmit toggle pair reads as the symmetric announce (transmit) / listen (receive) neighbor-beacon vocabulary. The old discovery: key is still accepted via a serde alias, so deployed configs load unchanged; to_yaml re-emits it under the canonical listen: name. Marked deprecated for removal at the v2 cutover. Updates the config field + accessor, the transport listen_enabled local, the one struct-literal test consumer, the chaos sim config generator, packaged fips.yaml examples, and the classified operator-facing docs (ethernet neighbor-beacon subsystem prose; the generic Transport discovery capability prose is left unchanged). Adds a compat parse test asserting the legacy alias, the new key, and that deny_unknown_fields still rejects unknown keys. Behavior-neutral. --- CHANGELOG.md | 10 ++++++ docs/design/fips-transport-layer.md | 12 +++---- docs/reference/configuration.md | 12 +++---- docs/reference/security.md | 2 +- docs/tutorials/ground-up-mesh.md | 18 +++++------ packaging/common/fips.yaml | 2 +- .../openwrt-ipk/files/etc/fips/fips.yaml | 6 ++-- packaging/systemd/README.install.md | 2 +- src/config/transport.rs | 31 +++++++++++++++---- src/node/tests/ethernet.rs | 2 +- src/transport/ethernet/mod.rs | 8 ++--- testing/chaos/sim/config_gen.py | 2 +- 12 files changed, 68 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d607c9..11adc62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- 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 @@ -32,6 +39,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 (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 diff --git a/docs/design/fips-transport-layer.md b/docs/design/fips-transport-layer.md index 18ad41a..a4b524d 100644 --- a/docs/design/fips-transport-layer.md +++ b/docs/design/fips-transport-layer.md @@ -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 @@ -293,7 +293,7 @@ socket. | 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. Each beacon is a 34-byte frame containing the sender's @@ -301,7 +301,7 @@ 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 — `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 @@ -310,13 +310,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: @@ -895,7 +895,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 | diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index f8de395..785011b 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -436,7 +436,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 | @@ -450,7 +450,7 @@ transports: ethernet: lan: interface: "eth0" - discovery: true + listen: true announce: true backbone: interface: "eth1" @@ -458,7 +458,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.*`) @@ -840,7 +840,7 @@ peers: ### Mixed UDP + Ethernet Example A node bridging internet peers (UDP) and a local Ethernet segment with -beacon discovery: +neighbor beacons: ```yaml node: @@ -856,7 +856,7 @@ transports: mtu: 1472 ethernet: interface: "eth0" - discovery: true + listen: true announce: true auto_connect: true accept_connections: true @@ -999,7 +999,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 diff --git a/docs/reference/security.md b/docs/reference/security.md index a439b0c..4a207a1 100644 --- a/docs/reference/security.md +++ b/docs/reference/security.md @@ -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` | diff --git a/docs/tutorials/ground-up-mesh.md b/docs/tutorials/ground-up-mesh.md index 2c5afd7..d1c97c7 100644 --- a/docs/tutorials/ground-up-mesh.md +++ b/docs/tutorials/ground-up-mesh.md @@ -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: "" # 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 diff --git a/packaging/common/fips.yaml b/packaging/common/fips.yaml index d4743e9..c8ea17c 100644 --- a/packaging/common/fips.yaml +++ b/packaging/common/fips.yaml @@ -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 diff --git a/packaging/openwrt-ipk/files/etc/fips/fips.yaml b/packaging/openwrt-ipk/files/etc/fips/fips.yaml index 363f241..ff8ecf2 100644 --- a/packaging/openwrt-ipk/files/etc/fips/fips.yaml +++ b/packaging/openwrt-ipk/files/etc/fips/fips.yaml @@ -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 diff --git a/packaging/systemd/README.install.md b/packaging/systemd/README.install.md index 06b2918..1333db6 100644 --- a/packaging/systemd/README.install.md +++ b/packaging/systemd/README.install.md @@ -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 diff --git a/src/config/transport.rs b/src/config/transport.rs index 1c73cc0..61651d7 100644 --- a/src/config/transport.rs +++ b/src/config/transport.rs @@ -282,9 +282,10 @@ pub struct EthernetConfig { #[serde(default, skip_serializing_if = "Option::is_none")] pub send_buf_size: Option, - /// Listen for discovery beacons from other nodes. Default: true. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub discovery: Option, + /// 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, /// 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 = + serde_yaml::from_str("interface: eth0\nbogus: true\n"); + assert!(bogus.is_err()); + } } diff --git a/src/node/tests/ethernet.rs b/src/node/tests/ethernet.rs index 227ef3a..ed24a09 100644 --- a/src/node/tests/ethernet.rs +++ b/src/node/tests/ethernet.rs @@ -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() diff --git a/src/transport/ethernet/mod.rs b/src/transport/ethernet/mod.rs index e0ebca7..d8209ed 100644 --- a/src/transport/ethernet/mod.rs +++ b/src/transport/ethernet/mod.rs @@ -163,7 +163,7 @@ 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 listen_enabled = self.config.listen(); let neighbor_buffer = self.neighbor_buffer.clone(); let stats = self.stats.clone(); let recv_socket = socket.clone(); @@ -174,7 +174,7 @@ impl EthernetTransport { transport_id, packet_tx, mtu, - discovery_enabled, + listen_enabled, neighbor_buffer, stats, ) @@ -390,7 +390,7 @@ async fn ethernet_receive_loop( transport_id: TransportId, packet_tx: PacketTx, mtu: u16, - discovery_enabled: bool, + listen_enabled: bool, neighbor_buffer: Arc, stats: Arc, ) { @@ -448,7 +448,7 @@ async fn ethernet_receive_loop( FRAME_TYPE_BEACON => { stats.record_beacon_recv(); - if discovery_enabled && let Some(pubkey) = parse_beacon(&buf[..len]) { + if listen_enabled && let Some(pubkey) = parse_beacon(&buf[..len]) { neighbor_buffer.add_peer(src_mac, pubkey); trace!( transport_id = %transport_id, diff --git a/testing/chaos/sim/config_gen.py b/testing/chaos/sim/config_gen.py index bd519fb..1b30144 100644 --- a/testing/chaos/sim/config_gen.py +++ b/testing/chaos/sim/config_gen.py @@ -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,