WIP: rebase for 2026-05-18 #7

Draft
codinget wants to merge 234 commits from rebase/2026-05-18 into webnet
2 changed files with 157 additions and 0 deletions
Showing only changes of commit d0ae993334 - Show all commits
+17
View File
@@ -426,6 +426,23 @@ func (e *Env) SetExitNode(client, exitNode *Node) {
}
}
// SetAcceptRoutes toggles the node's RouteAll preference (the
// --accept-routes flag), controlling whether it installs subnet routes
// advertised by peers.
func (e *Env) SetAcceptRoutes(n *Node, on bool) {
e.t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if _, err := n.agent.EditPrefs(ctx, &ipn.MaskedPrefs{
Prefs: ipn.Prefs{RouteAll: on},
RouteAllSet: true,
}); err != nil {
e.t.Fatalf("SetAcceptRoutes(%s, %v): %v", n.name, on, err)
}
e.t.Logf("[%s] accept-routes=%v", n.name, on)
}
// ApproveRoutes tells the test control server to approve subnet routes
// for the given node. The routes should be CIDR strings.
func (e *Env) ApproveRoutes(n *Node, routes ...string) {
+140
View File
@@ -163,6 +163,146 @@ func TestInterNetworkTCP(t *testing.T) {
}
}
// TestSubnetRouterPublicIP verifies that toggling --accept-routes on the
// client switches between dialing a webserver directly and routing through a
// subnet router that advertises the webserver's public IP range.
//
// Topology: client, subnet router, and webserver each live behind their own
// NAT'd network with distinct WAN IPs; the subnet router advertises the
// webserver's network as a route. The webserver echoes the source IP it
// sees:
// - accept-routes=off: client dials webserver directly; source is client's WAN.
// - accept-routes=on: client tunnels to the subnet router, which forwards
// and SNATs; source is subnet router's WAN.
func TestSubnetRouterPublicIP(t *testing.T) {
env := vmtest.New(t)
const (
clientWAN = "1.0.0.1"
routerWAN = "2.0.0.1"
webWAN = "5.0.0.1"
webRoute = "5.0.0.0/24"
)
clientNet := env.AddNetwork(clientWAN, "192.168.1.1/24", vnet.EasyNAT)
routerNet := env.AddNetwork(routerWAN, "192.168.2.1/24", vnet.EasyNAT)
webNet := env.AddNetwork(webWAN, "192.168.5.1/24", vnet.One2OneNAT)
client := env.AddNode("client", clientNet,
vmtest.OS(vmtest.Gokrazy))
sr := env.AddNode("subnet-router", routerNet,
vmtest.OS(vmtest.Gokrazy),
vmtest.AdvertiseRoutes(webRoute))
env.AddNode("webserver", webNet,
vmtest.OS(vmtest.Gokrazy),
vmtest.DontJoinTailnet(),
vmtest.WebServer(8080))
env.Start()
// ApproveRoutes also turns on RouteAll on the client.
env.ApproveRoutes(sr, webRoute)
webURL := fmt.Sprintf("http://%s:8080/", webWAN)
check := func(label, wantSrc string) {
t.Helper()
body := env.HTTPGet(client, webURL)
t.Logf("[%s] response: %s", label, body)
if !strings.Contains(body, "Hello world I am webserver") {
t.Fatalf("[%s] unexpected webserver response: %q", label, body)
}
if !strings.Contains(body, "from "+wantSrc) {
t.Fatalf("[%s] expected source %q in response, got %q", label, wantSrc, body)
}
}
// accept-routes=on (set by ApproveRoutes): traffic flows via the subnet router.
check("accept-routes=on", routerWAN)
// accept-routes=off: client dials the webserver directly.
env.SetAcceptRoutes(client, false)
check("accept-routes=off", clientWAN)
// Toggle back on to confirm the transition works in both directions.
env.SetAcceptRoutes(client, true)
check("accept-routes=on (again)", routerWAN)
}
// TestSubnetRouterAndExitNode checks how the subnet router and exit node
// preferences interact. Topology: client, subnet router, exit node, and
// webserver, each on its own NAT'd network with distinct WAN IPs. The subnet
// router advertises the webserver's network (5.0.0.0/24); the exit node
// advertises 0.0.0.0/0 + ::/0. The webserver echoes the source IP it sees:
//
// exit=off, subnet=off → client's WAN (direct dial)
// exit=off, subnet=on → subnet router's WAN
// exit=on, subnet=off → exit node's WAN
// exit=on, subnet=on → subnet router's WAN (more-specific /24 beats /0)
func TestSubnetRouterAndExitNode(t *testing.T) {
env := vmtest.New(t)
const (
clientWAN = "1.0.0.1"
routerWAN = "2.0.0.1"
exitWAN = "3.0.0.1"
webWAN = "5.0.0.1"
webRoute = "5.0.0.0/24"
)
clientNet := env.AddNetwork(clientWAN, "192.168.1.1/24", vnet.EasyNAT)
routerNet := env.AddNetwork(routerWAN, "192.168.2.1/24", vnet.EasyNAT)
exitNet := env.AddNetwork(exitWAN, "192.168.3.1/24", vnet.EasyNAT)
webNet := env.AddNetwork(webWAN, "192.168.5.1/24", vnet.One2OneNAT)
client := env.AddNode("client", clientNet,
vmtest.OS(vmtest.Gokrazy))
sr := env.AddNode("subnet-router", routerNet,
vmtest.OS(vmtest.Gokrazy),
vmtest.AdvertiseRoutes(webRoute))
exit := env.AddNode("exit", exitNet,
vmtest.OS(vmtest.Gokrazy),
vmtest.AdvertiseRoutes("0.0.0.0/0,::/0"))
env.AddNode("webserver", webNet,
vmtest.OS(vmtest.Gokrazy),
vmtest.DontJoinTailnet(),
vmtest.WebServer(8080))
env.Start()
env.ApproveRoutes(sr, webRoute)
env.ApproveRoutes(exit, "0.0.0.0/0", "::/0")
// Don't let the exit node itself forward via the subnet router: when the
// client is using the exit node only, we want the exit node to egress to
// the simulated internet directly so the webserver sees the exit's WAN.
env.SetAcceptRoutes(exit, false)
webURL := fmt.Sprintf("http://%s:8080/", webWAN)
tests := []struct {
name string // subtest name; describes (exit, subnet) toggles
exit *vmtest.Node
subnet bool
wantSrc string
}{
{"exit-off,subnet-off", nil, false, clientWAN},
{"exit-off,subnet-on", nil, true, routerWAN},
{"exit-on,subnet-off", exit, false, exitWAN},
// More-specific 5.0.0.0/24 from sr beats 0.0.0.0/0 from exit.
{"exit-on,subnet-on", exit, true, routerWAN},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
env.SetExitNode(client, tc.exit)
env.SetAcceptRoutes(client, tc.subnet)
body := env.HTTPGet(client, webURL)
t.Logf("response: %s", body)
if !strings.Contains(body, "Hello world I am webserver") {
t.Fatalf("unexpected webserver response: %q", body)
}
if !strings.Contains(body, "from "+tc.wantSrc) {
t.Fatalf("expected source %q in response, got %q", tc.wantSrc, body)
}
})
}
}
// TestExitNode verifies that switching the client's exit node setting between
// off, exit1, and exit2 correctly routes the client's internet traffic.
//