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>
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, thencp 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. Runcargo 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 addrreports 4 passed;make gateprintsgate: ok.
Stop and report if
- A test's expectation disagrees with these tables.