Files
boxmaker/docs/plans/M3b/07-toolkit-addr.md
T
kyleandClaude Opus 5.5 b426ca1958 Specify and plan M3b: the runner and the tools
A draft spec for the owner's review and 13 offline tasks with their given
tests: shared tool arguments and host rules in proto, the sealed fetch
target (M3a finding 14), the toolkit tools and SOCKS5 egress proxy, and
brokerd's [runner], podman argument lists, runtime and proxy lifecycle. Each
task's tests were run against a reference at that task's end state (560 to
638 tests, clippy clean); the reference is not in the repository. Adds the
runner-unavailable runbook entry and tip T23 (ETXTBSY in script tests).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-22 22:29:27 -07:00

3.4 KiB

M3b task 07: which addresses are public

Branch: m3b (run git switch m3b; git status --short must be empty, otherwise stop) Commit subject: toolkit: is_public, the addresses the egress proxy may reach

Goal

The egress proxy (task 08) connects only to public addresses. A grant allows a host on the internet; if its name resolves into the tailnet (100.64.0.0/10), the host (127.0.0.1) or a private network, the connection is refused even though the name is allowed. This task is the one pure function that decides it. Spec section 5, "Public".

Files

  • Copy: crates/toolkit/tests/addr.rs
  • Create: crates/toolkit/src/addr.rs
  • Modify: crates/toolkit/src/lib.rs (pub mod addr;), docs/implementer-log.md

Interface

/// True if `ip` is a public unicast address.
pub fn is_public(ip: std::net::IpAddr) -> bool;

Refused ranges: every one of these is not public

IPv4 (with [a, b, c, _] = ip.octets()):

Range Test
0.0.0.0/8 a == 0
10.0.0.0/8 a == 10
100.64.0.0/10 (the tailnet) a == 100 && (64..=127).contains(&b)
127.0.0.0/8 a == 127
169.254.0.0/16 a == 169 && b == 254
172.16.0.0/12 a == 172 && (16..=31).contains(&b)
192.0.0.0/24 a == 192 && b == 0 && c == 0
192.0.2.0/24 a == 192 && b == 0 && c == 2
192.168.0.0/16 a == 192 && b == 168
198.18.0.0/15 a == 198 && (b == 18 || b == 19)
198.51.100.0/24 a == 198 && b == 51 && c == 100
203.0.113.0/24 a == 203 && b == 0 && c == 113
224.0.0.0/4 and 240.0.0.0/4 a >= 224

IPv6 (with s = ip.segments(), eight u16s):

Range Test
::/96 (holds ::, ::1, and the old IPv4-compatible form) the first six segments are all 0
IPv4-mapped ::ffff:0:0/96 s[0..5] all 0 and s[5] == 0xffff: judge the last 32 bits as IPv4
NAT64 64:ff9b::/96 s[0..6] == [0x64, 0xff9b, 0, 0, 0, 0]: judge the last 32 bits as IPv4
fc00::/7 s[0] & 0xfe00 == 0xfc00
fe80::/10 s[0] & 0xffc0 == 0xfe80
ff00::/8 s[0] & 0xff00 == 0xff00
2001:db8::/32 s[0] == 0x2001 && s[1] == 0x0db8

Check the three "judge as IPv4" rows before the others. The last 32 bits as IPv4 are Ipv4Addr::from(((s[6] as u32) << 16) | s[7] as u32) in spirit, but no as casts: use u32::from(s[6]) and u32::from(s[7]), or s[6].to_be_bytes() and s[7].to_be_bytes().

Everything else is public. Do not use the standard library's is_global (unstable) or add ranges that are not in these tables: the test checks public neighbours just outside each range too.

Steps

  • 1. Copy. git switch m3b, then cp docs/plans/M3b/files/crates/toolkit/tests/addr.rs crates/toolkit/tests/
  • 2. See it fail. cargo test -p toolkit --test addr. Expected: it does not compile.
  • 3. Write addr.rs. Run cargo fmt --all.
  • 4. See it pass. cargo test -p toolkit --test addr. Expected: 4 passed.
  • 5. Walk the tables. Point at the line for each row of both tables.
  • 6. Run the gate. make gate. Expected last line: gate: ok.
  • 7. Log and commit. git add crates/toolkit docs/implementer-log.md && git commit

Done when

  • cargo test -p toolkit --test addr reports 4 passed; make gate prints gate: ok.

Stop and report if

  • A test's expectation disagrees with these tables.