import { test } from "node:test"; import assert from "node:assert/strict"; import { parseScenario } from "../src/declaration/parse.ts"; import { scenarioRoutesFor } from "../src/lifecycle/address.ts"; /** * The uplink and the declared gateway must not fight. * * **This is the one decision the registry's removal turned on, and it is invisible in a raise.** * Every machine that needs an image now has an `egress` uplink, and the uplink's DHCP offers a * default route. So did the scenario: a machine behind a household gateway defaulted through it, a * machine on a public segment defaulted through transit. Both of those are containers that reach * the scenario and nothing else — no route to the real internet, by design, because they exist to * reproduce a household router rather than to be one. * * A default route through either is therefore a black hole for anything outside, and it beats the * uplink's route on metric. The machine would sit failing every pull with a routing table that * looks perfectly reasonable. * * The answer is that an egress machine states the scenario's ranges explicitly and lets the uplink * be the default. These tests are how that is checked without spending an hour raising four nodes. */ const HOUSEHOLD = ` scenario: household segments: hosting: kind: public cidr: [192.0.2.0/24] home: kind: private cidr: [10.99.1.0/24] gateway: to: hosting address: [192.0.2.50] nat: [v4] forwardable: true machines: novox: at: { segment: hosting, address: [192.0.2.20] } egress: true ace: at: { segment: home, address: [10.99.1.10] } egress: true sealed: at: { segment: home, address: [10.99.1.99] } `; test("a machine behind a gateway still reaches the scenario through that gateway", () => { // The whole point of the topology: home→public is a masqueraded outbound path, and the overlay // handshake has to survive it. An egress machine that stopped using its gateway would be // testing a flat network with extra steps. const routes = scenarioRoutesFor(parseScenario(HOUSEHOLD), "ace"); assert.deepEqual(routes, [{ cidr: "192.0.2.0/24", via: "10.99.1.1" }]); }); test("a machine's own segment gets no route — it is already on-link", () => { const routes = scenarioRoutesFor(parseScenario(HOUSEHOLD), "ace"); assert.ok(!routes.some((r) => r.cidr === "10.99.1.0/24"), JSON.stringify(routes)); }); /** * **The dangerous one.** `home` is 10.99.1.0/24 — a documentation range in spirit, an ordinary * private one in fact, and very possibly the network the workstation itself is on. * * With one public segment there is no transit router, so novox has no path to `home` at all. Left * to fall through, that traffic would leave by the uplink and land on whatever the workstation can * reach. Unreachable is both the faithful reproduction of what it had before — a default route into * scenery that dropped it — and the only safe answer. */ test("a range with no path inside the scenario is unreachable, not leaked to the uplink", () => { const routes = scenarioRoutesFor(parseScenario(HOUSEHOLD), "novox"); assert.deepEqual(routes, [{ cidr: "10.99.1.0/24", via: null }]); }); test("a machine without egress is left to its default route, and states nothing", () => { // Not because it needs no routes — it has one, a default through its gateway, applied the old // way. This function is only asked about machines whose default belongs to the uplink. const scenario = parseScenario(HOUSEHOLD); assert.equal(scenario.machines["sealed"]?.egress, undefined); }); const TWO_PUBLIC = ` scenario: two-public segments: hosting: kind: public cidr: [192.0.2.0/24] elsewhere: kind: public cidr: [198.51.100.0/24] machines: anchor: at: { segment: hosting, address: [192.0.2.10] } egress: true `; test("with a second public segment the transit router is the way across, as it always was", () => { // Transit is raised only when there is more than one public segment, so this is exactly the // case where pointing at it means something. const routes = scenarioRoutesFor(parseScenario(TWO_PUBLIC), "anchor"); assert.deepEqual(routes, [{ cidr: "198.51.100.0/24", via: "192.0.2.254" }]); }); const V6 = ` scenario: both-families segments: hosting: kind: public cidr: [192.0.2.0/24, "2001:db8:a::/48"] elsewhere: kind: public cidr: [198.51.100.0/24, "2001:db8:b::/48"] machines: anchor: at: { segment: hosting, address: [192.0.2.10, "2001:db8:a::10"] } egress: true `; test("each family is routed through its own next hop", () => { // A v6 range routed via a v4 next hop is not a route, and the reverse is not either. const routes = scenarioRoutesFor(parseScenario(V6), "anchor"); assert.deepEqual(routes, [ { cidr: "198.51.100.0/24", via: "192.0.2.254" }, { cidr: "2001:db8:b::/48", via: "2001:db8:a::fffe" }, ]); });