# 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.