Files
kyleandClaude Opus 5.5 5e55fe4c66 M3b plan: every task's git add includes Cargo.lock
Task 04 added dependencies to toolkit and its git add line left out the lock
file, so the driver stopped on an unclean tree. The lock change is folded into
task 04's commit.

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

83 lines
3.4 KiB
Markdown

# 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
```rust
/// 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 `u16`s):
| 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 Cargo.lock && 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.