From 86c043cc946ac9b249de93d39836b27a5ddbf2e7 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Sat, 6 Jun 2026 12:16:26 +0000 Subject: [PATCH 1/2] docs: document libclang-dev as a mandatory Linux build prerequisite Linux source builds pull in rustables, whose build script runs bindgen to generate nftables bindings for the LAN gateway. bindgen needs libclang.so on the build host, so a clean source build fails with 'Unable to find libclang' unless libclang-dev (or llvm) is installed. The prerequisite text in README.md and CONTRIBUTING.md previously listed only the optional BLE dependencies, and packaging/README.md had no source-build prerequisite list at all. Document libclang-dev as a mandatory Linux build dependency, distinct from the optional BLE deps, and note that it is build-time only so pre-built .deb installs are unaffected. --- CONTRIBUTING.md | 9 ++++++--- README.md | 12 ++++++++++-- packaging/README.md | 22 ++++++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2583bfc..4e813a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,9 +35,12 @@ cargo test ``` The pinned toolchain in [rust-toolchain.toml](rust-toolchain.toml) is -used for deterministic builds. On Debian/Ubuntu, BLE-capable builds -need `bluez`, `libdbus-1-dev`, and `pkg-config` installed; the default -build picks up BLE if those are present and skips it cleanly if not. +used for deterministic builds. On Linux, a source build requires +`libclang` (`sudo apt install libclang-dev` on Debian/Ubuntu): the LAN +gateway's nftables bindings are generated by `bindgen` at build time +and fail without it. BLE-capable builds additionally need `bluez`, +`libdbus-1-dev`, and `pkg-config` installed; the default build picks +up BLE if those are present and skips it cleanly if not. For multi-node integration runs, Docker is required. The harness under [testing/](testing/) starts containerized topologies and diff --git a/README.md b/README.md index 565b836..d752cb4 100644 --- a/README.md +++ b/README.md @@ -122,8 +122,16 @@ supported; transport availability varies by platform. | Tor | ✅ | ✅ | ✅ | ✅ | | BLE | ✅ | ❌ | ❌ | ❌ | -On Linux, BLE requires BlueZ and libdbus -(`sudo apt install bluez libdbus-1-dev` on Debian / Ubuntu) and is +On Linux, a source build requires `libclang` — the LAN gateway's +nftables bindings are generated by `bindgen` at build time, which +needs `libclang.so` on the build host. Install it before building +(`sudo apt install libclang-dev` on Debian / Ubuntu); without it the +build fails inside the `rustables` crate with an "Unable to find +libclang" error. This is a build-time prerequisite only — it is not a +runtime dependency, and the pre-built `.deb` artifacts do not need it. + +BLE is optional and, on Linux, requires BlueZ and libdbus +(`sudo apt install bluez libdbus-1-dev` on Debian / Ubuntu). It is gated on a build-script probe — install the dependencies first and the `cargo build` line above picks it up. The OpenWrt ipk omits BLE because libdbus is not available on the target. diff --git a/packaging/README.md b/packaging/README.md index 7147728..d5d9644 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -15,6 +15,28 @@ make zip # Windows .zip package make all # deb + tarball (default) ``` +## Build Prerequisites + +These targets build FIPS from source, so the host needs a build +environment in addition to a Rust toolchain (the version pinned in +`rust-toolchain.toml` is auto-installed by rustup). + +On Linux, `libclang` is **required**: the LAN gateway's nftables +bindings are generated by `bindgen` at build time, which needs +`libclang.so` on the build host. Without it the build fails inside the +`rustables` crate with an "Unable to find libclang" error. + +```sh +sudo apt install libclang-dev # Debian / Ubuntu +``` + +This is a build-time prerequisite only — it is not a runtime +dependency, so hosts installing a pre-built `.deb` do not need it. + +BLE support is optional and, when building with it, additionally needs +`bluez`, `libdbus-1-dev`, and `pkg-config`; the build picks up BLE if +those are present and skips it cleanly if not. + ## Directory Structure ```text From 9dcc421f6ff95f1597a04b770505af8653a61ca2 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Sat, 6 Jun 2026 12:54:05 +0000 Subject: [PATCH 2/2] transport: recover poisoned mutex guards instead of panicking on lock The transport layer used Mutex::lock().unwrap() at ten sites across the UDP, BLE, and Ethernet code. A std mutex poisons if a thread panics while holding it, after which every lock().unwrap() on that same mutex also panics, turning one fault into a cascade. These critical sections only perform short HashMap/Vec operations on locally constructed values and are not reachable from peer input, but the idiom is fragile against any future in-section panic. Replace each with lock().unwrap_or_else(|e| e.into_inner()), which recovers the guarded data and removes the cascade with no new dependency and no call-graph change. Also replace four self.local_addr.unwrap() calls in the UDP start and adopt paths with a sentinel fallback. The value is provably set just above each log line today, but the unwrap is brittle against a future reordering; logging an unbound sentinel is harmless and cannot panic. --- src/transport/ble/discovery.rs | 6 +++--- src/transport/ble/io.rs | 10 ++++++++-- src/transport/ethernet/discovery.rs | 4 ++-- src/transport/ethernet/socket_macos.rs | 2 +- src/transport/udp/mod.rs | 12 ++++++------ 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/transport/ble/discovery.rs b/src/transport/ble/discovery.rs index 364b677..8db9736 100644 --- a/src/transport/ble/discovery.rs +++ b/src/transport/ble/discovery.rs @@ -34,7 +34,7 @@ impl DiscoveryBuffer { pub fn add_peer(&self, addr: &BleAddr) { let ta = addr.to_transport_addr(); let peer = DiscoveredPeer::new(self.transport_id, ta.clone()); - let mut peers = self.peers.lock().unwrap(); + let mut peers = self.peers.lock().unwrap_or_else(|e| e.into_inner()); // Deduplicate by address string let addr_str = addr.to_string_repr(); peers.retain(|p| p.addr.as_str() != Some(addr_str.as_str())); @@ -49,7 +49,7 @@ impl DiscoveryBuffer { pub fn add_peer_with_pubkey(&self, addr: &BleAddr, pubkey: XOnlyPublicKey) { let ta = addr.to_transport_addr(); let peer = DiscoveredPeer::with_hint(self.transport_id, ta.clone(), pubkey); - let mut peers = self.peers.lock().unwrap(); + let mut peers = self.peers.lock().unwrap_or_else(|e| e.into_inner()); let addr_str = addr.to_string_repr(); peers.retain(|p| p.addr.as_str() != Some(addr_str.as_str())); peers.push(peer); @@ -57,7 +57,7 @@ impl DiscoveryBuffer { /// Drain all discovered peers since the last call. pub fn take(&self) -> Vec { - let mut peers = self.peers.lock().unwrap(); + let mut peers = self.peers.lock().unwrap_or_else(|e| e.into_inner()); std::mem::take(&mut *peers) } } diff --git a/src/transport/ble/io.rs b/src/transport/ble/io.rs index 16c6c5c..178a8a2 100644 --- a/src/transport/ble/io.rs +++ b/src/transport/ble/io.rs @@ -634,7 +634,10 @@ impl MockBleIo { where F: Fn(&BleAddr, u16) -> Result + Send + Sync + 'static, { - *self.connect_handler.lock().unwrap() = Some(Box::new(handler)); + *self + .connect_handler + .lock() + .unwrap_or_else(|e| e.into_inner()) = Some(Box::new(handler)); } } @@ -654,7 +657,10 @@ impl BleIo for MockBleIo { } async fn connect(&self, addr: &BleAddr, psm: u16) -> Result { - let handler = self.connect_handler.lock().unwrap(); + let handler = self + .connect_handler + .lock() + .unwrap_or_else(|e| e.into_inner()); match handler.as_ref() { Some(f) => f(addr, psm), None => Err(TransportError::ConnectionRefused), diff --git a/src/transport/ethernet/discovery.rs b/src/transport/ethernet/discovery.rs index 7130b53..26ff75e 100644 --- a/src/transport/ethernet/discovery.rs +++ b/src/transport/ethernet/discovery.rs @@ -65,7 +65,7 @@ impl DiscoveryBuffer { pub fn add_peer(&self, src_mac: [u8; 6], pubkey: XOnlyPublicKey) { let addr = TransportAddr::from_bytes(&src_mac); let peer = DiscoveredPeer::with_hint(self.transport_id, addr, pubkey); - let mut peers = self.peers.lock().unwrap(); + let mut peers = self.peers.lock().unwrap_or_else(|e| e.into_inner()); // Deduplicate by MAC address — keep the latest peers.retain(|p| p.addr.as_bytes() != src_mac); peers.push(peer); @@ -73,7 +73,7 @@ impl DiscoveryBuffer { /// Drain all discovered peers since the last call. pub fn take(&self) -> Vec { - let mut peers = self.peers.lock().unwrap(); + let mut peers = self.peers.lock().unwrap_or_else(|e| e.into_inner()); std::mem::take(&mut *peers) } } diff --git a/src/transport/ethernet/socket_macos.rs b/src/transport/ethernet/socket_macos.rs index 4f24fdc..6adb70f 100644 --- a/src/transport/ethernet/socket_macos.rs +++ b/src/transport/ethernet/socket_macos.rs @@ -214,7 +214,7 @@ impl PacketSocket { /// parses the next frame from the internal buffer, stripping the /// Ethernet header. Returns `(payload_bytes, source_mac)`. pub fn recv_from(&self, buf: &mut [u8]) -> std::io::Result<(usize, [u8; 6])> { - let mut state = self.read_state.lock().unwrap(); + let mut state = self.read_state.lock().unwrap_or_else(|e| e.into_inner()); let state = &mut *state; loop { // Try to parse the next frame from the read buffer diff --git a/src/transport/udp/mod.rs b/src/transport/udp/mod.rs index 2530def..d075de0 100644 --- a/src/transport/udp/mod.rs +++ b/src/transport/udp/mod.rs @@ -103,7 +103,7 @@ impl UdpTransport { // Check cache { - let cache = self.dns_cache.lock().unwrap(); + let cache = self.dns_cache.lock().unwrap_or_else(|e| e.into_inner()); if let Some((resolved, cached_at)) = cache.get(addr) && cached_at.elapsed() < DNS_CACHE_TTL { @@ -116,7 +116,7 @@ impl UdpTransport { // Store in cache { - let mut cache = self.dns_cache.lock().unwrap(); + let mut cache = self.dns_cache.lock().unwrap_or_else(|e| e.into_inner()); cache.insert(addr.clone(), (resolved, Instant::now())); } @@ -189,14 +189,14 @@ impl UdpTransport { if let Some(ref name) = self.name { info!( name = %name, - local_addr = %self.local_addr.unwrap(), + local_addr = %self.local_addr.map_or_else(|| "".to_string(), |a| a.to_string()), recv_buf = actual_recv, send_buf = actual_send, "UDP transport started" ); } else { info!( - local_addr = %self.local_addr.unwrap(), + local_addr = %self.local_addr.map_or_else(|| "".to_string(), |a| a.to_string()), recv_buf = actual_recv, send_buf = actual_send, "UDP transport started" @@ -248,14 +248,14 @@ impl UdpTransport { if let Some(ref name) = self.name { info!( name = %name, - local_addr = %self.local_addr.unwrap(), + local_addr = %self.local_addr.map_or_else(|| "".to_string(), |a| a.to_string()), recv_buf = actual_recv, send_buf = actual_send, "UDP transport adopted existing socket" ); } else { info!( - local_addr = %self.local_addr.unwrap(), + local_addr = %self.local_addr.map_or_else(|| "".to_string(), |a| a.to_string()), recv_buf = actual_recv, send_buf = actual_send, "UDP transport adopted existing socket"