Gate platform-specific code behind cfg attributes and add full Windows
support: TUN device via wintun, TCP control socket on localhost:21210,
Windows Service lifecycle (--install-service/--uninstall-service/--service),
CI build and test matrix, and packaging with ZIP builder and PowerShell
service management scripts.
Key changes:
- Cargo.toml: move tun/libc/rtnetlink behind cfg(unix); add wintun and
windows-service dependencies for Windows
- upper/tun.rs: wintun-based TUN implementation with netsh configuration
for IPv6 address, MTU, and fd00::/8 routing
- control/mod.rs: split into unix_impl/windows_impl; Windows uses TCP on
localhost:21210 with shared connection handler
- bin/fips.rs: refactor main() into run_daemon() accepting a shutdown
signal; add Windows Service support via windows-service crate
- transport/udp/socket.rs: platform-gated modules; Windows uses
tokio::net::UdpSocket (kernel drop count unavailable, returns 0)
- transport/ethernet: gate to cfg(unix); add Windows stub types
- config: platform-conditional default paths (socket, hosts) for Windows
- CI: add windows-latest to build matrix and test-windows job with
cargo-nextest
- packaging/windows: build-zip.ps1, install-service.ps1,
uninstall-service.ps1, and package-windows.yml workflow
- README/docs: Windows build instructions, service management, and
control socket platform differences
Linux and macOS behavior is unchanged.
macOS platform:
- Platform-native TUN interface management with shutdown pipe
- Raw Ethernet transport with macOS socket backend (socket_macos.rs)
- EthernetTransport and TransportHandle::Ethernet ungated from Linux-only
- macOS .pkg packaging (build-pkg.sh, launchd plist, uninstall script)
- CI: macOS build and unit test jobs; x86_64 cross-compiled from
macos-latest via rustup target add x86_64-apple-darwin
Gateway feature flag:
- New opt-in `gateway` Cargo feature activates optional `rustables` dep
- `pub mod gateway` and `Config.gateway` gated behind the feature so
macOS builds never pull in Linux-only nftables bindings
- `fips-gateway` bin has `required-features = ["gateway"]`
- All Linux/OpenWrt/AUR packaging passes `--features gateway`
CI / packaging:
- package-linux, package-macos, package-openwrt now trigger on push to
master/maint/next and on pull requests; release uploads remain tag-gated
- Bloom filter routing fix: fall through to tree routing when no candidate
is strictly closer
- MMP intervals: raise MIN to 1s / MAX to 5s with 5-sample cold-start phase
Add fips-gateway binary to CI artifact and Docker build. Systemd
service unit with After=fips.service dependency and security
hardening. Debian and AUR package entries.
OpenWrt packaging: procd init script managing dnsmasq forwarding,
proxy NDP, RA route advertisements for the virtual IP pool, and a
global IPv6 prefix on br-lan to work around Android suppressing AAAA
queries on ULA-only networks. Sysctl config for IPv6 forwarding.
Gateway enabled by default in OpenWrt config. Ethernet transport
enabled by default.
Default gateway config section (commented out) in common fips.yaml.
BLE transport implementation using L2CAP Connection-Oriented Channels
(SeqPacket mode) via the bluer crate, behind cfg(feature = "ble").
Core transport:
- BleTransport<I> generic over BleIo trait (BluerIo prod, MockBleIo test)
- Connection pool with priority eviction (static > discovered, max 7)
- Connect-on-send via connect_inline() matching TCP behavior
- Per-connection receive loops with pool cleanup on disconnect
Discovery and probing:
- Combined scan_probe_loop using select! over scanner events and a
BinaryHeap delay queue with per-entry random jitter (0-5s) to prevent
herd effects when multiple nodes see the same beacon simultaneously
- Pre-handshake pubkey exchange ([0x00][pubkey:32]) for IK identity
- Cross-probe tie-breaker: smaller NodeAddr's outbound wins (same
convention as FMP/FSP rekey dual-initiation)
- Probed peers reported to DiscoveryBuffer; pool fills through normal
node-layer auto-connect -> send_async -> connect_inline path
Beacon management:
- Periodic advertising: 1s burst every 30s (configurable via
beacon_interval_secs / beacon_duration_secs)
- FIPS service UUID for scan filtering
Configuration (all fields optional with defaults):
- adapter, psm, mtu, max_connections, connect_timeout_ms
- advertise, scan, auto_connect, accept_connections
- beacon_interval_secs (30), beacon_duration_secs (1)
Hardware validated with two BLE nodes:
- 2048-byte MTU, ~60-160ms RTT, zero-config auto-connect
- BLE spike tool at testing/ble/ for standalone adapter validation
42 unit tests + 4 node-level integration tests, all CI-compatible
via MockBleIo (no hardware required). tokio test-util added for
time-dependent scan/probe tests.
Pin Rust toolchain to 1.94.0 via rust-toolchain.toml for deterministic
compiler output across environments. Set SOURCE_DATE_EPOCH from git
commit timestamp in CI workflows and packaging scripts to normalize
embedded timestamps.
Make packaging archives reproducible: pass --mtime=@$SOURCE_DATE_EPOCH
to tar in .deb, .ipk, and systemd tarball builds. Normalize ownership
to root:root in systemd tarballs. Pin cargo-zigbuild to 0.19.8 for
cross-compilation stability.
Add SHA-256 hash output to CI build and OpenWrt packaging workflows
for binary verification.
Implement periodic full rekey at both protocol layers using fresh DH
key exchanges. Uses the existing K-bit flag (FLAG_KEY_EPOCH /
FSP_FLAG_K) to coordinate cutover between peers.
FMP layer (IK pattern):
- ActivePeer gains rekey state: pending/previous sessions, K-bit epoch
tracking, drain window, dampening timer
- Handshake state stored on ActivePeer with msg1 sent on existing link
- Encrypted frame handler detects K-bit flips, promotes pending
sessions, falls back to previous session during drain
- Handshake handlers distinguish rekey from new connections using
addr_to_link lookup with identity-based fallback
- Free all session indices (current, rekey, pending, previous) on
peer removal
FSP layer (XK pattern):
- SessionEntry gains parallel rekey fields with XK-specific state
for the 3-message handshake
- Route availability check before FSP rekey initiation
- Encrypted session handler adds K-bit flip detection and dual-session
decrypt fallback
- SessionSetup/Ack/Msg3 handlers extended for rekey paths
Defense-in-depth:
- Consecutive decryption failure detector (threshold=20) triggers
forced peer removal instead of waiting for link-dead timeout
- Identity-based rekey detection as fallback when addr_to_link
doesn't match (e.g., TCP ephemeral ports)
Configuration: RekeyConfig with enabled flag, after_secs (default 120),
and after_messages (default 65536) thresholds.
Logging: info for successful K-bit cutover completions, warn for
failures, debug for intermediate handshake steps, trace for routine
operations (resends, drain cleanup).
Rekey lifecycle:
1. Timer/counter fires -> initiator starts new handshake
2. Old session continues handling traffic during handshake
3. Handshake completes -> initiator cuts over, flips K-bit
4. Responder sees flipped K-bit -> promotes new session
5. Both keep old session for 10s drain window
6. After drain, old session discarded
Integration test: Docker-based multi-phase test exercising both FMP
and FSP rekey with aggressive timers (35s). Verifies connectivity
across all 20 directed pairs survives two consecutive rekey cycles.
Includes rekey topology, docker-compose profile, and CI matrix entry.
Increase ping test convergence wait from 3s to 5s for CI reliability.
Tailscale-style sidecar pattern: a FIPS container provides mesh
networking, and a companion app container shares its network namespace
via network_mode: service:fips.
Security model:
- iptables enforces strict isolation — the app container can only
communicate over the FIPS mesh (fd::/8 via fips0)
- No IPv4 access: eth0 restricted to FIPS UDP transport (port 2121)
- No IPv6 on eth0: ip6tables blocks all eth0 IPv6 traffic
- Only fips0 and loopback are reachable from the app container
The sidecar accepts peer configuration via environment variables
(FIPS_NSEC, FIPS_PEER_NPUB, FIPS_PEER_ADDR), so it can be pointed
at any FIPS node without config file generation.
Files:
- testing/sidecar/: Dockerfile, Dockerfile.app, docker-compose.yml,
entrypoint.sh, .env, resolv.conf, scripts/build.sh
- testing/sidecar/README.md: security model, quick-start, architecture,
DNS resolution, troubleshooting, production considerations
- testing/sidecar/scripts/test-sidecar.sh: 3-node chain integration
test verifying link establishment, multi-hop connectivity, and
network isolation on each app container
- .github/workflows/ci.yml: sidecar integration test matrix entry
Three-job pipeline:
- build: matrix over ubuntu/macos/windows, native cargo build per runner;
macOS and Windows are continue-on-error (best-effort). Uploads Linux
binary as artifact for downstream jobs.
- test: cargo test --all on ubuntu, gated on build succeeding.
- integration: parallel matrix of static-mesh, static-chain, and
chaos-smoke-10; runs only when build + test both pass. Static jobs
use docker compose + ping-test.sh; chaos job runs the stochastic
simulation via chaos.sh.