Fix the Go control plane NAT path now that runtime state lives in the daemon instead of the old repo-local vm.json files. Add a daemon-native NAT helper that derives uplink, guest IP, and TAP rules directly from VMRecord, applies the existing iptables/sysctl behavior idempotently, and removes the broken nat.sh handoff from vm.go. Cover uplink parsing and rule generation with unit tests. Validated with go test ./... and make build; a live verify.sh --nat run installed host rules but stopped on the same guest SSH-readiness issue seen in the plain smoke test on this host.
129 lines
3.6 KiB
Go
129 lines
3.6 KiB
Go
package daemon
|
|
|
|
import (
|
|
"slices"
|
|
"testing"
|
|
|
|
"banger/internal/model"
|
|
)
|
|
|
|
func TestParseDefaultUplink(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
output := "default via 192.168.1.1 dev enp5s0 proto dhcp src 192.168.1.40 metric 100\n"
|
|
uplink, err := parseDefaultUplink(output)
|
|
if err != nil {
|
|
t.Fatalf("parseDefaultUplink returned error: %v", err)
|
|
}
|
|
if uplink != "enp5s0" {
|
|
t.Fatalf("uplink = %q, want enp5s0", uplink)
|
|
}
|
|
}
|
|
|
|
func TestParseDefaultUplinkFailsWithoutRoute(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if _, err := parseDefaultUplink("10.0.0.0/24 dev br-fc proto kernel scope link src 10.0.0.1\n"); err == nil {
|
|
t.Fatal("expected parseDefaultUplink to fail without a default route")
|
|
}
|
|
}
|
|
|
|
func TestNATRulesForVM(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
vm := model.VMRecord{
|
|
Runtime: model.VMRuntime{
|
|
GuestIP: "172.16.0.8",
|
|
TapDevice: "tap-fc-abcd1234",
|
|
},
|
|
}
|
|
rules, err := natRulesForVM(vm, "wlan0")
|
|
if err != nil {
|
|
t.Fatalf("natRulesForVM returned error: %v", err)
|
|
}
|
|
if len(rules) != 3 {
|
|
t.Fatalf("rule count = %d, want 3", len(rules))
|
|
}
|
|
if got, want := natRuleArgs("-A", rules[0]), []string{"-t", "nat", "-A", "POSTROUTING", "-s", "172.16.0.8/32", "-o", "wlan0", "-j", "MASQUERADE"}; !slices.Equal(got, want) {
|
|
t.Fatalf("postrouting args = %v, want %v", got, want)
|
|
}
|
|
if got, want := natRuleArgs("-A", rules[1]), []string{"-A", "FORWARD", "-i", "tap-fc-abcd1234", "-o", "wlan0", "-j", "ACCEPT"}; !slices.Equal(got, want) {
|
|
t.Fatalf("forward-out args = %v, want %v", got, want)
|
|
}
|
|
if got, want := natRuleArgs("-A", rules[2]), []string{"-A", "FORWARD", "-i", "wlan0", "-o", "tap-fc-abcd1234", "-m", "state", "--state", "RELATED,ESTABLISHED", "-j", "ACCEPT"}; !slices.Equal(got, want) {
|
|
t.Fatalf("forward-in args = %v, want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestNATRulesForVMRequiresRuntimeData(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
vm model.VMRecord
|
|
uplink string
|
|
}{
|
|
{
|
|
name: "guest ip",
|
|
vm: model.VMRecord{
|
|
Runtime: model.VMRuntime{TapDevice: "tap-fc-abcd1234"},
|
|
},
|
|
uplink: "eth0",
|
|
},
|
|
{
|
|
name: "tap",
|
|
vm: model.VMRecord{
|
|
Runtime: model.VMRuntime{GuestIP: "172.16.0.8"},
|
|
},
|
|
uplink: "eth0",
|
|
},
|
|
{
|
|
name: "uplink",
|
|
vm: model.VMRecord{
|
|
Runtime: model.VMRuntime{
|
|
GuestIP: "172.16.0.8",
|
|
TapDevice: "tap-fc-abcd1234",
|
|
},
|
|
},
|
|
uplink: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if _, err := natRulesForVM(tt.vm, tt.uplink); err == nil {
|
|
t.Fatalf("expected natRulesForVM to fail for missing %s", tt.name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNATPlans(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rules := []natRule{
|
|
{table: "nat", chain: "POSTROUTING", args: []string{"-s", "172.16.0.8/32", "-o", "eth0", "-j", "MASQUERADE"}},
|
|
{chain: "FORWARD", args: []string{"-i", "tap-fc-abcd1234", "-o", "eth0", "-j", "ACCEPT"}},
|
|
}
|
|
|
|
addPlan := natAddPlan(rules)
|
|
if len(addPlan) != 3 {
|
|
t.Fatalf("addPlan count = %d, want 3", len(addPlan))
|
|
}
|
|
if got, want := addPlan[0], []string{"sysctl", "-w", "net.ipv4.ip_forward=1"}; !slices.Equal(got, want) {
|
|
t.Fatalf("sysctl command = %v, want %v", got, want)
|
|
}
|
|
if got, want := addPlan[1], []string{"-t", "nat", "-A", "POSTROUTING", "-s", "172.16.0.8/32", "-o", "eth0", "-j", "MASQUERADE"}; !slices.Equal(got, want) {
|
|
t.Fatalf("add NAT command = %v, want %v", got, want)
|
|
}
|
|
|
|
removePlan := natRemovePlan(rules)
|
|
if len(removePlan) != 2 {
|
|
t.Fatalf("removePlan count = %d, want 2", len(removePlan))
|
|
}
|
|
if got, want := removePlan[0], []string{"-t", "nat", "-D", "POSTROUTING", "-s", "172.16.0.8/32", "-o", "eth0", "-j", "MASQUERADE"}; !slices.Equal(got, want) {
|
|
t.Fatalf("remove NAT command = %v, want %v", got, want)
|
|
}
|
|
}
|