Remove the last shell-owned NAT surface by extracting the iptables logic into a shared Go package and using it from both bangerd and a hidden helper bridge in the CLI. Route customize.sh and interactive.sh through banger internal nat up/down so the remaining shell helpers reuse the same rule logic, resolve the local banger binary explicitly, and tear NAT back down during cleanup. Drop nat.sh from the runtime bundle and docs now that NAT is Go-managed everywhere, and keep coverage aligned with the new shared package and helper command. Validation: go test ./..., bash -n customize.sh interactive.sh verify.sh, make build, and a live ./verify.sh --nat run that installed host rules, reached outbound network access, and cleaned them up successfully.
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)
|
|
}
|
|
}
|