mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Replace two-node-udp with Docker network test harness, fix dead code warnings
Replace examples/two-node-udp/ (netns-based) with examples/docker-network/ (Docker compose, 5 nodes, mesh + chain topologies). The new harness uses auto-detecting build script and includes SVG topology diagrams. Remove unused get_session_mut, remote_addr(), remote_pubkey() methods. Gate test-only accessors with #[cfg(test)]. Zero warnings in release build.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
fips
|
||||
@@ -0,0 +1,11 @@
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
iproute2 iputils-ping dnsutils && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY fips /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/fips
|
||||
|
||||
ENTRYPOINT ["fips", "--config", "/etc/fips/fips.yaml"]
|
||||
@@ -0,0 +1,134 @@
|
||||
# Docker Network Test Harness
|
||||
|
||||
Multi-node integration test for FIPS using Docker containers. Two topologies
|
||||
are provided: a sparse mesh (5 nodes, 6 links) and a linear chain (5 nodes,
|
||||
4 links). Both exercise the full FIPS stack including TUN devices, DNS
|
||||
resolution, peer link encryption, spanning tree construction, and
|
||||
discovery-driven multi-hop routing.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker with the compose plugin
|
||||
- Rust toolchain (for building the FIPS binary)
|
||||
|
||||
## Quick Start
|
||||
|
||||
Build the binary and copy it to the docker context:
|
||||
|
||||
```bash
|
||||
./scripts/build.sh
|
||||
```
|
||||
|
||||
### Mesh Topology
|
||||
|
||||
```bash
|
||||
docker compose --profile mesh build
|
||||
docker compose --profile mesh up -d
|
||||
./scripts/ping-test.sh mesh # 20/20 expected
|
||||
docker compose --profile mesh down
|
||||
```
|
||||
|
||||
### Chain Topology
|
||||
|
||||
```bash
|
||||
docker compose --profile chain build
|
||||
docker compose --profile chain up -d
|
||||
./scripts/ping-test.sh chain # 6/6 expected
|
||||
docker compose --profile chain down
|
||||
```
|
||||
|
||||
## Mesh Topology
|
||||
|
||||

