mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 16:24:45 +00:00
Add fd00::/8 route to TUN configure_interface() via rtnetlink - without
this route the kernel had no path to FIPS addresses ("network unreachable").
Update two-node-udp README based on live namespace testing:
- Remove resolvectl steps (doesn't work inside namespaces)
- Remove ICMPv6 Echo Reply mention (kernel handles it natively)
- Add namespace DNS limitation note and kernel ping reply explanation
- Simplify from 8 to 7 steps
283 lines
10 KiB
Markdown
283 lines
10 KiB
Markdown
# 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
|
|
|
|
```text
|
|
Namespace: fips-a Namespace: fips-b
|
|
┌─────────────────────┐ ┌─────────────────────┐
|
|
│ │ │ │
|
|
│ ┌───────────────┐ │ │ ┌───────────────┐ │
|
|
│ │ FIPS Node A │ │ │ │ FIPS Node B │ │
|
|
│ │ │ │ │ │ │ │
|
|
│ │ fd69:e08d:.. │ │ │ │ fd8e:302c:.. │ │
|
|
│ └──┬─────────┬──┘ │ │ └──┬─────────┬──┘ │
|
|
│ │ │ │ │ │ │ │
|
|
│ ┌──┴──┐ ┌───┴──┐ │ │ ┌──┴──┐ ┌───┴──┐ │
|
|
│ │fips0│ │ DNS │ │ │ │fips0│ │ DNS │ │
|
|
│ │ TUN │ │:5354 │ │ │ │ TUN │ │:5354 │ │
|
|
│ └─────┘ └──────┘ │ │ └─────┘ └──────┘ │
|
|
│ │ │ │
|
|
│ ┌────────────────┐ │ │ ┌────────────────┐ │
|
|
│ │ veth-a │ │ UDP :4000 │ │ veth-b │ │
|
|
│ │ 10.0.0.1/24 ├─┼──────────────┼─┤ 10.0.0.2/24 │ │
|
|
│ └────────────────┘ │ │ └────────────────┘ │
|
|
└─────────────────────┘ └─────────────────────┘
|
|
|
|
Transport layer: UDP over IPv4 veth pair (10.0.0.0/24)
|
|
Data plane: IPv6 over FIPS mesh (fd::/8 via fips0 TUN)
|
|
DNS: <npub>.fips → FIPS IPv6 address (127.0.0.1:5354)
|
|
```
|
|
|
|
## 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.
|