Skip to content

Commit b0d6155

Browse files
authored
Merge pull request moby#50180 from robmry/test_nftabler
Add TestNftabler
2 parents 5b9fa6c + ec185e5 commit b0d6155

File tree

285 files changed

+13202
-11
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

285 files changed

+13202
-11
lines changed
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
//go:build linux
2+
3+
package nftabler
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"net"
9+
"net/netip"
10+
"testing"
11+
12+
"github.com/docker/docker/internal/testutils/netnsutils"
13+
"github.com/docker/docker/libnetwork/drivers/bridge/internal/firewaller"
14+
"github.com/docker/docker/libnetwork/internal/nftables"
15+
"github.com/docker/docker/libnetwork/types"
16+
"gotest.tools/v3/assert"
17+
is "gotest.tools/v3/assert/cmp"
18+
"gotest.tools/v3/golden"
19+
"gotest.tools/v3/icmd"
20+
)
21+
22+
func TestNftabler(t *testing.T) {
23+
const (
24+
ipv4 uint64 = 1 << iota
25+
ipv6
26+
hairpin
27+
internal
28+
icc
29+
masq
30+
snat
31+
bindLocalhost
32+
wsl2Mirrored
33+
numBoolParams = iota
34+
)
35+
nftables.Enable()
36+
t.Cleanup(func() { nftables.Disable() }) // Cleanup instead of defer, this func returns before the parallel subtests finish.
37+
for i := range uint64(1) << numBoolParams {
38+
p := func(n uint64) bool { return (i & n) == n }
39+
for _, gwmode := range []string{"nat", "nat-unprotected", "routed"} {
40+
config := firewaller.Config{
41+
IPv4: p(ipv4),
42+
IPv6: p(ipv6),
43+
Hairpin: p(hairpin),
44+
WSL2Mirrored: p(wsl2Mirrored),
45+
}
46+
netConfig := firewaller.NetworkConfig{
47+
IfName: "br-dummy",
48+
Internal: p(internal),
49+
ICC: p(icc),
50+
Masquerade: p(masq),
51+
Config4: firewaller.NetworkConfigFam{
52+
HostIP: netip.Addr{},
53+
Prefix: netip.MustParsePrefix("192.168.0.0/24"),
54+
Routed: gwmode == "routed",
55+
Unprotected: gwmode == "nat-unprotected",
56+
},
57+
Config6: firewaller.NetworkConfigFam{
58+
HostIP: netip.Addr{},
59+
Prefix: netip.MustParsePrefix("fd49:efd7:54aa::/64"),
60+
Routed: gwmode == "routed",
61+
Unprotected: gwmode == "nat-unprotected",
62+
},
63+
}
64+
if p(snat) {
65+
netConfig.Config4.HostIP = netip.MustParseAddr("192.168.123.0")
66+
netConfig.Config6.HostIP = netip.MustParseAddr("fd34:d0d4:672f::123")
67+
}
68+
tn := t.Name()
69+
t.Run(fmt.Sprintf("ipv4=%v/ipv6=%v/hairpin=%v/internal=%v/icc=%v/masq=%v/snat=%v/gwm=%v/bindlh=%v/wsl2mirrored=%v",
70+
p(ipv4), p(ipv6), p(hairpin), p(internal), p(icc), p(masq), p(snat), gwmode, p(bindLocalhost), p(wsl2Mirrored)), func(t *testing.T) {
71+
// If updating results, don't run in parallel because some of the results files are shared.
72+
if !golden.FlagUpdate() {
73+
t.Parallel()
74+
}
75+
// Combine results (golden output files) where possible to:
76+
// - check params that should have no effect when made irrelevant by other params, and
77+
// - minimise the number of results files.
78+
var resName string
79+
if p(internal) {
80+
// Port binding params should have no effect on an internal network.
81+
resName = fmt.Sprintf("hairpin=%v,internal=true,icc=%v", p(hairpin), p(icc))
82+
} else {
83+
resName = fmt.Sprintf("hairpin=%v,internal=%v,icc=%v,masq=%v,snat=%v,gwm=%v,bindlh=%v",
84+
p(hairpin), p(internal), p(icc), p(masq), p(snat), gwmode, p(bindLocalhost))
85+
}
86+
testNftabler(t, tn, config, netConfig, p(bindLocalhost), tn+"_"+resName)
87+
})
88+
}
89+
}
90+
}
91+
92+
func testNftabler(t *testing.T, tn string, config firewaller.Config, netConfig firewaller.NetworkConfig, bindLocalhost bool, resName string) {
93+
defer netnsutils.SetupTestOSContext(t)()
94+
95+
checkResults := func(family, name string, en bool) {
96+
t.Helper()
97+
res := icmd.RunCommand("nft", "list", "table", family, dockerTable)
98+
if !en {
99+
assert.Assert(t, is.Contains(res.Combined(), "No such file or directory"))
100+
return
101+
}
102+
assert.Assert(t, res.Error)
103+
golden.Assert(t, res.Combined(), name+"__"+family+".golden")
104+
}
105+
106+
makePB := func(hip string, cip netip.Addr) types.PortBinding {
107+
return types.PortBinding{
108+
Proto: types.TCP,
109+
IP: cip.AsSlice(),
110+
Port: 80,
111+
HostIP: net.ParseIP(hip),
112+
HostPort: 8080,
113+
HostPortEnd: 8080,
114+
}
115+
}
116+
117+
// WSL2Mirrored should only affect IPv4 results, and only if there's a port binding
118+
// to a loopback address or docker-proxy is disabled. Share other results files.
119+
rnWSL2Mirrored := func(resName string) string {
120+
if config.IPv4 && config.WSL2Mirrored && (bindLocalhost || !config.Hairpin) {
121+
return resName + ",wsl2mirrored=true"
122+
}
123+
return resName
124+
}
125+
126+
// Initialise iptables, check the iptables config looks like it should look at the
127+
// end of the test (after deleting per-network and per-port rules).
128+
fw, err := NewNftabler(context.Background(), config)
129+
assert.NilError(t, err)
130+
checkResults("ip", rnWSL2Mirrored(fmt.Sprintf("%s_cleaned,hairpin=%v", tn, config.Hairpin)), config.IPv4)
131+
checkResults("ip6", fmt.Sprintf("%s_cleaned,hairpin=%v", tn, config.Hairpin), config.IPv6)
132+
133+
// Add the network.
134+
nw, err := fw.NewNetwork(context.Background(), netConfig)
135+
assert.NilError(t, err)
136+
137+
// Add an endpoint.
138+
epAddr4 := netip.MustParseAddr("192.168.0.2")
139+
epAddr6 := netip.MustParseAddr("fd49:efd7:54aa::1")
140+
err = nw.AddEndpoint(context.Background(), epAddr4, epAddr6)
141+
assert.NilError(t, err)
142+
143+
// Add IPv4 and IPv6 port mappings.
144+
var pb4, pb6 types.PortBinding
145+
if bindLocalhost {
146+
pb4 = makePB("127.0.0.1", epAddr4)
147+
pb6 = makePB("::1", epAddr6)
148+
} else {
149+
pb4 = makePB("0.0.0.0", epAddr4)
150+
pb6 = makePB("::", epAddr6)
151+
}
152+
err = nw.AddPorts(context.Background(), []types.PortBinding{pb4, pb6})
153+
assert.NilError(t, err)
154+
155+
// Check the resulting iptables config.
156+
checkResults("ip", rnWSL2Mirrored(resName), config.IPv4)
157+
checkResults("ip6", resName, config.IPv6)
158+
159+
// Remove the port mappings and the network, and check the result (should be the same
160+
// for all tests with the same "hairpin" setting).
161+
err = nw.DelPorts(context.Background(), []types.PortBinding{pb4, pb6})
162+
assert.NilError(t, err)
163+
err = nw.DelEndpoint(context.Background(), epAddr4, epAddr6)
164+
assert.NilError(t, err)
165+
err = nw.DelNetworkLevelRules(context.Background())
166+
assert.NilError(t, err)
167+
checkResults("ip", rnWSL2Mirrored(fmt.Sprintf("%s_cleaned,hairpin=%v", tn, config.Hairpin)), config.IPv4)
168+
checkResults("ip6", fmt.Sprintf("%s_cleaned,hairpin=%v", tn, config.Hairpin), config.IPv6)
169+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
table ip docker-bridges {
2+
map filter-forward-in-jumps {
3+
type ifname : verdict
4+
}
5+
6+
map filter-forward-out-jumps {
7+
type ifname : verdict
8+
}
9+
10+
map nat-postrouting-in-jumps {
11+
type ifname : verdict
12+
}
13+
14+
map nat-postrouting-out-jumps {
15+
type ifname : verdict
16+
}
17+
18+
chain filter-FORWARD {
19+
type filter hook forward priority filter; policy accept;
20+
oifname vmap @filter-forward-in-jumps
21+
iifname vmap @filter-forward-out-jumps
22+
}
23+
24+
chain nat-OUTPUT {
25+
type nat hook output priority -100; policy accept;
26+
ip daddr != 127.0.0.0/8 fib daddr type local counter packets 0 bytes 0 jump nat-prerouting-and-output
27+
}
28+
29+
chain nat-POSTROUTING {
30+
type nat hook postrouting priority srcnat; policy accept;
31+
iifname vmap @nat-postrouting-out-jumps
32+
oifname vmap @nat-postrouting-in-jumps
33+
}
34+
35+
chain nat-PREROUTING {
36+
type nat hook prerouting priority dstnat; policy accept;
37+
fib daddr type local counter packets 0 bytes 0 jump nat-prerouting-and-output
38+
}
39+
40+
chain nat-prerouting-and-output {
41+
iifname "loopback0" ip daddr 127.0.0.0/8 counter packets 0 bytes 0 return
42+
}
43+
44+
chain raw-PREROUTING {
45+
type filter hook prerouting priority raw; policy accept;
46+
}
47+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
table ip docker-bridges {
2+
map filter-forward-in-jumps {
3+
type ifname : verdict
4+
}
5+
6+
map filter-forward-out-jumps {
7+
type ifname : verdict
8+
}
9+
10+
map nat-postrouting-in-jumps {
11+
type ifname : verdict
12+
}
13+
14+
map nat-postrouting-out-jumps {
15+
type ifname : verdict
16+
}
17+
18+
chain filter-FORWARD {
19+
type filter hook forward priority filter; policy accept;
20+
oifname vmap @filter-forward-in-jumps
21+
iifname vmap @filter-forward-out-jumps
22+
}
23+
24+
chain nat-OUTPUT {
25+
type nat hook output priority -100; policy accept;
26+
ip daddr != 127.0.0.0/8 fib daddr type local counter packets 0 bytes 0 jump nat-prerouting-and-output
27+
}
28+
29+
chain nat-POSTROUTING {
30+
type nat hook postrouting priority srcnat; policy accept;
31+
iifname vmap @nat-postrouting-out-jumps
32+
oifname vmap @nat-postrouting-in-jumps
33+
}
34+
35+
chain nat-PREROUTING {
36+
type nat hook prerouting priority dstnat; policy accept;
37+
fib daddr type local counter packets 0 bytes 0 jump nat-prerouting-and-output
38+
}
39+
40+
chain nat-prerouting-and-output {
41+
}
42+
43+
chain raw-PREROUTING {
44+
type filter hook prerouting priority raw; policy accept;
45+
}
46+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
table ip6 docker-bridges {
2+
map filter-forward-in-jumps {
3+
type ifname : verdict
4+
}
5+
6+
map filter-forward-out-jumps {
7+
type ifname : verdict
8+
}
9+
10+
map nat-postrouting-in-jumps {
11+
type ifname : verdict
12+
}
13+
14+
map nat-postrouting-out-jumps {
15+
type ifname : verdict
16+
}
17+
18+
chain filter-FORWARD {
19+
type filter hook forward priority filter; policy accept;
20+
oifname vmap @filter-forward-in-jumps
21+
iifname vmap @filter-forward-out-jumps
22+
}
23+
24+
chain nat-OUTPUT {
25+
type nat hook output priority -100; policy accept;
26+
ip6 daddr != ::1 fib daddr type local counter packets 0 bytes 0 jump nat-prerouting-and-output
27+
}
28+
29+
chain nat-POSTROUTING {
30+
type nat hook postrouting priority srcnat; policy accept;
31+
iifname vmap @nat-postrouting-out-jumps
32+
oifname vmap @nat-postrouting-in-jumps
33+
}
34+
35+
chain nat-PREROUTING {
36+
type nat hook prerouting priority dstnat; policy accept;
37+
fib daddr type local counter packets 0 bytes 0 jump nat-prerouting-and-output
38+
}
39+
40+
chain nat-prerouting-and-output {
41+
}
42+
43+
chain raw-PREROUTING {
44+
type filter hook prerouting priority raw; policy accept;
45+
}
46+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
table ip docker-bridges {
2+
map filter-forward-in-jumps {
3+
type ifname : verdict
4+
}
5+
6+
map filter-forward-out-jumps {
7+
type ifname : verdict
8+
}
9+
10+
map nat-postrouting-in-jumps {
11+
type ifname : verdict
12+
}
13+
14+
map nat-postrouting-out-jumps {
15+
type ifname : verdict
16+
}
17+
18+
chain filter-FORWARD {
19+
type filter hook forward priority filter; policy accept;
20+
oifname vmap @filter-forward-in-jumps
21+
iifname vmap @filter-forward-out-jumps
22+
}
23+
24+
chain nat-OUTPUT {
25+
type nat hook output priority -100; policy accept;
26+
fib daddr type local counter packets 0 bytes 0 jump nat-prerouting-and-output
27+
}
28+
29+
chain nat-POSTROUTING {
30+
type nat hook postrouting priority srcnat; policy accept;
31+
iifname vmap @nat-postrouting-out-jumps
32+
oifname vmap @nat-postrouting-in-jumps
33+
}
34+
35+
chain nat-PREROUTING {
36+
type nat hook prerouting priority dstnat; policy accept;
37+
fib daddr type local counter packets 0 bytes 0 jump nat-prerouting-and-output
38+
}
39+
40+
chain nat-prerouting-and-output {
41+
}
42+
43+
chain raw-PREROUTING {
44+
type filter hook prerouting priority raw; policy accept;
45+
}
46+
}

0 commit comments

Comments
 (0)