package nftables import ( "strings" "testing" ) func TestNewManager_DefaultPath(t *testing.T) { m := NewManager("") if m.GetRulesPath() != defaultRulesPath { t.Errorf("got %q, want %q", m.GetRulesPath(), defaultRulesPath) } } func TestNewManager_CustomPath(t *testing.T) { m := NewManager("/tmp/wild-central.nft") if m.GetRulesPath() != "/tmp/wild-central.nft" { t.Errorf("got %q, want /tmp/wild-central.nft", m.GetRulesPath()) } } func TestPortsToStr(t *testing.T) { cases := []struct { ports []int want string }{ {[]int{80, 443, 8404}, "80, 443, 8404"}, {[]int{443}, "443"}, {[]int{}, ""}, } for _, c := range cases { if got := portsToStr(c.ports); got != c.want { t.Errorf("portsToStr(%v) = %q, want %q", c.ports, got, c.want) } } } func TestGenerate_AlwaysIncludesStructure(t *testing.T) { m := NewManager("") out := m.Generate([]int{80, 443}, nil, nil, "") for _, want := range []string{ "table inet wild-central", "set allowed_tcp_ports", "chain input", "type filter hook input priority filter; policy accept;", "iif \"lo\" accept", "ct state { established, related } accept", } { if !strings.Contains(out, want) { t.Errorf("expected %q in output, got:\n%s", want, out) } } } func TestGenerate_WithPorts_IncludesElements(t *testing.T) { m := NewManager("") out := m.Generate([]int{80, 443, 8404}, nil, nil, "") if !strings.Contains(out, "elements = { 80, 443, 8404 }") { t.Errorf("expected elements line with ports, got:\n%s", out) } } func TestGenerate_NoPorts_TCPSetHasNoElements(t *testing.T) { m := NewManager("") out := m.Generate([]int{}, nil, nil, "") // TCP set should have no elements when no ports given if strings.Contains(out, "allowed_tcp_ports") && strings.Contains(out, "elements") { // Check that the elements line is not inside the TCP block tcpBlock := out[strings.Index(out, "set allowed_tcp_ports"):] tcpBlock = tcpBlock[:strings.Index(tcpBlock, "}")+1] if strings.Contains(tcpBlock, "elements") { t.Errorf("expected no elements in allowed_tcp_ports block when no ports, got:\n%s", tcpBlock) } } // UDP set always has DNS/DHCP elements if !strings.Contains(out, "53, 67, 68") { t.Errorf("expected DNS/DHCP elements in allowed_udp_ports even with no TCP ports, got:\n%s", out) } } func TestGenerate_ExtraPortsMerged(t *testing.T) { m := NewManager("") out := m.Generate([]int{80, 443}, []int{22, 443}, nil, "") // 443 deduplicated if !strings.Contains(out, "22") { t.Errorf("expected extra port 22 in output, got:\n%s", out) } // 443 should appear only once count := strings.Count(out, "443") if count != 1 { t.Errorf("expected 443 exactly once, got %d times in:\n%s", count, out) } } func TestGenerate_NoWANInterface_NoDropRules(t *testing.T) { m := NewManager("") out := m.Generate([]int{80, 443}, nil, nil, "") if strings.Contains(out, "drop") { t.Errorf("expected no drop rules without WAN interface, got:\n%s", out) } } func TestGenerate_WithWANInterface_IncludesDropRules(t *testing.T) { m := NewManager("") out := m.Generate([]int{80, 443}, nil, nil, "eth0") if !strings.Contains(out, `iif "eth0"`) { t.Errorf("expected WAN interface in rules, got:\n%s", out) } if !strings.Contains(out, "drop") { t.Errorf("expected drop rule with WAN interface, got:\n%s", out) } if !strings.Contains(out, "@allowed_tcp_ports") { t.Errorf("expected reference to allowed_tcp_ports set, got:\n%s", out) } if !strings.Contains(out, "@allowed_udp_ports") { t.Errorf("expected reference to allowed_udp_ports set, got:\n%s", out) } // DNS and DHCP UDP ports should be in the allowed_udp_ports set if !strings.Contains(out, "53, 67, 68") { t.Errorf("expected DNS/DHCP UDP ports in allowed_udp_ports set, got:\n%s", out) } } func TestGenerate_AlwaysIncludesUDPSet(t *testing.T) { m := NewManager("") // No WAN interface — permissive mode, but UDP set should still be visible out := m.Generate([]int{80, 443}, nil, []int{51820}, "") if !strings.Contains(out, "set allowed_udp_ports") { t.Errorf("expected allowed_udp_ports set even without WAN interface, got:\n%s", out) } if !strings.Contains(out, "51820") { t.Errorf("expected extra UDP port 51820 in allowed_udp_ports set, got:\n%s", out) } // No drop rules without WAN interface if strings.Contains(out, "drop") { t.Errorf("expected no drop rules without WAN interface, got:\n%s", out) } } func TestGenerate_ExtraUDPPorts(t *testing.T) { m := NewManager("") out := m.Generate([]int{80, 443}, nil, []int{5353}, "eth0") // User UDP port should appear in the UDP exception list if !strings.Contains(out, "5353") { t.Errorf("expected extra UDP port 5353 in output, got:\n%s", out) } // Hardcoded DNS/DHCP should still be there if !strings.Contains(out, "53, 67, 68") { t.Errorf("expected DNS/DHCP UDP ports still present, got:\n%s", out) } }