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", }, } rules, err := natRulesForVM(vm, "tap-fc-abcd1234", "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 tap string uplink string }{ { name: "guest ip", vm: model.VMRecord{}, tap: "tap-fc-abcd1234", uplink: "eth0", }, { name: "tap", vm: model.VMRecord{Runtime: model.VMRuntime{GuestIP: "172.16.0.8"}}, tap: "", uplink: "eth0", }, { name: "uplink", vm: model.VMRecord{Runtime: model.VMRuntime{GuestIP: "172.16.0.8"}}, tap: "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.tap, 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) } }