fix: Move VPN masquerade rules from iptables PostUp to nftables

iptables-nft rules added by WireGuard's PostUp were being flushed by
Docker on startup, breaking VPN LAN routing until the next reboot.
The nftables inet wild-central table survives Docker restarts.
This commit is contained in:
2026-10-23 13:32:41 +00:00
parent 1176c7715a
commit d35d0854cb
4 changed files with 76 additions and 20 deletions

View File

@@ -37,7 +37,7 @@ func TestPortsToStr(t *testing.T) {
func TestGenerate_AlwaysIncludesStructure(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{80, 443}, nil, nil, "")
out := m.Generate([]int{80, 443}, nil, nil, "", "")
for _, want := range []string{
"table inet wild-central",
@@ -55,7 +55,7 @@ func TestGenerate_AlwaysIncludesStructure(t *testing.T) {
func TestGenerate_WithPorts_IncludesElements(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{80, 443, 8404}, nil, nil, "")
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)
@@ -64,7 +64,7 @@ func TestGenerate_WithPorts_IncludesElements(t *testing.T) {
func TestGenerate_NoPorts_TCPSetHasNoElements(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{}, nil, nil, "")
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") {
@@ -83,7 +83,7 @@ func TestGenerate_NoPorts_TCPSetHasNoElements(t *testing.T) {
func TestGenerate_ExtraPortsMerged(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{80, 443}, []int{22, 443}, nil, "") // 443 deduplicated
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)
@@ -97,7 +97,7 @@ func TestGenerate_ExtraPortsMerged(t *testing.T) {
func TestGenerate_NoWANInterface_NoDropRules(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{80, 443}, nil, nil, "")
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)
@@ -106,7 +106,7 @@ func TestGenerate_NoWANInterface_NoDropRules(t *testing.T) {
func TestGenerate_WithWANInterface_IncludesDropRules(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{80, 443}, nil, nil, "eth0")
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)
@@ -129,7 +129,7 @@ func TestGenerate_WithWANInterface_IncludesDropRules(t *testing.T) {
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}, "")
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)
@@ -143,9 +143,39 @@ func TestGenerate_AlwaysIncludesUDPSet(t *testing.T) {
}
}
func TestGenerate_WithVPNCIDR_IncludesMasquerade(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{80, 443}, nil, nil, "", "10.8.0.0/24")
if !strings.Contains(out, "chain vpn-forward") {
t.Errorf("expected vpn-forward chain, got:\n%s", out)
}
if !strings.Contains(out, `iif "wg0" accept`) {
t.Errorf("expected wg0 accept rule, got:\n%s", out)
}
if !strings.Contains(out, "chain vpn-postrouting") {
t.Errorf("expected vpn-postrouting chain, got:\n%s", out)
}
if !strings.Contains(out, "ip saddr 10.8.0.0/24 ip daddr != 10.8.0.0/24 masquerade") {
t.Errorf("expected masquerade rule with VPN CIDR, got:\n%s", out)
}
}
func TestGenerate_NoVPNCIDR_NoMasquerade(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{80, 443}, nil, nil, "", "")
if strings.Contains(out, "masquerade") {
t.Errorf("expected no masquerade without VPN CIDR, got:\n%s", out)
}
if strings.Contains(out, "vpn-forward") {
t.Errorf("expected no vpn-forward chain without VPN CIDR, got:\n%s", out)
}
}
func TestGenerate_ExtraUDPPorts(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{80, 443}, nil, []int{5353}, "eth0")
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") {