build-tarball.sh only applied --mtime when SOURCE_DATE_EPOCH was
explicitly set, and did not sort tar entries. This produced different
tarballs across builds due to varying timestamps and filesystem
ordering. Default SOURCE_DATE_EPOCH to the git commit timestamp and
add --sort=name for deterministic output.
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.
Add a complete example running a strfry Nostr relay exclusively over
the FIPS mesh using the sidecar pattern. Includes Docker Compose
stack, network isolation via iptables, .fips DNS resolution, nak CLI,
build script with cross-compilation support, and documentation.
Also fix the package-openwrt.yml workflow path to match the
openwrt → openwrt-ipk directory rename.
Rekey dual-initiation race (FMP + FSP):
When both sides' rekey timers fire simultaneously on high-latency links
(Tor ~700ms RTT), both msg1s cross in flight before dampening can
suppress the second initiation. Each node acts as both initiator and
responder, with set_pending_session() from the responder path destroying
the initiator's in-progress state. Each side ends up with a
pending_new_session from a different Noise handshake, causing AEAD
verification failure after cutover and link death. Fix: deterministic
tie-breaker in both FMP msg1 handler and FSP SessionSetup handler
(smaller NodeAddr wins as initiator). Also guard against pending session
overwrite from retransmitted msg1s.
Parent selection SRTT gate:
Peers without MMP RTT data are excluded from parent candidacy via
has_srtt() filter, preventing freshly reconnected peers from appearing
artificially attractive. First-RTT triggers immediate parent re-eval.
The gate in evaluate_parent() skips unmeasured candidates when any peer
has cost data, but falls back to default cost 1.0 during cold start
when no peer has MMP data yet.
Auto-reconnect on all peer removal paths:
The excessive-decryption-failure and peer-restart epoch-mismatch removal
paths were missing schedule_reconnect() calls, causing auto-connect peers
to be permanently abandoned after those events.
Control socket accessibility:
Socket and parent directory chowned to root:fips with mode 0770/0750 so
group members can use fipsctl/fipstop without root.
Log level changes:
Default RUST_LOG changed to debug in systemd service files. "Unknown
session index" log reduced from debug to trace.
Add TorTransport in src/transport/tor/ supporting three operating modes:
Outbound (socks5 mode):
- Non-blocking SOCKS5 connect via tokio-socks with per-destination
circuit isolation (IsolateSOCKSAuth)
- TorAddr enum for .onion and clearnet address types
- Connection pool with per-connection receive tasks, reuses TCP
stream FMP framing
- connect_async()/connection_state_sync()/promote_connection() follow
the same non-blocking polling pattern as TCP transport
Inbound (directory mode — recommended for production):
- Tor manages the onion service via HiddenServiceDir in torrc
- FIPS reads .onion address from hostname file at startup
- No control port needed — enables Tor Sandbox 1 (seccomp-bpf)
- Accept loop mirrors TCP pattern with DirectoryServiceConfig
Monitoring (control_port mode and optional in directory mode):
- Async control port client supporting TCP and Unix socket connections
via Box<dyn AsyncRead/Write> trait objects
- AUTHENTICATE with cookie or password auth
- 8 GETINFO queries: bootstrap, circuits, traffic, liveness, version,
dormant state, SOCKS listeners
- Background monitoring task polls every 10s, caches TorMonitoringInfo
in Arc<RwLock> for synchronous query access
- Bootstrap milestone logging (25/50/75/100%), stall warning (>60s),
network liveness transitions, dormant mode entry
- Directory mode optionally connects to control port when control_addr
is configured (non-fatal on failure)
Operator visibility:
- show_transports query exposes tor_mode, onion_address, tor_monitoring
(bootstrap, circuit_established, traffic, liveness, version, dormant)
- fipstop transport detail view: Tor mode, onion address, SOCKS5/control
errors, connection stats, Tor daemon status section
- fipstop table view: tor(mode) label with truncated onion address hint
Security hardening:
- Per-destination circuit isolation via IsolateSOCKSAuth
- Unix socket default for control port (/run/tor/control)
- Reference torrc with HiddenServiceDir, VanguardsLiteEnabled,
ConnectionPadding, DoS protections (PoW + intro rate limiting)
Config:
- TorConfig with socks5, control_port, and directory modes
- DirectoryServiceConfig: hostname_file, bind_addr
- control_addr, control_auth, cookie_path, connect_timeout,
max_inbound_connections
Testing:
- 69 unit + integration tests with mock SOCKS5 and control servers
- Docker tests: socks5-outbound (clearnet via Tor) and directory-mode
(HiddenServiceDir onion service)
Documentation:
- Transport layer design doc: Tor architecture, directory mode
- Configuration doc: Tor config tables and examples
Add resolve_socket_addr() with IP fast path and tokio::net::lookup_host()
fallback for DNS hostnames. Peer addresses can now use hostnames like
"peer1.example.com:2121" alongside IP addresses.
UDP transport adds a per-transport DNS cache (60s TTL) to avoid
per-packet resolution. TCP resolves at connect time (one-shot).
Update design docs, config examples, and changelog to reflect hostname
support in transport addressing.
Update all fips-network/fips references to jmcorgan/fips across
Cargo.toml, README, CONTRIBUTING, packaging, and config files.
Merge CHANGELOG Unreleased and 0.1.0-alpha sections into a single
0.1.0 release entry. Update README roadmap.
Adds packaging/openwrt/ with everything needed to produce and distribute
FIPS .ipk packages for OpenWrt routers:
- Makefile: OpenWrt feed package definition using rust/host toolchain,
maps OpenWrt ARCH to Rust musl target triples
- build-ipk.sh: local build script using cargo-zigbuild + direct tar
assembly — no OpenWrt SDK or Docker required. Handles macOS BSD tar
(ustar format, resource fork suppression) and portable ar header
generation for cross-platform .ipk creation. Accepts PKG_VERSION
env var override for CI use.
- files/: procd init script, default fips.yaml (persistent identity,
br-lan and wwan interface examples), firewall helper, dnsmasq .fips
forwarding, br_netfilter sysctl, hotplug for fips0, UCI defaults for
first-boot setup, sysupgrade keeplist
- .github/workflows/package-openwrt.yml: CI workflow (ubuntu-latest +
cargo-zigbuild) building aarch64 and x86_64 .ipk packages using
build-ipk.sh; llvm-strip for cross-compiled binaries; triggers on
every push; publishes to GitHub Releases on version tags. MIPS
targets disabled pending portable-atomic crate (32-bit MIPS lacks
native AtomicU64), with nightly/rust-src toolchain steps prepared
for re-enablement.
- .gitignore: add reference/, dist/, *.ipk
The fips-dns.service unit had three issues:
1. Wants=systemd-resolved.service caused systemd to start systemd-resolved
on systems that weren't using it, breaking existing DNS by rewriting
/etc/resolv.conf to the stub resolver at 127.0.0.53.
2. The ExecStart busy-wait loop for fips0 had no timeout, hanging forever
if fips.service failed to create the TUN device.
3. Installers unconditionally enabled fips-dns.service regardless of whether
systemd-resolved was present.
Fix by replacing Wants= with ConditionPathExists=/run/systemd/resolve (skips
cleanly if resolved isn't running), adding Requires=fips.service (won't start
without the daemon), bounding the fips0 wait loop to 30 seconds, and making
the installers conditional on systemd-resolved being active.
Add a HostMap that resolves human-readable hostnames to npubs,
enabling `gateway.fips` instead of the full `npub1...xyz.fips`.
Two sources populate the map: peer `alias` fields from the YAML
config and an operator-maintained hosts file at /etc/fips/hosts.
The DNS responder auto-reloads the hosts file on each request by
checking the file modification time, so operators can update
mappings without restarting the daemon.
- New src/upper/hosts.rs: HostMap, HostMapReloader, hostname
validation, hosts file parser with auto-reload on mtime change
- DNS resolver checks host map before falling back to direct npub
- Node uses host map for peer display names
- Default hosts file added to both .deb and tarball packaging
- 26 new tests (789 total)
Debian packaging via cargo-deb:
- Add [package.metadata.deb] to Cargo.toml with assets, dependencies,
and conffiles declarations
- Maintainer scripts: postinst (create fips group, enable services,
restart on upgrade), prerm (stop/disable on remove, stop-only on
upgrade), postrm (purge config, keys, group)
- Systemd units (fips.service, fips-dns.service) with /usr/bin/ paths
for .deb installs
- tmpfiles.d entry for /run/fips/ runtime directory
- build-deb.sh wrapper script for building packages
README rewrite:
- Replace run-from-source Quick Start with Building section
- Add Installation options: Debian .deb (cargo-deb) and generic Linux
(systemd tarball)
- Add full Configuration reference with default fips.yaml
- Add Usage sections: DNS resolution, monitoring (fipsctl + fipstop),
service management, and testing
- Update project structure and features list
Packaging directory structure:
- packaging/common/ — shared config (fips.yaml) used by all formats
- packaging/systemd/ — systemd-specific installer and service units
Systemd packaging includes:
- build-tarball.sh: builds release binaries and creates a self-contained
install tarball with stripped binaries
- fips.service: systemd unit running the daemon with security hardening
- fips-dns.service: oneshot unit configuring resolvectl to route .fips
domain queries to the FIPS DNS shim on 127.0.0.1:5354
- install.sh: deploys binaries to /usr/local/bin, installs systemd units,
creates fips group for non-root control socket access
- uninstall.sh: removes service and binaries, optional --purge for
config and identity key files
- README.install.md: installation and configuration guide
Default config enables UDP (2121), TCP inbound (8443), TUN, and DNS
resolver. Identity is ephemeral by default for privacy; operators can
uncomment persistent: true to maintain a stable npub for static peer
publishing. Ethernet transport is commented out for per-node setup.