- fips-architecture.md → fips-software-architecture.md with all cross-references updated (4 files) - fips-transport-abstraction.svg → fips-node-architecture.svg, moved from Transport Abstraction section to Architecture Overview in fips-intro.md - Added descriptive paragraph for node architecture diagram covering three-layer design (application interfaces, router core, transports)
38 KiB
FIPS Software Architecture
Status: Design Draft
This document describes the software architecture for a FIPS node implementation, covering core entities, state machines, transport abstractions, and configuration.
Overview
FIPS is a Layer 3 mesh routing protocol that provides IPv6 connectivity over heterogeneous link types. A FIPS node exposes a TUN interface to local applications, routes packets via a spanning tree topology, and uses Bloom filters for efficient reachability lookup.
The architecture is event-driven with multiple focused state machines rather than a single monolithic event handler. Control protocols are designed for eventual consistency, tolerating packet loss without requiring acknowledgment/retry machinery.
Core Entities
Node
The top-level entity representing a running FIPS instance.
Node
├── identity: Identity // cryptographic identity (npub/nsec)
├── config: Config // loaded configuration
├── tun: TunInterface // IPv6 interface to local applications
├── tree_state: TreeState // local view of spanning tree
├── coord_cache: CoordCache // address → coordinates for routing
├── transports: HashMap<TransportId, Transport>
├── links: HashMap<LinkId, Link>
└── peers: HashMap<NodeAddr, Peer>
Identity
Cryptographic identity using Nostr keys (secp256k1).
Identity
├── npub: PublicKey // public key (bech32: npub1...)
├── nsec: SecretKey // secret key (bech32: nsec1...)
├── node_addr: NodeAddr // SHA-256(pubkey) truncated, 16 bytes
└── address: FipsAddress // IPv6 ULA derived from node_addr (fd::/8)
NodeAddr is the routing identifier, derived deterministically from npub.
Transport addresses and FIPS identity are fully decoupled.
Protocol Layer Visibility
| Observer | Link Addr | Node Addr | FIPS Addr (pubkey) | Payload |
|---|---|---|---|---|
| Transport (IP router, switch) | Yes | No | No | No |
| FIPS routing node | Last hop only | Yes (header) | No | No |
| Destination endpoint | Yes | Yes | Yes | Yes |
Key insight: Three independent encryption layers ensure:
- Passive transport observers see only encrypted blobs
- FIPS routing nodes see node_addrs but not pubkeys (FIPS addresses) or payload
- Only endpoints know each other's FIPS addresses (pubkeys) and can decrypt payload
Transport
A physical or logical interface over which links can be established. Transports are statically configured and exist for the lifetime of the node after startup.
Transport (trait)
├── transport_id: TransportId
├── transport_type: TransportType
├── config: TransportConfig
├── state: TransportState
├── mtu: u16
│
├── start() -> Result<()>
├── stop() -> Result<()>
├── send(addr: &TransportAddr, data: &[u8]) -> Result<()>
├── recv() -> Result<(TransportAddr, Vec<u8>)>
└── discover() -> Result<Vec<DiscoveredPeer>>
Transport metadata (static per type):
TransportType
├── name: &'static str // "udp", "ethernet", "wifi", "tor"
├── connection_oriented: bool // requires link establishment?
└── reliable: bool // delivery guaranteed by transport?
Transports handle framing, fragmentation, and any transport-layer encryption internally. The FIPS routing layer sees only FIPS packets.
Link
A communication channel to a specific remote endpoint over a transport. Links are created on demand when connecting to a peer and torn down when the peer connection terminates. Link lifecycle is driven by the peer lifecycle.
Link
├── link_id: LinkId
├── transport_id: TransportId
├── remote_addr: TransportAddr // opaque, transport-specific
├── direction: Inbound | Outbound
├── state: LinkState // trivial for connectionless, real for Tor
├── base_rtt: Duration // hint from transport type
└── io: ... // connection handles for connection-oriented
For connectionless transports (UDP, Ethernet, WiFi), links are lightweight—just
(transport_id, remote_addr) with implicit "established" state (no connection
setup required).
For connection-oriented transports (Tor), links track real connection state and hold I/O handles. The link must complete transport-layer connection setup before FIPS session establishment can proceed.
Link statistics (measured):
LinkStats
├── packets_sent: u64
├── packets_recv: u64
├── bytes_sent: u64
├── bytes_recv: u64
├── last_recv: Timestamp
├── rtt_estimate: Duration // measured from probes
├── loss_rate: f32 // observed (meaningful for unreliable)
└── throughput_estimate: u64 // bytes/sec observed
Peer
An authenticated remote FIPS node, reachable via a link.
Peer
├── node_addr: NodeAddr // routing identity
├── npub: PublicKey // cryptographic identity
├── link_id: LinkId // which link reaches this peer
├── state: PeerState // lifecycle state
│
│ // Spanning tree
├── declaration: ParentDeclaration // their latest
├── ancestry: Vec<NodeAddr> // their path to root
│
│ // Bloom filter (inbound—what's reachable through them)
├── inbound_filter: BloomFilter
├── filter_sequence: u64
├── filter_received_at: Timestamp
├── pending_filter_update: bool // we owe them an update
│
│ // Statistics
└── link_stats: LinkStats
Peer/Link Lifecycle:
Links and peers have a one-to-one mapping with coupled lifecycles:
-
Outbound connection: Desire to connect to a peer triggers link creation over the appropriate transport. For connection-oriented transports (Tor), the link goes through connection setup; for connectionless (UDP), it immediately becomes established. Once the link is ready, FIPS authentication proceeds.
-
Inbound connection: Incoming data on a transport creates a link, then authentication creates the peer.
-
Peer references link: An authenticated peer always references exactly one active link.
-
Termination: When a peer connection terminates, the associated link is torn down. For connectionless transports this is trivial cleanup; for connection-oriented transports this closes the underlying connection.
If the same remote node is reachable via multiple transports, that would be multiple Peer entries (though for initial implementation, we assume single transport per peer).
Spanning Tree State
Per-Node State
TreeState
├── my_declaration: ParentDeclaration
│ ├── node_addr: NodeAddr
│ ├── parent_id: NodeAddr // self if root candidate
│ ├── sequence: u64 // monotonic
│ └── signature: Signature
├── my_coords: Vec<NodeAddr> // [self, parent, grandparent, ..., root]
└── root: NodeAddr // elected root (smallest reachable node_addr)
Per-Peer State
From each peer, we receive and store:
- Their
ParentDeclaration - Their
ancestry(path from peer to root)
This provides their tree coordinates for routing decisions.
Bounded State
Each node's TreeState contains O(P × D) entries, not O(N):
- P = direct peer count
- D = tree depth
A node knows only:
- Its own parent declaration
- Direct peers' parent declarations
- Ancestry chains from each peer to root
Nodes do NOT know about other subtrees—only paths toward root.
Bloom Filter State
Per-Node State
BloomState
├── own_node_addr: NodeAddr // always included in outgoing filters
├── leaf_dependents: HashSet<NodeAddr> // leaf-only nodes we speak for
├── is_leaf_only: bool // if true, no filter processing
└── update_debounce: Duration // rate limit outgoing updates
Per-Peer State
Stored on Peer:
inbound_filter: what they advertise to us (1KB Bloom filter)filter_sequence: freshness/dedupfilter_received_at: for staleness detection
Computed (On-Demand)
Outgoing filter to peer Q is computed, not stored:
outbound_filter(Q) =
own_node_addr
∪ leaf_dependents
∪ { entries from peer[P].inbound_filter for all P ≠ Q }
Recomputation is cheap (1KB filter, 5 hashes) so on-demand is preferred over cache invalidation complexity.
State Machines
Transport Lifecycle
Configured ──► Starting ──► Up ──► Down
│ │ │
v v │
Failed ◄────────────┘
Configured: in config, not startedStarting: initialization in progress (instant for UDP, slow for Tor)Up: ready for linksDown: was up, now unavailableFailed: couldn't start
Events:
Start(from config policy or API)Started/StartFailedShutdownTransportError
Cascading: Transport down → all links over it disconnect → all peers on those links disconnect.
Link Lifecycle
Connectionless transports (UDP, Ethernet, WiFi):
Links are always implicitly "active"—no state machine needed. Link exists when
we have (transport_id, remote_addr).
Connection-oriented transports (Tor):
Outbound:
(connect requested) ──► Connecting ──► Connected ──► Disconnected
│ ▲
v │
Failed ─────────────────────────┘
Inbound:
(transport accepts) ──► Connected ──► Disconnected
Connecting: establishing connection (circuit for Tor)Connected: ready for FIPS trafficDisconnected: was connected, now goneFailed: connection attempt failed
Peer Lifecycle
The peer lifecycle uses Noise IK for authentication. Noise IK is a 2-message handshake where the initiator knows the responder's static key. See fips-wire-protocol.md §2 for wire format details.
┌─────────────────────────────────────────┐
│ Disconnected │
└─────────────────────────────────────────┘
│ │
[outbound] │ │ [inbound msg1]
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Connecting │ │ ReceivedMsg1 │
│(conn-oriented) │ │ (send msg2) │
└──────────────────┘ └──────────────────┘
│ │
[link ready] │ │ [recv encrypted]
[send msg1] │ │ [verify]
▼ │
┌──────────────────┐ │
│ AwaitingMsg2 │ │
│ (sent msg1) │ │
└──────────────────┘ │
│ │
[recv msg2] │ │
[verify] │ │
▼ ▼
┌─────────────────────────────────────────┐
│ Active │
│ (tree gossip, filter exchange) │
└─────────────────────────────────────────┘
│
│ [link down / timeout]
▼
┌─────────────────────────────────────────┐
│ Disconnected │
│ (retry if static peer) │
└─────────────────────────────────────────┘
State descriptions:
Disconnected: No active connection; for static peers, retry with backoffConnecting: Link establishment in progress (connection-oriented transports only)ReceivedMsg1: Inbound; received Noise IK msg1, sent msg2, awaiting first encrypted frameAwaitingMsg2: Outbound; sent Noise IK msg1, waiting for msg2Active: Authenticated; participating in tree gossip and filter exchange
Crossing connection handling:
When in AwaitingMsg2 and we receive a msg1 from the same peer (both sides
initiated simultaneously):
- If local npub < remote npub: Ignore incoming msg1, remain initiator
- If local npub > remote npub: Switch to responder role, send msg2,
transition to
ReceivedMsg1
Events:
PeerEvent
├── Discovered { link_id, transport_addr, hint: Option<PublicKey> }
├── LinkConnected
├── LinkFailed { reason }
├── Msg1Received { noise_payload }
├── Msg2Received { noise_payload }
├── HandshakeComplete { npub, node_addr }
├── HandshakeFailed { reason }
├── TreeAnnounceReceived { declaration, ancestry }
├── FilterAnnounceReceived { filter, sequence, ttl }
├── Timeout { kind: TimeoutKind }
├── PacketReceived { ... }
└── LinkDisconnected { reason }
Reference Transport Types
UDP/IP
UdpTransport
├── bind_addr: SocketAddr // e.g., 0.0.0.0:4000
├── socket: UdpSocket
└── state: TransportState
TransportAddr = SocketAddr // IP:port
| Property | Value |
|---|---|
| Connection-oriented | No |
| Reliable | No |
| MTU | 1280-1472 |
| Latency | Low (1-500ms) |
| Scope | Internet |
| Discovery | DNS-SD, Nostr, static config |
| Privileges | None |
| NAT | Hole punching possible |
Ethernet
EthernetTransport
├── interface: String // "eth0"
├── socket: RawSocket // AF_PACKET
├── local_mac: MacAddr
├── ethertype: u16 // FIPS ethertype
└── state: TransportState
TransportAddr = MacAddr // 6 bytes
| Property | Value |
|---|---|
| Connection-oriented | No |
| Reliable | No |
| MTU | 1500 |
| Latency | <1ms |
| Scope | Local segment |
| Discovery | Multicast |
| Privileges | CAP_NET_RAW |
WiFi
WifiTransport
├── interface: String // "wlan0"
├── socket: RawSocket
├── local_mac: MacAddr
├── mode: Infrastructure | AdHoc | Direct
└── state: TransportState
TransportAddr = MacAddr // same as Ethernet
| Property | Value |
|---|---|
| Connection-oriented | No |
| Reliable | No |
| MTU | 1500 |
| Latency | 1-10ms |
| Scope | Local segment (or Direct group) |
| Discovery | Multicast, P2P service discovery |
| Privileges | CAP_NET_RAW |
Infrastructure and Ad-hoc modes behave like Ethernet. WiFi Direct has its own service discovery mechanism.
Tor Onion
TorTransport
├── tor_client: TorClient // arti or external daemon
├── onion_service: Option<OnionService>
├── local_onion_addr: Option<OnionAddr>
└── state: TransportState
TransportAddr = OnionAddr // "abc...xyz.onion:port"
| Property | Value |
|---|---|
| Connection-oriented | Yes |
| Reliable | Yes (stream) |
| MTU | Stream (framed) |
| Latency | 500ms-5s |
| Scope | Internet (anonymous) |
| Discovery | Nostr, static config |
| Privileges | None |
| Transport startup | Slow (30s-2min for Tor bootstrap) |
Tor links require framing (length-prefix) over the stream. The .onion address is independent of FIPS npub—identity verified via FIPS auth after connecting.
Transport Comparison
| Aspect | UDP | Ethernet | WiFi | Tor |
|---|---|---|---|---|
| Connection | No | No | No | Yes |
| Link state machine | Trivial | Trivial | Trivial | Real |
| Address type | IP:port | MAC | MAC | .onion:port |
| Startup time | Instant | Instant | Instant | 30s-2min |
| Base RTT hint | 50ms | 1ms | 5ms | 2s |
| Framing | Datagram | Datagram | Datagram | Length-prefix |
Event-Driven Architecture
The system uses multiple focused state machines rather than one giant event handler:
- Transport state machines — one per transport instance
- Link state machines — one per link (meaningful for connection-oriented)
- Peer state machines — one per peer
- Spanning tree module — reacts to peer events, emits announcements
- Bloom filter module — reacts to peer/filter events, emits updates
Event flow:
Transport
│
├──► DiscoveredPeer { transport_id, addr, hint }
├──► InboundConnection { transport_id, addr, io } (connection-oriented)
└──► PacketReceived { transport_id, addr, data }
│
v
Link
│
├──► LinkConnected { link_id }
├──► LinkDisconnected { link_id, reason }
└──► FipsPacketReceived { link_id, packet }
│
v
Peer
│
├──► AuthSuccess { peer_id }
├──► TreeAnnounceReceived { peer_id, decl, ancestry }
└──► FilterAnnounceReceived { peer_id, filter, seq, ttl }
│
v
TreeState / BloomState
Timers drive keepalives, timeouts, and periodic refresh (debounced announcements).
Protocol Self-Healing Design
Control protocols tolerate packet loss without ack/retry machinery:
TreeAnnounce
- Monotonic sequence numbers (receiver keeps highest, ignores stale/dup)
- Full state (declaration + ancestry), not deltas
- Periodic refresh ensures convergence
- Lost announcement? Next one carries same or newer state
FilterAnnounce
- Full filter replacement with sequence number
- Periodic refresh
- Debounced on rapid changes
- Lost announcement? Peer has stale filter until next update
LookupRequest/LookupResponse
- Request-response pattern
- Lost request/response → sender times out, retries at application level
SessionSetup/SessionAck
- Same as lookup—sender retries on timeout
- Lost setup → first data packet fails → triggers re-establishment
This gossip-style eventual consistency is simpler and avoids the complexity of per-message reliability over unreliable links.
Leaf-Only Operation
Leaf-only mode enables constrained devices (sensors, battery-powered nodes, mobile devices) to participate in FIPS without the overhead of full mesh routing.
Architectural Subset
A leaf-only node uses a minimal subset of the full architecture:
LeafOnlyNode
├── identity: Identity // full (npub, nsec, node_addr, address)
├── config: Config // simplified
├── tun: TunInterface // full (provides IPv6 to local apps)
├── transport: Transport // one
├── link: Link // one
└── upstream_peer: Peer // one (simplified)
What's Required
| Component | Usage |
|---|---|
| Identity | Full—same cryptographic identity model |
| TUN interface | Full—provides IPv6 to local applications |
| Transport | One instance (to reach upstream peer) |
| Link | One instance (to upstream peer) |
| Peer | One instance (upstream), with auth only |
| Peer authentication | Full—must prove identity to upstream |
| Packet send/receive | Full |
What's Not Required
| Component | Reason |
|---|---|
| TreeState | No tree participation; upstream handles routing |
| ParentDeclaration | Doesn't announce position to network |
| Bloom filters | Upstream peer handles reachability |
| Filter computation | N/A |
| CoordCache | Doesn't route for others |
| Discovery protocol | Upstream peer handles lookups |
| Multiple peers | Single upstream by design |
| Session caching | Tunnels everything to upstream |
| Transit routing | Never forwards for others |
Simplified Peer Structure
The upstream peer entry for a leaf-only node:
UpstreamPeer (leaf-only)
├── node_addr: NodeAddr
├── npub: PublicKey
├── link_id: LinkId
├── state: PeerState // auth lifecycle only
└── link_stats: LinkStats // for keepalive/timeout
// NOT present:
// - declaration, ancestry (no tree participation)
// - inbound_filter, filter_* (no Bloom filters)
Routing Behavior
Outbound (local app → network):
TUN → upstream peer (unconditionally)
Inbound (network → local app):
upstream peer → TUN (if dest == self)
upstream peer → DROP (if dest ≠ self, never transit)
The leaf-only node doesn't make routing decisions—it tunnels everything to/from its upstream peer.
Upstream Peer Responsibilities
The upstream peer (a full participant) handles:
- Including leaf-only node in its Bloom filter
- Responding to LookupRequests for the leaf-only node
- Forwarding packets to/from the leaf-only node
- The leaf-only node appears as an entry in the upstream's
leaf_dependentsset
State Machine (Simplified)
Only peer lifecycle matters:
Configured ──► Connecting ──► Authenticating ──► Active ──► Disconnected
│ │ │
v v │
[fail] ─────────────────────────────────────────┘
No TreeAnnounce or FilterAnnounce processing in the Active state.
Configuration (Leaf-Only Subset)
# Required
node.identity.nsec # or auto-generated
node.leaf_only = true
node.tun.device
node.tun.mtu
# One transport
transport.udp.enabled = true # or ethernet, wifi, tor
transport.udp.bind_addr
# One peer (the upstream)
peers[0].npub # required: upstream identity
peers[0].addresses[0].type # transport type
peers[0].addresses[0].addr # how to reach them
peers[0].connect_policy = auto_connect
# Timeouts
peer.auth.timeout
peer.keepalive.interval
peer.keepalive.timeout
peer.reconnect.*
Parameters NOT relevant to leaf-only operation:
tree.* # no tree participation
filter.* # no Bloom filters
discovery.* # upstream handles
session.* # no session caching
transport.*.discovery.* # single configured peer
transport.*.auto_connect # single configured peer
Resource Comparison
| Resource | Full Participant | Leaf-Only |
|---|---|---|
| RAM (Bloom filters) | d × 4KB (d = peers) | 0 |
| RAM (coord cache) | 10K-100K entries | 0 |
| RAM (tree state) | O(P × D) entries | 0 |
| Bandwidth (idle) | < 1 KB/sec | Near zero |
| CPU (filter ops) | Moderate | None |
| Peers | Multiple | One |
Use Cases
- IoT sensors: Send telemetry, receive commands
- Mobile devices: Battery/bandwidth constraints
- Privacy-conscious: Don't see others' traffic
- Monitoring nodes: Observe network, don't route
- Embedded systems: Limited RAM/CPU
Node Startup Sequence
The startup sequence initializes components in dependency order:
1. Load configuration
├── Parse config files (system, user, local)
├── Validate transport and peer configurations
└── Merge with defaults
2. Initialize identity
├── Load nsec from config (or generate if absent)
├── Derive npub, node_addr, and FIPS address
└── Log identity information
3. Initialize transports
├── Create transport instances from config
└── Transports in Configured state
4. Start transports (begin listening)
├── Bind sockets, open interfaces
├── Transports transition to Up state
└── Ready to accept inbound connections
5. Connect to static peers
├── For each configured peer with AutoConnect policy:
│ ├── Create link via appropriate transport
│ ├── Send AuthInit to initiate authentication
│ └── On success: peer joins tree gossip
└── Failed connections enter retry with backoff
6. Node operational
├── Participating in spanning tree (even with 0 peers)
├── Processing inbound connections
└── Retrying unreachable static peers in background
Notes:
- Transports start listening (step 4) before outbound connections (step 5) to accept inbound connections from peers who have us configured
- The node is "operational" as soon as any peer authenticates successfully
- Static peer connection attempts continue in background with retry policy
- With 0 authenticated peers, the node considers itself a potential root
Static Peer Retry Policy
When a static peer is unreachable or authentication fails:
| Parameter | Default | Description |
|---|---|---|
| Initial delay | 1s | First retry delay |
| Max delay | 300s | Cap on exponential backoff |
| Backoff factor | 2.0 | Multiplier per attempt |
| Jitter | ±25% | Randomization to avoid thundering herd |
| Max attempts | unlimited | Static peers retry indefinitely |
The retry timer resets to initial delay after a successful connection that later disconnects.
Inbound Connection Acceptance
For initial implementation, all inbound connections that successfully authenticate are accepted. Future versions may add:
- Peer allowlists/blocklists
- Connection limits per transport
- Rate limiting on authentication attempts
- Reputation-based acceptance
Configuration
Peer Configuration
Peers are configured at the node level, separately from transports. For initial
implementation, only static peers with AutoConnect policy are supported;
discovery-based and on-demand peering are future enhancements.
PeerConfig
├── npub: PublicKey // required: who is this
├── alias: Option<String> // human-readable label
├── addresses: Vec<PeerAddress> // how to reach them
└── connect_policy: ConnectPolicy // AutoConnect for initial impl
PeerAddress
├── transport_type: TransportType // "udp", "ethernet", "tor", etc.
├── addr: String // transport-specific, parsed by driver
└── priority: u8 // preference order (lower = preferred)
ConnectPolicy
├── AutoConnect // connect on startup (initial impl)
├── OnDemand // connect when traffic needs routing (future)
└── Manual // wait for explicit API call (future)
Example configuration (YAML):
node:
peers:
- npub: "npub1abc..."
alias: "gateway"
addresses:
- transport: udp
addr: "192.168.1.1:4000"
priority: 1
- transport: tor
addr: "xyz...abc.onion:4000"
priority: 2
connect_policy: auto_connect
Transport Configuration
TransportConfig
├── transport_type: TransportType
├── driver_config: DriverConfig // type-specific
├── start_policy: StartPolicy
├── discovery_enabled: bool
└── auto_connect: bool // auto-connect to discovered peers
Discovery
Discovery is per-transport:
- Transports emit
DiscoveredPeer { addr, hint }events - Node matches against known peer configs or creates "unknown peer" entries
- Policy (
auto_connect, per-peerconnect_policy) determines action
Configuration Reference (sysctl-style)
Node Identity
| Parameter | Type | Default | Description |
|---|---|---|---|
node.identity.nsec |
string | (generated) | Secret key (nsec1... or hex) |
TUN Interface
| Parameter | Type | Default | Description |
|---|---|---|---|
node.tun.device |
string | "fips0" | TUN device name |
node.tun.mtu |
u16 | 1280 | TUN interface MTU |
Resource Limits
| Parameter | Type | Default | Description |
|---|---|---|---|
limits.max_peers |
u32 | 128 | Maximum concurrent authenticated peers |
limits.max_transports |
u8 | 8 | Maximum configured transports |
limits.max_pending_auth |
u32 | 32 | Maximum connections awaiting authentication |
limits.max_pending_lookups |
u32 | 1000 | Maximum in-flight discovery lookups |
limits.memory_budget |
u64 | 0 | Soft memory limit in bytes (0 = unlimited) |
Limits are enforced at connection time. Exceeding max_pending_auth rejects new
inbound connections; exceeding max_peers prevents new outbound connections.
The memory_budget is advisory—implementations should shed load when approaching
the limit but need not enforce it strictly.
Spanning Tree
| Parameter | Type | Default | Description |
|---|---|---|---|
tree.announce.interval |
duration | 30s | Periodic TreeAnnounce refresh |
tree.announce.on_change |
bool | true | Immediate announce on parent change |
tree.parent.hold_time |
duration | 10s | Min time before switching parent |
tree.parent.hysteresis |
f32 | 0.1 | Cost improvement threshold to switch |
Bloom Filters
| Parameter | Type | Default | Description |
|---|---|---|---|
filter.size_class |
u8 | 1 | Size class: 0=512B, 1=1KB, 2=2KB, 3=4KB |
filter.hash_count |
u8 | 5 | Number of hash functions |
filter.scope |
u8 | 2 | TTL for filter propagation (K) |
filter.refresh.interval |
duration | 60s | Periodic FilterAnnounce refresh |
filter.update.debounce |
duration | 500ms | Min interval between updates |
filter.stale.threshold |
duration | 300s | Consider peer's filter stale |
v1 protocol requires size_class=1 (1 KB filters). The size_class field is
present in the wire format for forward compatibility with larger filters.
Discovery Protocol
| Parameter | Type | Default | Description |
|---|---|---|---|
discovery.lookup.ttl |
u8 | 8 | Initial TTL for LookupRequest |
discovery.lookup.timeout |
duration | 10s | Timeout waiting for response |
discovery.lookup.retry_count |
u8 | 3 | Retries before giving up |
discovery.cache.max_entries |
u32 | 10000 | Route cache size |
discovery.cache.ttl |
duration | 300s | Cached coordinates expiry |
The discovery cache stores coordinates learned from LookupResponses for destinations this node wants to reach. This is the primary cache for endpoint nodes.
Routing Session Management
Terminology note: These parameters configure routing sessions—hop-by-hop cached state at intermediate routers. For crypto session (end-to-end encryption) parameters, see the Crypto Session section below. See fips-session-protocol.md §3 for crypto sessions and §5 for route cache warming.
| Parameter | Type | Default | Description |
|---|---|---|---|
session.setup.timeout |
duration | 5s | SessionSetup ack timeout |
session.cache.max_entries |
u32 | 50000 | Coord cache size (per router) |
session.cache.ttl |
duration | 300s | Cached coordinates expiry |
session.refresh.interval |
duration | 240s | Proactive session refresh |
The session cache stores coordinates learned from SessionSetup packets passing through this node as a transit router. Larger than discovery cache since routers see traffic for many destinations. Both caches are part of Node.coord_cache; these parameters configure the same underlying cache but are grouped by purpose.
Crypto Session Management
Note
: Crypto sessions provide end-to-end authenticated encryption using Noise IK. See fips-session-protocol.md §6 for details.
| Parameter | Type | Default | Description |
|---|---|---|---|
crypto.session.max_entries |
u32 | 10000 | Max concurrent crypto sessions |
crypto.session.idle_timeout |
duration | 3600s | Expire idle sessions |
crypto.session.rekey_interval |
duration | 86400s | Rekey after this interval |
crypto.session.rekey_bytes |
u64 | 0 | Rekey after N bytes (0 = disabled) |
Crypto sessions are keyed by remote npub and survive transport changes. The handshake is carried within SessionSetup/SessionAck messages (combined establishment).
Peer Defaults
| Parameter | Type | Default | Description |
|---|---|---|---|
peer.auth.timeout |
duration | 10s | Auth handshake timeout |
peer.auth.timeout_tor |
duration | 60s | Auth timeout for Tor links |
peer.keepalive.interval |
duration | 30s | Keepalive probe interval |
peer.keepalive.timeout |
duration | 90s | Declare peer dead after silence |
peer.reconnect.policy |
enum | backoff | none, immediate, backoff |
peer.reconnect.delay_initial |
duration | 1s | Initial reconnect delay |
peer.reconnect.delay_max |
duration | 300s | Maximum reconnect delay |
peer.reconnect.max_attempts |
u32 | 0 | 0 = unlimited |
Transport: UDP
| Parameter | Type | Default | Description |
|---|---|---|---|
transport.udp.enabled |
bool | true | Enable UDP transport |
transport.udp.bind_addr |
string | "0.0.0.0:4000" | Bind address |
transport.udp.discovery.enabled |
bool | true | Enable discovery |
transport.udp.discovery.dns_sd |
bool | false | Use DNS-SD discovery |
transport.udp.discovery.nostr_relays |
list | [] | Relays for peer discovery |
transport.udp.auto_connect |
bool | true | Connect to discovered peers |
transport.udp.base_rtt |
duration | 50ms | RTT hint for timeouts |
Transport: Ethernet
| Parameter | Type | Default | Description |
|---|---|---|---|
transport.ethernet.enabled |
bool | false | Enable Ethernet transport |
transport.ethernet.interface |
string | "eth0" | Interface name |
transport.ethernet.ethertype |
u16 | 0x88b5 | FIPS EtherType |
transport.ethernet.discovery.enabled |
bool | true | Enable multicast discovery |
transport.ethernet.discovery.multicast_addr |
string | "33:33:00:00:ff:05" | Discovery multicast MAC |
transport.ethernet.auto_connect |
bool | true | Connect to discovered peers |
transport.ethernet.base_rtt |
duration | 1ms | RTT hint for timeouts |
Transport: WiFi
| Parameter | Type | Default | Description |
|---|---|---|---|
transport.wifi.enabled |
bool | false | Enable WiFi transport |
transport.wifi.interface |
string | "wlan0" | Interface name |
transport.wifi.mode |
enum | infrastructure | infrastructure, adhoc, direct |
transport.wifi.discovery.enabled |
bool | true | Enable discovery |
transport.wifi.auto_connect |
bool | true | Connect to discovered peers |
transport.wifi.base_rtt |
duration | 5ms | RTT hint for timeouts |
Transport: Tor
| Parameter | Type | Default | Description |
|---|---|---|---|
transport.tor.enabled |
bool | false | Enable Tor transport |
transport.tor.mode |
enum | embedded | embedded, external |
transport.tor.control_port |
string | "127.0.0.1:9051" | External daemon control |
transport.tor.onion_service.enabled |
bool | true | Publish onion service |
transport.tor.onion_service.port |
u16 | 4000 | Onion service port |
transport.tor.discovery.enabled |
bool | true | Enable discovery |
transport.tor.discovery.nostr_relays |
list | [] | Relays for peer discovery |
transport.tor.auto_connect |
bool | false | Connect to discovered peers |
transport.tor.base_rtt |
duration | 2s | RTT hint for timeouts |
transport.tor.startup_timeout |
duration | 180s | Tor bootstrap timeout |
Adaptive Timeouts
| Parameter | Type | Default | Description |
|---|---|---|---|
timeout.adaptive.enabled |
bool | true | Use measured RTT for timeouts |
timeout.adaptive.rtt_multiplier |
f32 | 3.0 | Timeout = RTT × multiplier |
timeout.adaptive.min |
duration | 100ms | Minimum timeout |
timeout.adaptive.max |
duration | 60s | Maximum timeout |
Leaf-Only Mode
| Parameter | Type | Default | Description |
|---|---|---|---|
node.leaf_only |
bool | false | Operate as leaf-only node |
References
- fips-intro.md — Overall FIPS protocol design
- fips-session-protocol.md — Traffic flow, session terminology, crypto sessions
- fips-transports.md — Transport protocol characteristics
- fips-routing.md — Routing, Bloom filters, discovery
- spanning-tree-dynamics.md — Tree protocol dynamics