|
||||
|
||||
Five nodes with 6 bidirectional UDP links forming a sparse, fully connected
|
||||
graph. Not all nodes are direct peers — non-adjacent pairs require
|
||||
discovery-driven multi-hop routing to establish end-to-end sessions.
|
||||
|
||||
The spanning tree is rooted at node A, which has the lexicographically
|
||||
smallest `NodeAddr` (the first 16 bytes of `SHA-256(pubkey)`). Tree edges
|
||||
are highlighted in blue in the diagram above.
|
||||
|
||||
The ping test exercises all 20 directed pairs (5 nodes x 4 targets each),
|
||||
covering both direct-peer and multi-hop paths.
|
||||
|
||||
| Link | Type |
|
||||
|------|------|
|
||||
| A — D | tree edge (D's parent is A) |
|
||||
| A — E | tree edge (E's parent is A) |
|
||||
| C — D | tree edge (C's parent is D) |
|
||||
| B — C | tree edge (B's parent is C) |
|
||||
| D — E | non-tree link |
|
||||
| C — E | non-tree link |
|
||||
|
||||
## Chain Topology
|
||||
|
||||

|
||||
|
||||
Five nodes in a linear chain: A — B — C — D — E. Each node peers only with
|
||||
its immediate neighbors. Multi-hop communication (e.g., A to E) requires the
|
||||
discovery protocol to find routes through intermediate nodes.
|
||||
|
||||
The ping test covers:
|
||||
|
||||
- Adjacent hops: A→B, B→C (1 hop each)
|
||||
- Multi-hop: A→C (2 hops), A→D (3 hops), A→E (4 hops)
|
||||
- Reverse: E→A (4 hops)
|
||||
|
||||
## Node Identities
|
||||
|
||||
All nodes use deterministic test keys (not for production use).
|
||||
|
||||
| Node | npub | FIPS IPv6 Address | Docker IP |
|
||||
|------|------|-------------------|-----------|
|
||||
| A | `npub1sjlh2c3...` | `fd69:e08d:65cc:3a6b:...` | 172.20.0.10 |
|
||||
| B | `npub1tdwa4vj...` | `fd8e:302c:287e:b48d:...` | 172.20.0.11 |
|
||||
| C | `npub1cld9yay...` | `fdac:a221:4069:5044:...` | 172.20.0.12 |
|
||||
| D | `npub1n9lpnv0...` | `fdb6:8411:a191:6d48:...` | 172.20.0.13 |
|
||||
| E | `npub1wf8akf8...` | `fded:7dee:d386:a546:...` | 172.20.0.14 |
|
||||
|
||||
## Container Configuration
|
||||
|
||||
- **Base image**: debian:bookworm-slim
|
||||
- **Capabilities**: `CAP_NET_ADMIN` (for TUN device creation)
|
||||
- **Devices**: `/dev/net/tun` mapped into each container
|
||||
- **DNS**: FIPS built-in resolver on `127.0.0.1:53`
|
||||
- **Transport**: UDP on port 4000, MTU 1280
|
||||
- **TUN**: `fips0` interface, MTU 1280
|
||||
|
||||
Each node resolves `<npub>.fips` DNS names to FIPS IPv6 addresses via its
|
||||
local DNS responder, which primes the identity cache for session establishment.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Stale images after code changes**: Docker compose may cache old layers.
|
||||
Force a clean rebuild:
|
||||
|
||||
```bash
|
||||
docker compose --profile mesh build --no-cache
|
||||
```
|
||||
|
||||
**Check node logs**:
|
||||
|
||||
```bash
|
||||
docker logs fips-node-a
|
||||
docker logs -f fips-node-c # follow
|
||||
```
|
||||
|
||||
**Verify DNS resolution inside a container**:
|
||||
|
||||
```bash
|
||||
docker exec fips-node-a dig AAAA npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le.fips @127.0.0.1
|
||||
```
|
||||
|
||||
**Verify binary is up to date**: Compare hashes between the local build and
|
||||
the binary inside the container:
|
||||
|
||||
```bash
|
||||
md5sum examples/docker-network/fips
|
||||
docker exec fips-node-a md5sum /usr/local/bin/fips
|
||||
```
|
||||
|
||||
**Increase convergence time**: If tests fail intermittently, the 5-second
|
||||
convergence wait in `ping-test.sh` may be insufficient. Edit the `sleep`
|
||||
value at the top of the script.
|
||||
+4
-5
@@ -1,4 +1,4 @@
|
||||
# FIPS Node A configuration
|
||||
# FIPS Node A configuration (chain topology: A-B-C-D-E)
|
||||
#
|
||||
# Identity: npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m
|
||||
# FIPS address: fd69:e08d:65cc:3a6b:9c2c:2ac4:bd40:5e4b
|
||||
@@ -15,11 +15,11 @@ tun:
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
port: 5354
|
||||
port: 53
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "10.0.0.1:4000"
|
||||
bind_addr: "0.0.0.0:4000"
|
||||
mtu: 1280
|
||||
|
||||
peers:
|
||||
@@ -27,6 +27,5 @@ peers:
|
||||
alias: "node-b"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "10.0.0.2:4000"
|
||||
priority: 1
|
||||
addr: "172.20.0.11:4000"
|
||||
connect_policy: auto_connect
|
||||
@@ -0,0 +1,37 @@
|
||||
# FIPS Node B configuration (chain topology: A-B-C-D-E)
|
||||
#
|
||||
# Identity: npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le
|
||||
# FIPS address: fd8e:302c:287e:b48d:6268:122f:da76:b77
|
||||
|
||||
node:
|
||||
identity:
|
||||
nsec: "b102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fb0"
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
port: 53
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:4000"
|
||||
mtu: 1280
|
||||
|
||||
peers:
|
||||
- npub: "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
||||
alias: "node-a"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.10:4000"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
|
||||
alias: "node-c"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.12:4000"
|
||||
connect_policy: auto_connect
|
||||
@@ -0,0 +1,37 @@
|
||||
# FIPS Node C configuration (chain topology: A-B-C-D-E)
|
||||
#
|
||||
# Identity: npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6
|
||||
# FIPS address: fdac:a221:4069:5044:c41:d550:280e:d15a
|
||||
|
||||
node:
|
||||
identity:
|
||||
nsec: "c102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fc0"
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
port: 53
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:4000"
|
||||
mtu: 1280
|
||||
|
||||
peers:
|
||||
- npub: "npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le"
|
||||
alias: "node-b"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.11:4000"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
|
||||
alias: "node-d"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.13:4000"
|
||||
connect_policy: auto_connect
|
||||
@@ -0,0 +1,37 @@
|
||||
# FIPS Node D configuration (chain topology: A-B-C-D-E)
|
||||
#
|
||||
# Identity: npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl
|
||||
# FIPS address: fdb6:8411:a191:6d48:efc6:b8bd:63bb:1cd7
|
||||
|
||||
node:
|
||||
identity:
|
||||
nsec: "d102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fd0"
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
port: 53
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:4000"
|
||||
mtu: 1280
|
||||
|
||||
peers:
|
||||
- npub: "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
|
||||
alias: "node-c"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.12:4000"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1wf8akf8lu2zdkjkmwhl75pqvven654mpv4sz2x2tprl5265mgrzq8nhak4"
|
||||
alias: "node-e"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.14:4000"
|
||||
connect_policy: auto_connect
|
||||
@@ -0,0 +1,31 @@
|
||||
# FIPS Node E configuration (chain topology: A-B-C-D-E)
|
||||
#
|
||||
# Identity: npub1wf8akf8lu2zdkjkmwhl75pqvven654mpv4sz2x2tprl5265mgrzq8nhak4
|
||||
# FIPS address: fded:7dee:d386:a546:2a43:5103:16d8:4f3c
|
||||
|
||||
node:
|
||||
identity:
|
||||
nsec: "e102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fe0"
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
port: 53
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:4000"
|
||||
mtu: 1280
|
||||
|
||||
peers:
|
||||
- npub: "npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
|
||||
alias: "node-d"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.13:4000"
|
||||
connect_policy: auto_connect
|
||||
@@ -0,0 +1,37 @@
|
||||
# FIPS Node A configuration (mesh topology)
|
||||
#
|
||||
# Identity: npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m
|
||||
# FIPS address: fd69:e08d:65cc:3a6b:9c2c:2ac4:bd40:5e4b
|
||||
|
||||
node:
|
||||
identity:
|
||||
nsec: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
port: 53
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:4000"
|
||||
mtu: 1280
|
||||
|
||||
peers:
|
||||
- npub: "npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
|
||||
alias: "node-d"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.13:4000"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1wf8akf8lu2zdkjkmwhl75pqvven654mpv4sz2x2tprl5265mgrzq8nhak4"
|
||||
alias: "node-e"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.14:4000"
|
||||
connect_policy: auto_connect
|
||||
+6
-7
@@ -1,4 +1,4 @@
|
||||
# FIPS Node B configuration
|
||||
# FIPS Node B configuration (mesh topology)
|
||||
#
|
||||
# Identity: npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le
|
||||
# FIPS address: fd8e:302c:287e:b48d:6268:122f:da76:b77
|
||||
@@ -15,18 +15,17 @@ tun:
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
port: 5354
|
||||
port: 53
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "10.0.0.2:4000"
|
||||
bind_addr: "0.0.0.0:4000"
|
||||
mtu: 1280
|
||||
|
||||
peers:
|
||||
- npub: "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
||||
alias: "node-a"
|
||||
- npub: "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
|
||||
alias: "node-c"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "10.0.0.1:4000"
|
||||
priority: 1
|
||||
addr: "172.20.0.12:4000"
|
||||
connect_policy: auto_connect
|
||||
@@ -0,0 +1,43 @@
|
||||
# FIPS Node C configuration (mesh topology)
|
||||
#
|
||||
# Identity: npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6
|
||||
# FIPS address: fdac:a221:4069:5044:c41:d550:280e:d15a
|
||||
|
||||
node:
|
||||
identity:
|
||||
nsec: "c102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fc0"
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
port: 53
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:4000"
|
||||
mtu: 1280
|
||||
|
||||
peers:
|
||||
- npub: "npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le"
|
||||
alias: "node-b"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.11:4000"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
|
||||
alias: "node-d"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.13:4000"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1wf8akf8lu2zdkjkmwhl75pqvven654mpv4sz2x2tprl5265mgrzq8nhak4"
|
||||
alias: "node-e"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.14:4000"
|
||||
connect_policy: auto_connect
|
||||
@@ -0,0 +1,43 @@
|
||||
# FIPS Node D configuration (mesh topology)
|
||||
#
|
||||
# Identity: npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl
|
||||
# FIPS address: fdb6:8411:a191:6d48:efc6:b8bd:63bb:1cd7
|
||||
|
||||
node:
|
||||
identity:
|
||||
nsec: "d102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fd0"
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
port: 53
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:4000"
|
||||
mtu: 1280
|
||||
|
||||
peers:
|
||||
- npub: "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
||||
alias: "node-a"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.10:4000"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
|
||||
alias: "node-c"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.12:4000"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1wf8akf8lu2zdkjkmwhl75pqvven654mpv4sz2x2tprl5265mgrzq8nhak4"
|
||||
alias: "node-e"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.14:4000"
|
||||
connect_policy: auto_connect
|
||||
@@ -0,0 +1,43 @@
|
||||
# FIPS Node E configuration (mesh topology)
|
||||
#
|
||||
# Identity: npub1wf8akf8lu2zdkjkmwhl75pqvven654mpv4sz2x2tprl5265mgrzq8nhak4
|
||||
# FIPS address: fded:7dee:d386:a546:2a43:5103:16d8:4f3c
|
||||
|
||||
node:
|
||||
identity:
|
||||
nsec: "e102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fe0"
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
port: 53
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:4000"
|
||||
mtu: 1280
|
||||
|
||||
peers:
|
||||
- npub: "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
||||
alias: "node-a"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.10:4000"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
|
||||
alias: "node-c"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.12:4000"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
|
||||
alias: "node-d"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.20.0.13:4000"
|
||||
connect_policy: auto_connect
|
||||
@@ -0,0 +1,113 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 400" font-family="monospace" font-size="13">
|
||||
<style>
|
||||
circle.node { fill: #1a1a2e; stroke: #e0e0e0; stroke-width: 2; }
|
||||
text { fill: #e0e0e0; }
|
||||
text.name { font-size: 14px; font-weight: bold; }
|
||||
text.ip { font-size: 10px; fill: #808890; }
|
||||
text.fips-addr { font-size: 9px; fill: #607090; }
|
||||
text.title { font-size: 16px; font-weight: bold; }
|
||||
text.subtitle { font-size: 11px; fill: #909090; }
|
||||
text.legend { font-size: 11px; }
|
||||
text.net-label { font-size: 10px; fill: #606880; }
|
||||
text.hop-label { font-size: 10px; fill: #909090; }
|
||||
line.link { stroke: #506090; stroke-width: 2; }
|
||||
line.path { stroke: #d0a040; stroke-width: 1.2; stroke-dasharray: 5 3; }
|
||||
rect.legend-box { fill: #151520; stroke: #404060; stroke-width: 1; rx: 4; }
|
||||
rect.net-box { fill: none; stroke: #303050; stroke-width: 1; stroke-dasharray: 6 3; rx: 8; }
|
||||
marker { fill: #d0a040; }
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 6" refX="10" refY="3"
|
||||
markerWidth="8" markerHeight="5" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 3 L 0 6 z"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="700" height="400" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="350" y="30" text-anchor="middle" class="title">Docker Chain Topology</text>
|
||||
<text x="350" y="48" text-anchor="middle" class="subtitle">Linear chain — A–B–C–D–E, multi-hop routing via discovery</text>
|
||||
|
||||
<!-- Docker network boundary -->
|
||||
<rect x="30" y="65" width="640" height="175" class="net-box"/>
|
||||
<text x="40" y="82" class="net-label">172.20.0.0/24 (docker: fips-net)</text>
|
||||
|
||||
<!--
|
||||
Horizontal chain layout:
|
||||
A (110, 170) B (230, 170) C (350, 170) D (470, 170) E (590, 170)
|
||||
-->
|
||||
|
||||
<!-- === Links === -->
|
||||
|
||||
<!-- A—B -->
|
||||
<line x1="138" y1="170" x2="202" y2="170" class="link"/>
|
||||
<text x="170" y="160" text-anchor="middle" class="hop-label">UDP</text>
|
||||
|
||||
<!-- B—C -->
|
||||
<line x1="258" y1="170" x2="322" y2="170" class="link"/>
|
||||
<text x="290" y="160" text-anchor="middle" class="hop-label">UDP</text>
|
||||
|
||||
<!-- C—D -->
|
||||
<line x1="378" y1="170" x2="442" y2="170" class="link"/>
|
||||
<text x="410" y="160" text-anchor="middle" class="hop-label">UDP</text>
|
||||
|
||||
<!-- D—E -->
|
||||
<line x1="498" y1="170" x2="562" y2="170" class="link"/>
|
||||
<text x="530" y="160" text-anchor="middle" class="hop-label">UDP</text>
|
||||
|
||||
<!-- === Multi-hop path illustration (A→E, 4 hops) === -->
|
||||
<line x1="110" y1="210" x2="590" y2="210" class="path"
|
||||
marker-end="url(#arrow)"/>
|
||||
<text x="350" y="228" text-anchor="middle" class="hop-label">
|
||||
A → E (4 hops, discovery-routed)
|
||||
</text>
|
||||
|
||||
<!-- === Nodes === -->
|
||||
|
||||
<!-- Node A -->
|
||||
<circle cx="110" cy="170" r="28" class="node"/>
|
||||
<text x="110" y="166" text-anchor="middle" class="name">A</text>
|
||||
<text x="110" y="179" text-anchor="middle" class="ip">.10</text>
|
||||
<text x="110" y="130" text-anchor="middle" class="fips-addr">fd69:e08d:...</text>
|
||||
|
||||
<!-- Node B -->
|
||||
<circle cx="230" cy="170" r="28" class="node"/>
|
||||
<text x="230" y="166" text-anchor="middle" class="name">B</text>
|
||||
<text x="230" y="179" text-anchor="middle" class="ip">.11</text>
|
||||
<text x="230" y="130" text-anchor="middle" class="fips-addr">fd8e:302c:...</text>
|
||||
|
||||
<!-- Node C -->
|
||||
<circle cx="350" cy="170" r="28" class="node"/>
|
||||
<text x="350" y="166" text-anchor="middle" class="name">C</text>
|
||||
<text x="350" y="179" text-anchor="middle" class="ip">.12</text>
|
||||
<text x="350" y="130" text-anchor="middle" class="fips-addr">fdac:a221:...</text>
|
||||
|
||||
<!-- Node D -->
|
||||
<circle cx="470" cy="170" r="28" class="node"/>
|
||||
<text x="470" y="166" text-anchor="middle" class="name">D</text>
|
||||
<text x="470" y="179" text-anchor="middle" class="ip">.13</text>
|
||||
<text x="470" y="130" text-anchor="middle" class="fips-addr">fdb6:8411:...</text>
|
||||
|
||||
<!-- Node E -->
|
||||
<circle cx="590" cy="170" r="28" class="node"/>
|
||||
<text x="590" y="166" text-anchor="middle" class="name">E</text>
|
||||
<text x="590" y="179" text-anchor="middle" class="ip">.14</text>
|
||||
<text x="590" y="130" text-anchor="middle" class="fips-addr">fded:7dee:...</text>
|
||||
|
||||
<!-- === Legend === -->
|
||||
<rect x="30" y="260" width="640" height="120" class="legend-box"/>
|
||||
|
||||
<line x1="50" y1="282" x2="90" y2="282" stroke="#506090" stroke-width="2"/>
|
||||
<text x="100" y="286" class="legend">Direct UDP peer link (port 4000, auto_connect)</text>
|
||||
|
||||
<line x1="50" y1="304" x2="90" y2="304" stroke="#d0a040" stroke-width="1.2"
|
||||
stroke-dasharray="5 3" marker-end="url(#arrow)"/>
|
||||
<text x="100" y="308" class="legend">Multi-hop path (discovery + session establishment)</text>
|
||||
|
||||
<text x="50" y="332" class="legend" fill="#808890">Routing flow: TUN packet → identity cache → initiate_session()</text>
|
||||
<text x="50" y="347" class="legend" fill="#808890"> → find_next_hop() fails → discovery flood → route_cache populated</text>
|
||||
<text x="50" y="362" class="legend" fill="#808890"> → session retry → Noise IK handshake → encrypted data delivery</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,142 @@
|
||||
networks:
|
||||
fips-net:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.20.0.0/24
|
||||
|
||||
x-fips-common: &fips-common
|
||||
build:
|
||||
context: .
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
sysctls:
|
||||
- net.ipv6.conf.all.disable_ipv6=0
|
||||
restart: "no"
|
||||
environment:
|
||||
- RUST_LOG=info
|
||||
|
||||
services:
|
||||
# ── Mesh topology ──────────────────────────────────────────────
|
||||
node-a:
|
||||
<<: *fips-common
|
||||
profiles: ["mesh"]
|
||||
container_name: fips-node-a
|
||||
hostname: node-a
|
||||
volumes:
|
||||
- ./resolv.conf:/etc/resolv.conf:ro
|
||||
- ./configs/mesh/node-a.yaml:/etc/fips/fips.yaml:ro
|
||||
networks:
|
||||
fips-net:
|
||||
ipv4_address: 172.20.0.10
|
||||
|
||||
node-b:
|
||||
<<: *fips-common
|
||||
profiles: ["mesh"]
|
||||
container_name: fips-node-b
|
||||
hostname: node-b
|
||||
volumes:
|
||||
- ./resolv.conf:/etc/resolv.conf:ro
|
||||
- ./configs/mesh/node-b.yaml:/etc/fips/fips.yaml:ro
|
||||
networks:
|
||||
fips-net:
|
||||
ipv4_address: 172.20.0.11
|
||||
|
||||
node-c:
|
||||
<<: *fips-common
|
||||
profiles: ["mesh"]
|
||||
container_name: fips-node-c
|
||||
hostname: node-c
|
||||
volumes:
|
||||
- ./resolv.conf:/etc/resolv.conf:ro
|
||||
- ./configs/mesh/node-c.yaml:/etc/fips/fips.yaml:ro
|
||||
networks:
|
||||
fips-net:
|
||||
ipv4_address: 172.20.0.12
|
||||
|
||||
node-d:
|
||||
<<: *fips-common
|
||||
profiles: ["mesh"]
|
||||
container_name: fips-node-d
|
||||
hostname: node-d
|
||||
volumes:
|
||||
- ./resolv.conf:/etc/resolv.conf:ro
|
||||
- ./configs/mesh/node-d.yaml:/etc/fips/fips.yaml:ro
|
||||
networks:
|
||||
fips-net:
|
||||
ipv4_address: 172.20.0.13
|
||||
|
||||
node-e:
|
||||
<<: *fips-common
|
||||
profiles: ["mesh"]
|
||||
container_name: fips-node-e
|
||||
hostname: node-e
|
||||
volumes:
|
||||
- ./resolv.conf:/etc/resolv.conf:ro
|
||||
- ./configs/mesh/node-e.yaml:/etc/fips/fips.yaml:ro
|
||||
networks:
|
||||
fips-net:
|
||||
ipv4_address: 172.20.0.14
|
||||
|
||||
# ── Chain topology (A-B-C-D-E) ────────────────────────────────
|
||||
chain-a:
|
||||
<<: *fips-common
|
||||
profiles: ["chain"]
|
||||
container_name: fips-node-a
|
||||
hostname: node-a
|
||||
volumes:
|
||||
- ./resolv.conf:/etc/resolv.conf:ro
|
||||
- ./configs/chain/node-a.yaml:/etc/fips/fips.yaml:ro
|
||||
networks:
|
||||
fips-net:
|
||||
ipv4_address: 172.20.0.10
|
||||
|
||||
chain-b:
|
||||
<<: *fips-common
|
||||
profiles: ["chain"]
|
||||
container_name: fips-node-b
|
||||
hostname: node-b
|
||||
volumes:
|
||||
- ./resolv.conf:/etc/resolv.conf:ro
|
||||
- ./configs/chain/node-b.yaml:/etc/fips/fips.yaml:ro
|
||||
networks:
|
||||
fips-net:
|
||||
ipv4_address: 172.20.0.11
|
||||
|
||||
chain-c:
|
||||
<<: *fips-common
|
||||
profiles: ["chain"]
|
||||
container_name: fips-node-c
|
||||
hostname: node-c
|
||||
volumes:
|
||||
- ./resolv.conf:/etc/resolv.conf:ro
|
||||
- ./configs/chain/node-c.yaml:/etc/fips/fips.yaml:ro
|
||||
networks:
|
||||
fips-net:
|
||||
ipv4_address: 172.20.0.12
|
||||
|
||||
chain-d:
|
||||
<<: *fips-common
|
||||
profiles: ["chain"]
|
||||
container_name: fips-node-d
|
||||
hostname: node-d
|
||||
volumes:
|
||||
- ./resolv.conf:/etc/resolv.conf:ro
|
||||
- ./configs/chain/node-d.yaml:/etc/fips/fips.yaml:ro
|
||||
networks:
|
||||
fips-net:
|
||||
ipv4_address: 172.20.0.13
|
||||
|
||||
chain-e:
|
||||
<<: *fips-common
|
||||
profiles: ["chain"]
|
||||
container_name: fips-node-e
|
||||
hostname: node-e
|
||||
volumes:
|
||||
- ./resolv.conf:/etc/resolv.conf:ro
|
||||
- ./configs/chain/node-e.yaml:/etc/fips/fips.yaml:ro
|
||||
networks:
|
||||
fips-net:
|
||||
ipv4_address: 172.20.0.14
|
||||
@@ -0,0 +1,130 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 560" font-family="monospace" font-size="13">
|
||||
<style>
|
||||
circle.node { fill: #1a1a2e; stroke: #e0e0e0; stroke-width: 2; }
|
||||
circle.root { fill: #1a1a2e; stroke: #60a0e0; stroke-width: 2.5; }
|
||||
text { fill: #e0e0e0; }
|
||||
text.name { font-size: 14px; font-weight: bold; }
|
||||
text.root-label { font-size: 9px; fill: #60a0e0; }
|
||||
text.npub { font-size: 9px; fill: #808890; }
|
||||
text.title { font-size: 16px; font-weight: bold; }
|
||||
text.subtitle { font-size: 11px; fill: #909090; }
|
||||
text.legend { font-size: 11px; }
|
||||
text.net-label { font-size: 10px; fill: #606880; }
|
||||
text.test-label { font-size: 10px; fill: #909090; }
|
||||
line.link { stroke: #404860; stroke-width: 1.5; }
|
||||
line.link-tree { stroke: #60a0e0; stroke-width: 2; }
|
||||
rect.legend-box { fill: #151520; stroke: #404060; stroke-width: 1; rx: 4; }
|
||||
rect.net-box { fill: none; stroke: #303050; stroke-width: 1;
|
||||
stroke-dasharray: 6 3; rx: 8; }
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
<marker id="tree-arrow" viewBox="0 0 10 6" refX="38" refY="3"
|
||||
markerWidth="10" markerHeight="7" orient="auto-start-reverse"
|
||||
markerUnits="userSpaceOnUse">
|
||||
<path d="M 0 0 L 10 3 L 0 6 z" fill="#60a0e0"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="700" height="560" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="350" y="30" text-anchor="middle" class="title">Docker Mesh Topology</text>
|
||||
<text x="350" y="48" text-anchor="middle" class="subtitle">Sparse mesh — 5 nodes, 6 bidirectional UDP links</text>
|
||||
|
||||
<!-- Docker network boundary -->
|
||||
<rect x="40" y="62" width="620" height="310" class="net-box"/>
|
||||
<text x="52" y="78" class="net-label">172.20.0.0/24 (docker: fips-net)</text>
|
||||
|
||||
<!--
|
||||
Pentagon layout (centered at 350,225):
|
||||
A = top (350, 110)
|
||||
B = upper-right (550, 195)
|
||||
C = lower-right (480, 335)
|
||||
D = lower-left (220, 335)
|
||||
E = upper-left (150, 195)
|
||||
|
||||
Links: B-C, C-D, D-E, E-A, A-D, C-E
|
||||
|
||||
Spanning tree (root=A, lowest NodeAddr):
|
||||
A → D, A → E
|
||||
D → C
|
||||
C → B
|
||||
Tree edges highlighted; non-tree edges dim.
|
||||
-->
|
||||
|
||||
<!-- === Peer links (6 edges) === -->
|
||||
<!-- Non-tree edges (dim) -->
|
||||
<!-- D — E -->
|
||||
<line x1="220" y1="335" x2="150" y2="195" class="link"/>
|
||||
<!-- C — E -->
|
||||
<line x1="480" y1="335" x2="150" y2="195" class="link"/>
|
||||
|
||||
<!-- Tree edges (highlighted, arrow toward parent/root) -->
|
||||
<!-- B → C (parent) -->
|
||||
<line x1="550" y1="195" x2="480" y2="335" class="link-tree"
|
||||
marker-end="url(#tree-arrow)"/>
|
||||
<!-- C → D (parent) -->
|
||||
<line x1="480" y1="335" x2="220" y2="335" class="link-tree"
|
||||
marker-end="url(#tree-arrow)"/>
|
||||
<!-- E → A (parent) -->
|
||||
<line x1="150" y1="195" x2="350" y2="110" class="link-tree"
|
||||
marker-end="url(#tree-arrow)"/>
|
||||
<!-- D → A (parent) -->
|
||||
<line x1="220" y1="335" x2="350" y2="110" class="link-tree"
|
||||
marker-end="url(#tree-arrow)"/>
|
||||
|
||||
<!-- === Nodes (on top of links) === -->
|
||||
|
||||
<!-- Node A — root (2 peers: D, E) -->
|
||||
<circle cx="350" cy="110" r="26" class="root"/>
|
||||
<text x="350" y="108" text-anchor="middle" class="name">A</text>
|
||||
<text x="350" y="121" text-anchor="middle" class="npub">.10</text>
|
||||
<text x="350" y="75" text-anchor="middle" class="npub">npub1sjlh2c3...</text>
|
||||
<text x="350" y="148" text-anchor="middle" class="root-label">root</text>
|
||||
|
||||
<!-- Node B (1 peer: C) -->
|
||||
<circle cx="550" cy="195" r="26" class="node"/>
|
||||
<text x="550" y="193" text-anchor="middle" class="name">B</text>
|
||||
<text x="550" y="206" text-anchor="middle" class="npub">.11</text>
|
||||
<text x="550" y="162" text-anchor="middle" class="npub">npub1tdwa4vj...</text>
|
||||
|
||||
<!-- Node C (3 peers: B, D, E) -->
|
||||
<circle cx="480" cy="335" r="26" class="node"/>
|
||||
<text x="480" y="333" text-anchor="middle" class="name">C</text>
|
||||
<text x="480" y="346" text-anchor="middle" class="npub">.12</text>
|
||||
<text x="516" y="357" text-anchor="start" class="npub">npub1cld9yay...</text>
|
||||
|
||||
<!-- Node D (3 peers: A, C, E) -->
|
||||
<circle cx="220" cy="335" r="26" class="node"/>
|
||||
<text x="220" y="333" text-anchor="middle" class="name">D</text>
|
||||
<text x="220" y="346" text-anchor="middle" class="npub">.13</text>
|
||||
<text x="112" y="357" text-anchor="start" class="npub">npub1n9lpnv0...</text>
|
||||
|
||||
<!-- Node E (3 peers: A, C, D) -->
|
||||
<circle cx="150" cy="195" r="26" class="node"/>
|
||||
<text x="150" y="193" text-anchor="middle" class="name">E</text>
|
||||
<text x="150" y="206" text-anchor="middle" class="npub">.14</text>
|
||||
<text x="150" y="162" text-anchor="middle" class="npub">npub1wf8akf8...</text>
|
||||
|
||||
<!-- Test info -->
|
||||
<text x="350" y="390" text-anchor="middle" class="test-label">
|
||||
All-pairs ping test: 20 directed paths (direct + multi-hop via discovery)
|
||||
</text>
|
||||
|
||||
<!-- === Legend === -->
|
||||
<rect x="40" y="410" width="620" height="130" class="legend-box"/>
|
||||
|
||||
<line x1="60" y1="435" x2="100" y2="435" stroke="#404860" stroke-width="1.5"/>
|
||||
<text x="110" y="439" class="legend">UDP peer link (port 4000, auto_connect)</text>
|
||||
|
||||
<line x1="55" y1="457" x2="100" y2="457" stroke="#60a0e0" stroke-width="2"/>
|
||||
<path d="M 90 454 L 100 457 L 90 460 z" fill="#60a0e0"/>
|
||||
<text x="110" y="461" class="legend">Spanning tree edge (arrow toward parent/root)</text>
|
||||
|
||||
<circle cx="72" cy="479" r="8" fill="#1a1a2e" stroke="#60a0e0" stroke-width="2"/>
|
||||
<text x="110" y="483" class="legend">Tree root (lowest NodeAddr = SHA-256(pubkey)[:16])</text>
|
||||
|
||||
<text x="60" y="525" class="legend" fill="#808890">Each container: CAP_NET_ADMIN, /dev/net/tun, DNS on 127.0.0.1:53</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
@@ -0,0 +1 @@
|
||||
nameserver 127.0.0.1
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# Build the FIPS binary and copy it to the docker build context.
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
DOCKER_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Find project root (directory containing Cargo.toml)
|
||||
PROJECT_ROOT="$(cd "$DOCKER_DIR/../.." && pwd)"
|
||||
if [ ! -f "$PROJECT_ROOT/Cargo.toml" ]; then
|
||||
echo "Error: Cannot find Cargo.toml at $PROJECT_ROOT" >&2
|
||||
echo "Expected layout: <project-root>/examples/docker-network/scripts/build.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Building FIPS (release)..."
|
||||
cargo build --release --manifest-path="$PROJECT_ROOT/Cargo.toml"
|
||||
|
||||
echo "Copying binary to docker context..."
|
||||
cp "$PROJECT_ROOT/target/release/fips" "$DOCKER_DIR/fips"
|
||||
|
||||
echo "Done. Binary at $DOCKER_DIR/fips"
|
||||
echo ""
|
||||
echo "Next: cd $DOCKER_DIR && docker compose --profile mesh build"
|
||||
Executable
+102
@@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
# End-to-end ping test between FIPS nodes via DNS resolution.
|
||||
# Usage: ./ping-test.sh [mesh|chain]
|
||||
#
|
||||
# Requires containers to be running:
|
||||
# docker compose --profile mesh up -d
|
||||
# ./scripts/ping-test.sh mesh
|
||||
set -e
|
||||
|
||||
PROFILE="${1:-mesh}"
|
||||
COUNT=3
|
||||
TIMEOUT=10
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
|
||||
# Node identities
|
||||
NPUB_A="npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
||||
NPUB_B="npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le"
|
||||
NPUB_C="npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
|
||||
NPUB_D="npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
|
||||
NPUB_E="npub1wf8akf8lu2zdkjkmwhl75pqvven654mpv4sz2x2tprl5265mgrzq8nhak4"
|
||||
|
||||
ping_test() {
|
||||
local from="$1"
|
||||
local to_npub="$2"
|
||||
local label="$3"
|
||||
|
||||
echo -n " $label ... "
|
||||
if docker exec "fips-$from" ping6 -c "$COUNT" -W "$TIMEOUT" "${to_npub}.fips" > /dev/null 2>&1; then
|
||||
echo "OK"
|
||||
PASSED=$((PASSED + 1))
|
||||
else
|
||||
echo "FAIL"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
echo "=== FIPS Ping Test ($PROFILE topology) ==="
|
||||
echo ""
|
||||
|
||||
# Wait for nodes to converge
|
||||
echo "Waiting 5s for mesh convergence..."
|
||||
sleep 5
|
||||
|
||||
if [ "$PROFILE" = "mesh" ]; then
|
||||
# Sparse mesh topology: A-B, B-C, C-D, D-E, E-A, A-D
|
||||
# Test all 20 directed pairs (5 nodes × 4 targets each)
|
||||
echo ""
|
||||
echo "From node-a:"
|
||||
ping_test node-a "$NPUB_B" "A → B"
|
||||
ping_test node-a "$NPUB_C" "A → C"
|
||||
ping_test node-a "$NPUB_D" "A → D"
|
||||
ping_test node-a "$NPUB_E" "A → E"
|
||||
|
||||
echo ""
|
||||
echo "From node-b:"
|
||||
ping_test node-b "$NPUB_A" "B → A"
|
||||
ping_test node-b "$NPUB_C" "B → C"
|
||||
ping_test node-b "$NPUB_D" "B → D"
|
||||
ping_test node-b "$NPUB_E" "B → E"
|
||||
|
||||
echo ""
|
||||
echo "From node-c:"
|
||||
ping_test node-c "$NPUB_A" "C → A"
|
||||
ping_test node-c "$NPUB_B" "C → B"
|
||||
ping_test node-c "$NPUB_D" "C → D"
|
||||
ping_test node-c "$NPUB_E" "C → E"
|
||||
|
||||
echo ""
|
||||
echo "From node-d:"
|
||||
ping_test node-d "$NPUB_A" "D → A"
|
||||
ping_test node-d "$NPUB_B" "D → B"
|
||||
ping_test node-d "$NPUB_C" "D → C"
|
||||
ping_test node-d "$NPUB_E" "D → E"
|
||||
|
||||
echo ""
|
||||
echo "From node-e:"
|
||||
ping_test node-e "$NPUB_A" "E → A"
|
||||
ping_test node-e "$NPUB_B" "E → B"
|
||||
ping_test node-e "$NPUB_C" "E → C"
|
||||
ping_test node-e "$NPUB_D" "E → D"
|
||||
|
||||
elif [ "$PROFILE" = "chain" ]; then
|
||||
echo ""
|
||||
echo "Adjacent peer tests:"
|
||||
ping_test node-a "$NPUB_B" "A → B (1 hop)"
|
||||
ping_test node-b "$NPUB_C" "B → C (1 hop)"
|
||||
|
||||
echo ""
|
||||
echo "Multi-hop tests:"
|
||||
ping_test node-a "$NPUB_C" "A → C (2 hops)"
|
||||
ping_test node-a "$NPUB_D" "A → D (3 hops)"
|
||||
ping_test node-a "$NPUB_E" "A → E (4 hops)"
|
||||
|
||||
echo ""
|
||||
echo "Reverse multi-hop:"
|
||||
ping_test node-e "$NPUB_A" "E → A (4 hops)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Results: $PASSED passed, $FAILED failed ==="
|
||||
[ "$FAILED" -eq 0 ] && exit 0 || exit 1
|
||||
@@ -1,258 +0,0 @@
|
||||
# Two-Node UDP Test
|
||||
|
||||
This example demonstrates two FIPS nodes communicating over UDP using Linux
|
||||
network namespaces. Both nodes establish an encrypted peer link, build a
|
||||
spanning tree, and create end-to-end sessions. With TUN and DNS enabled, you
|
||||
can `ping6` between nodes using raw IPv6 addresses.
|
||||
|
||||
## Network Diagram
|
||||
|
||||

|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Linux with network namespace support (requires root for namespace setup)
|
||||
- IPv6 enabled (`sysctl net.ipv6.conf.all.disable_ipv6` should be `0`)
|
||||
- `iproute2` tools (`ip`), `dig` (from `dnsutils` or `bind-utils`)
|
||||
- Rust toolchain (to build the FIPS binary)
|
||||
|
||||
## Node Identities
|
||||
|
||||
| Node | npub | FIPS Address |
|
||||
|------|------|-------------|
|
||||
| A | `npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m` | `fd69:e08d:65cc:3a6b:9c2c:2ac4:bd40:5e4b` |
|
||||
| B | `npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le` | `fd8e:302c:287e:b48d:6268:122f:da76:b77` |
|
||||
|
||||
## Step 1: Build FIPS
|
||||
|
||||
From the FIPS source root:
|
||||
|
||||
```bash
|
||||
cargo build
|
||||
```
|
||||
|
||||
The binary will be at `target/debug/fips`. Note the absolute path — you'll
|
||||
need it when running inside namespaces.
|
||||
|
||||
## Step 2: Create Network Namespaces
|
||||
|
||||
This creates two namespaces (`fips-a` and `fips-b`) connected by a virtual
|
||||
ethernet pair. Each namespace has its own isolated network stack, including
|
||||
its own routing table, TUN devices, and DNS resolver.
|
||||
|
||||
```bash
|
||||
# Create namespaces
|
||||
sudo ip netns add fips-a
|
||||
sudo ip netns add fips-b
|
||||
|
||||
# Create a veth pair connecting them
|
||||
sudo ip link add veth-a type veth peer name veth-b
|
||||
|
||||
# Move each end into its namespace
|
||||
sudo ip link set veth-a netns fips-a
|
||||
sudo ip link set veth-b netns fips-b
|
||||
|
||||
# Configure IPv4 addresses (used by UDP transport)
|
||||
sudo ip netns exec fips-a ip addr add 10.0.0.1/24 dev veth-a
|
||||
sudo ip netns exec fips-b ip addr add 10.0.0.2/24 dev veth-b
|
||||
|
||||
# Bring interfaces up
|
||||
sudo ip netns exec fips-a ip link set veth-a up
|
||||
sudo ip netns exec fips-b ip link set veth-b up
|
||||
|
||||
# Enable loopback in both (needed for DNS responder on 127.0.0.1)
|
||||
sudo ip netns exec fips-a ip link set lo up
|
||||
sudo ip netns exec fips-b ip link set lo up
|
||||
|
||||
# Enable IPv6 in both namespaces
|
||||
sudo ip netns exec fips-a sysctl -w net.ipv6.conf.all.disable_ipv6=0
|
||||
sudo ip netns exec fips-b sysctl -w net.ipv6.conf.all.disable_ipv6=0
|
||||
```
|
||||
|
||||
Verify connectivity between namespaces:
|
||||
|
||||
```bash
|
||||
sudo ip netns exec fips-a ping -c 1 10.0.0.2
|
||||
```
|
||||
|
||||
## Step 3: Start Node A
|
||||
|
||||
Open **Terminal 1**. This runs the FIPS daemon for Node A inside its
|
||||
namespace. The daemon creates a `fips0` TUN device, starts the DNS responder,
|
||||
connects to Node B over UDP, and begins the Noise IK handshake.
|
||||
|
||||
```bash
|
||||
sudo ip netns exec fips-a \
|
||||
env RUST_LOG=info \
|
||||
/path/to/target/debug/fips --config /path/to/examples/two-node-udp/fips-a.yaml
|
||||
```
|
||||
|
||||
Replace `/path/to/` with the actual absolute paths to your build and this
|
||||
example directory.
|
||||
|
||||
You should see output like:
|
||||
|
||||
```text
|
||||
INFO fips: FIPS starting
|
||||
INFO fips: Node created:
|
||||
INFO fips: npub: npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m
|
||||
INFO fips: address: fd69:e08d:65cc:3a6b:9c2c:2ac4:bd40:5e4b
|
||||
INFO fips: TUN device active:
|
||||
INFO fips: name: fips0
|
||||
INFO fips: address: fd69:e08d:65cc:3a6b:9c2c:2ac4:bd40:5e4b
|
||||
INFO fips: DNS responder started for .fips domain
|
||||
INFO fips: Peer connection initiated (node-b)
|
||||
```
|
||||
|
||||
## Step 4: Start Node B
|
||||
|
||||
Open **Terminal 2**. Run Node B in its namespace:
|
||||
|
||||
```bash
|
||||
sudo ip netns exec fips-b \
|
||||
env RUST_LOG=info \
|
||||
/path/to/target/debug/fips --config /path/to/examples/two-node-udp/fips-b.yaml
|
||||
```
|
||||
|
||||
Once both nodes are running, you should see handshake completion messages in
|
||||
both terminals:
|
||||
|
||||
```text
|
||||
INFO fips::node::handlers::handshake: Peer promoted to active
|
||||
```
|
||||
|
||||
The spanning tree will converge within a few seconds (TreeAnnounce exchange),
|
||||
followed by bloom filter exchange (FilterAnnounce).
|
||||
|
||||
## Step 5: Test DNS Resolution
|
||||
|
||||
The FIPS daemon includes a DNS responder that resolves `<npub>.fips` queries
|
||||
to FIPS IPv6 addresses. Open **Terminal 3** to test it.
|
||||
|
||||
Query the DNS responder directly with `dig`:
|
||||
|
||||
```bash
|
||||
# From Node A, resolve Node B's name
|
||||
sudo ip netns exec fips-a dig @127.0.0.1 -p 5354 AAAA \
|
||||
npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le.fips
|
||||
|
||||
# Expected answer: fd8e:302c:287e:b48d:6268:122f:da76:b77
|
||||
```
|
||||
|
||||
Watch Terminal 1 — you should see a log line (requires `RUST_LOG=debug`):
|
||||
|
||||
```text
|
||||
DEBUG fips::dns: DNS resolved .fips name, registering identity
|
||||
```
|
||||
|
||||
This confirms the identity cache was populated. The subsequent ping will be
|
||||
able to route through the mesh.
|
||||
|
||||
> **Note:** `resolvectl` and system resolver integration (e.g., `ping6
|
||||
> npub1...fips`) do not work inside network namespaces because
|
||||
> `systemd-resolved` runs in the host namespace and is not accessible from
|
||||
> within isolated namespaces. Use `dig @127.0.0.1 -p 5354` to query the
|
||||
> DNS responder directly, and use raw IPv6 addresses for `ping6`. System
|
||||
> resolver integration works when FIPS runs in the host namespace or with
|
||||
> the future D-Bus auto-registration (Phase 2).
|
||||
|
||||
## Step 6: Ping Between Nodes
|
||||
|
||||
Now test end-to-end connectivity. The first ping triggers session
|
||||
establishment (Noise IK handshake through the mesh), so it may take a moment
|
||||
longer than subsequent pings.
|
||||
|
||||
### Ping Node B from Node A
|
||||
|
||||
From Terminal 3:
|
||||
|
||||
```bash
|
||||
sudo ip netns exec fips-a ping6 -c 4 fd8e:302c:287e:b48d:6268:122f:da76:b77
|
||||
```
|
||||
|
||||
### Ping Node A from Node B
|
||||
|
||||
```bash
|
||||
sudo ip netns exec fips-b ping6 -c 4 fd69:e08d:65cc:3a6b:9c2c:2ac4:bd40:5e4b
|
||||
```
|
||||
|
||||
Ping replies are handled by the kernel's IPv6 stack — when a ping arrives
|
||||
at the destination's TUN device, the kernel sees it addressed to its own
|
||||
`fips0` address and replies natively. FIPS handles the encrypted transport
|
||||
between nodes; the kernel handles ICMPv6 Echo Reply.
|
||||
|
||||
## Step 7: Watch the Logs
|
||||
|
||||
While pinging, watch the daemon terminals for the protocol flow:
|
||||
|
||||
1. **DNS resolution** — `DNS resolved .fips name, registering identity`
|
||||
2. **TUN packet** — `TUN packet received` with src/dst addresses
|
||||
3. **Session initiation** — `Initiating session to <node_addr>`
|
||||
4. **SessionSetup sent** — Noise IK msg1 sent through mesh
|
||||
5. **SessionSetup received** — Responder processes msg1
|
||||
6. **SessionAck** — Responder sends msg2 back
|
||||
7. **Session established** — Both sides transition to Established
|
||||
8. **DataPacket** — Encrypted IPv6 payload delivered
|
||||
|
||||
Set `RUST_LOG=debug` for the full protocol trace, or `RUST_LOG=info` for
|
||||
high-level events only.
|
||||
|
||||
## Cleanup
|
||||
|
||||
Stop both FIPS daemons with Ctrl+C in Terminals 1 and 2. Then tear down
|
||||
the namespaces:
|
||||
|
||||
```bash
|
||||
sudo ip netns delete fips-a
|
||||
sudo ip netns delete fips-b
|
||||
```
|
||||
|
||||
This also removes the veth pair and TUN devices automatically.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Permission denied" creating TUN device
|
||||
|
||||
The FIPS binary needs `CAP_NET_ADMIN` to create TUN devices. Running via
|
||||
`sudo ip netns exec` already provides root privileges. If running outside
|
||||
a namespace, use:
|
||||
|
||||
```bash
|
||||
sudo setcap cap_net_admin+ep /path/to/target/debug/fips
|
||||
```
|
||||
|
||||
### "Address already in use" on DNS port
|
||||
|
||||
Another process is using port 5354. Change the `dns.port` in the YAML config
|
||||
to a different port (e.g., 5355).
|
||||
|
||||
### No handshake completion
|
||||
|
||||
Check that the veth pair is up and the namespaces can reach each other:
|
||||
|
||||
```bash
|
||||
sudo ip netns exec fips-a ping -c 1 10.0.0.2
|
||||
```
|
||||
|
||||
If this fails, the namespace setup is incomplete.
|
||||
|
||||
### IPv6 disabled
|
||||
|
||||
```bash
|
||||
sudo ip netns exec fips-a sysctl net.ipv6.conf.all.disable_ipv6
|
||||
# Should be 0
|
||||
```
|
||||
|
||||
### DNS query returns no answer
|
||||
|
||||
Verify the DNS responder is running by querying it directly:
|
||||
|
||||
```bash
|
||||
sudo ip netns exec fips-a dig @127.0.0.1 -p 5354 AAAA \
|
||||
npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le.fips
|
||||
```
|
||||
|
||||
If `dig` works but `resolvectl query` or `ping6 <npub>.fips` doesn't,
|
||||
this is expected — `systemd-resolved` runs in the host namespace and is
|
||||
not accessible from inside network namespaces. Use raw IPv6 addresses
|
||||
for testing within namespaces.
|
||||
@@ -1,110 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 420" font-family="monospace" font-size="12">
|
||||
<style>
|
||||
rect.ns { fill: #151525; stroke: #505070; stroke-width: 2; rx: 6; }
|
||||
rect.node { fill: #1a3a2a; stroke: #40a060; stroke-width: 1.5; rx: 4; }
|
||||
rect.iface { fill: #1a2a3a; stroke: #4080c0; stroke-width: 1; rx: 3; }
|
||||
rect.veth { fill: #2a1a1a; stroke: #c06040; stroke-width: 1; rx: 3; }
|
||||
text { fill: #e0e0e0; }
|
||||
text.title { font-size: 16px; font-weight: bold; }
|
||||
text.subtitle { font-size: 12px; fill: #909090; }
|
||||
text.ns-label { font-size: 12px; fill: #909090; font-style: italic; }
|
||||
text.label { font-size: 13px; font-weight: bold; }
|
||||
text.detail { font-size: 10px; fill: #909090; }
|
||||
text.addr { font-size: 10px; fill: #70a0d0; }
|
||||
text.note { font-size: 10px; fill: #c0c0c0; }
|
||||
line.udp { stroke: #c06040; stroke-width: 2; }
|
||||
line.mesh { stroke: #d0a040; stroke-width: 2; stroke-dasharray: 6,3; }
|
||||
line.conn { stroke: #606080; stroke-width: 1.5; }
|
||||
</style>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="800" height="420" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="400" y="28" text-anchor="middle" class="title">Two-Node UDP Example</text>
|
||||
<text x="400" y="46" text-anchor="middle" class="subtitle">Network namespaces with veth pair transport</text>
|
||||
|
||||
<!-- ═══ Namespace A ═══ -->
|
||||
<rect x="30" y="64" width="340" height="270" class="ns"/>
|
||||
<text x="200" y="82" text-anchor="middle" class="ns-label">Namespace: fips-a</text>
|
||||
|
||||
<!-- FIPS Node A -->
|
||||
<rect x="70" y="94" width="260" height="62" class="node"/>
|
||||
<text x="200" y="114" text-anchor="middle" class="label">FIPS Node A</text>
|
||||
<text x="200" y="130" text-anchor="middle" class="addr">fd69:e08d:65cc:3a6b:9c2c:2ac4:bd40:5e4b</text>
|
||||
<text x="200" y="148" text-anchor="middle" class="detail">npub1sjlh2c...awh57m</text>
|
||||
|
||||
<!-- fips0 TUN -->
|
||||
<rect x="70" y="176" width="120" height="44" class="iface"/>
|
||||
<text x="130" y="196" text-anchor="middle" class="label">fips0</text>
|
||||
<text x="130" y="210" text-anchor="middle" class="detail">TUN device</text>
|
||||
|
||||
<!-- DNS -->
|
||||
<rect x="210" y="176" width="120" height="44" class="iface"/>
|
||||
<text x="270" y="196" text-anchor="middle" class="label">DNS</text>
|
||||
<text x="270" y="210" text-anchor="middle" class="detail">127.0.0.1:5354</text>
|
||||
|
||||
<!-- Connections: Node → fips0, Node → DNS -->
|
||||
<line x1="130" y1="156" x2="130" y2="176" class="conn"/>
|
||||
<line x1="270" y1="156" x2="270" y2="176" class="conn"/>
|
||||
|
||||
<!-- veth-a -->
|
||||
<rect x="70" y="250" width="260" height="44" class="veth"/>
|
||||
<text x="200" y="268" text-anchor="middle" class="label">veth-a</text>
|
||||
<text x="200" y="282" text-anchor="middle" class="addr">10.0.0.1/24</text>
|
||||
|
||||
<!-- Node → veth connection -->
|
||||
<line x1="200" y1="156" x2="200" y2="250" class="conn"/>
|
||||
|
||||
<!-- ═══ Namespace B ═══ -->
|
||||
<rect x="430" y="64" width="340" height="270" class="ns"/>
|
||||
<text x="600" y="82" text-anchor="middle" class="ns-label">Namespace: fips-b</text>
|
||||
|
||||
<!-- FIPS Node B -->
|
||||
<rect x="470" y="94" width="260" height="62" class="node"/>
|
||||
<text x="600" y="114" text-anchor="middle" class="label">FIPS Node B</text>
|
||||
<text x="600" y="130" text-anchor="middle" class="addr">fd8e:302c:287e:b48d:6268:122f:da76:0b77</text>
|
||||
<text x="600" y="148" text-anchor="middle" class="detail">npub1tdwa4v...d84le</text>
|
||||
|
||||
<!-- fips0 TUN -->
|
||||
<rect x="470" y="176" width="120" height="44" class="iface"/>
|
||||
<text x="530" y="196" text-anchor="middle" class="label">fips0</text>
|
||||
<text x="530" y="210" text-anchor="middle" class="detail">TUN device</text>
|
||||
|
||||
<!-- DNS -->
|
||||
<rect x="610" y="176" width="120" height="44" class="iface"/>
|
||||
<text x="670" y="196" text-anchor="middle" class="label">DNS</text>
|
||||
<text x="670" y="210" text-anchor="middle" class="detail">127.0.0.1:5354</text>
|
||||
|
||||
<!-- Connections: Node → fips0, Node → DNS -->
|
||||
<line x1="530" y1="156" x2="530" y2="176" class="conn"/>
|
||||
<line x1="670" y1="156" x2="670" y2="176" class="conn"/>
|
||||
|
||||
<!-- veth-b -->
|
||||
<rect x="470" y="250" width="260" height="44" class="veth"/>
|
||||
<text x="600" y="268" text-anchor="middle" class="label">veth-b</text>
|
||||
<text x="600" y="282" text-anchor="middle" class="addr">10.0.0.2/24</text>
|
||||
|
||||
<!-- Node → veth connection -->
|
||||
<line x1="600" y1="156" x2="600" y2="250" class="conn"/>
|
||||
|
||||
<!-- ═══ UDP link between veths ═══ -->
|
||||
<line x1="330" y1="272" x2="470" y2="272" class="udp"/>
|
||||
<text x="400" y="264" text-anchor="middle" font-size="11" font-weight="bold" fill="#c06040">UDP :4000</text>
|
||||
|
||||
<!-- ═══ Mesh session (dashed gold) ═══ -->
|
||||
<line x1="330" y1="125" x2="470" y2="125" class="mesh"/>
|
||||
<text x="400" y="118" text-anchor="middle" font-size="11" font-weight="bold" fill="#d0a040">Encrypted session</text>
|
||||
|
||||
<!-- ═══ Legend ═══ -->
|
||||
<rect x="30" y="352" width="740" height="56" fill="#151520" stroke="#404060" stroke-width="1" rx="4"/>
|
||||
|
||||
<line x1="50" y1="374" x2="90" y2="374" class="udp"/>
|
||||
<text x="96" y="378" class="note">Transport layer: UDP over IPv4 veth pair</text>
|
||||
|
||||
<line x1="50" y1="394" x2="90" y2="394" class="mesh"/>
|
||||
<text x="96" y="398" class="note">Data plane: IPv6 over FIPS mesh (fd::/8 via fips0 TUN)</text>
|
||||
|
||||
<rect x="450" y="366" width="14" height="14" class="iface"/>
|
||||
<text x="470" y="378" class="note">DNS: <npub>.fips → FIPS IPv6 address</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.3 KiB |
+2
-5
@@ -806,16 +806,13 @@ impl Node {
|
||||
// === End-to-End Sessions ===
|
||||
|
||||
/// Get a session by remote NodeAddr.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn get_session(&self, remote: &NodeAddr) -> Option<&SessionEntry> {
|
||||
self.sessions.get(remote)
|
||||
}
|
||||
|
||||
/// Get a mutable session by remote NodeAddr.
|
||||
pub(crate) fn get_session_mut(&mut self, remote: &NodeAddr) -> Option<&mut SessionEntry> {
|
||||
self.sessions.get_mut(remote)
|
||||
}
|
||||
|
||||
/// Remove a session.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn remove_session(&mut self, remote: &NodeAddr) -> Option<SessionEntry> {
|
||||
self.sessions.remove(remote)
|
||||
}
|
||||
|
||||
+5
-10
@@ -42,12 +42,15 @@ impl EndToEndState {
|
||||
/// The state is `None` only transiently during handler processing.
|
||||
pub(crate) struct SessionEntry {
|
||||
/// Remote node's address (session table key).
|
||||
#[allow(dead_code)]
|
||||
remote_addr: NodeAddr,
|
||||
/// Remote node's static public key (for Noise IK).
|
||||
#[allow(dead_code)]
|
||||
remote_pubkey: PublicKey,
|
||||
/// Current session state. `None` only during state transitions.
|
||||
state: Option<EndToEndState>,
|
||||
/// When the session was created (Unix milliseconds).
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
created_at: u64,
|
||||
/// Last activity timestamp (Unix milliseconds).
|
||||
last_activity: u64,
|
||||
@@ -70,16 +73,6 @@ impl SessionEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the remote node address.
|
||||
pub(crate) fn remote_addr(&self) -> &NodeAddr {
|
||||
&self.remote_addr
|
||||
}
|
||||
|
||||
/// Get the remote node's public key.
|
||||
pub(crate) fn remote_pubkey(&self) -> &PublicKey {
|
||||
&self.remote_pubkey
|
||||
}
|
||||
|
||||
/// Get the current session state.
|
||||
pub(crate) fn state(&self) -> &EndToEndState {
|
||||
self.state.as_ref().expect("session state taken but not restored")
|
||||
@@ -114,11 +107,13 @@ impl SessionEntry {
|
||||
}
|
||||
|
||||
/// Get creation time.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn created_at(&self) -> u64 {
|
||||
self.created_at
|
||||
}
|
||||
|
||||
/// Get last activity time.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn last_activity(&self) -> u64 {
|
||||
self.last_activity
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user