diff --git a/daemon/info_unix.go b/daemon/info_unix.go index 4a972c4f01..aa8e40afb2 100644 --- a/daemon/info_unix.go +++ b/daemon/info_unix.go @@ -159,6 +159,10 @@ func (daemon *Daemon) fillPlatformInfo(ctx context.Context, v *system.Info, sysI if !v.IPv4Forwarding { v.Warnings = append(v.Warnings, "WARNING: IPv4 forwarding is disabled") } + // Env-var belonging to the bridge driver, disables use of the iptables "raw" table. + if os.Getenv("DOCKER_INSECURE_NO_IPTABLES_RAW") == "1" { + v.Warnings = append(v.Warnings, "WARNING: DOCKER_INSECURE_NO_IPTABLES_RAW is set") + } return nil } diff --git a/integration/networking/port_mapping_linux_test.go b/integration/networking/port_mapping_linux_test.go index 6675a6aef1..0fd55d924f 100644 --- a/integration/networking/port_mapping_linux_test.go +++ b/integration/networking/port_mapping_linux_test.go @@ -28,6 +28,7 @@ import ( "github.com/docker/go-connections/nat" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" + "gotest.tools/v3/golden" "gotest.tools/v3/icmd" "gotest.tools/v3/poll" "gotest.tools/v3/skip" @@ -1179,3 +1180,60 @@ func getContainerStdout(t *testing.T, ctx context.Context, c *client.Client, ctr return logs.String() } + +// TestSkipRawRules checks that when env var DOCKER_INSECURE_NO_IPTABLES_RAW=1, no rules are added to +// the iptables "raw" table - as a workaround for kernels that don't have CONFIG_IP_NF_RAW. +// See https://github.com/moby/moby/issues/49557 +func TestSkipRawRules(t *testing.T) { + skip.If(t, testEnv.IsRootless, "can't use L3Segment, or check iptables rules") + + testcases := []struct { + name string + noIptablesRaw string + }{ + { + name: "skip=false", + noIptablesRaw: "0", + }, + { + name: "skip=true", + noIptablesRaw: "1", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + // Run in a new netns, to make sure there are no raw rules left behind by other tests + const l3SegHost = "skip-raw" + l3 := networking.NewL3Segment(t, "test-"+l3SegHost) + defer l3.Destroy(t) + hostAddrs := []netip.Prefix{ + netip.MustParsePrefix("192.168.234.0/24"), + netip.MustParsePrefix("fd3f:c09d:715b::/64"), + } + l3.AddHost(t, l3SegHost, "ns-"+l3SegHost, "eth0", hostAddrs...) + l3.Hosts[l3SegHost].Do(t, func() { + ctx := setupTest(t) + d := daemon.New(t, daemon.WithEnvVars("DOCKER_INSECURE_NO_IPTABLES_RAW="+tc.noIptablesRaw)) + d.StartWithBusybox(ctx, t, "--ipv6", "--bip=192.168.0.1/24", "--bip6=fd30:1159:a755::1/64") + defer d.Stop(t) + c := d.NewClientT(t) + defer c.Close() + + ctrId := container.Run(ctx, t, c, + container.WithExposedPorts("80/tcp"), + container.WithPortMap(nat.PortMap{"80/tcp": { + {HostIP: "127.0.0.1", HostPort: "8080"}, + {HostPort: "8081"}, + }}), + ) + defer c.ContainerRemove(ctx, ctrId, containertypes.RemoveOptions{Force: true}) + + res4 := icmd.RunCommand("iptables", "-S", "-t", "raw") + golden.Assert(t, res4.Stdout(), t.Name()+"_ipv4.golden") + res6 := icmd.RunCommand("ip6tables", "-S", "-t", "raw") + golden.Assert(t, res6.Stdout(), t.Name()+"_ipv6.golden") + }) + }) + } +} diff --git a/integration/networking/testdata/TestSkipRawRules/skip=false_ipv4.golden b/integration/networking/testdata/TestSkipRawRules/skip=false_ipv4.golden new file mode 100644 index 0000000000..c686c1d626 --- /dev/null +++ b/integration/networking/testdata/TestSkipRawRules/skip=false_ipv4.golden @@ -0,0 +1,4 @@ +-P PREROUTING ACCEPT +-P OUTPUT ACCEPT +-A PREROUTING -d 127.0.0.1/32 ! -i lo -p tcp -m tcp --dport 8080 -j DROP +-A PREROUTING -d 192.168.0.2/32 ! -i docker0 -p tcp -m tcp --dport 80 -j DROP diff --git a/integration/networking/testdata/TestSkipRawRules/skip=false_ipv6.golden b/integration/networking/testdata/TestSkipRawRules/skip=false_ipv6.golden new file mode 100644 index 0000000000..2760cebf50 --- /dev/null +++ b/integration/networking/testdata/TestSkipRawRules/skip=false_ipv6.golden @@ -0,0 +1,3 @@ +-P PREROUTING ACCEPT +-P OUTPUT ACCEPT +-A PREROUTING -d fd30:1159:a755::2/128 ! -i docker0 -p tcp -m tcp --dport 80 -j DROP diff --git a/integration/networking/testdata/TestSkipRawRules/skip=true_ipv4.golden b/integration/networking/testdata/TestSkipRawRules/skip=true_ipv4.golden new file mode 100644 index 0000000000..5ca8147135 --- /dev/null +++ b/integration/networking/testdata/TestSkipRawRules/skip=true_ipv4.golden @@ -0,0 +1,2 @@ +-P PREROUTING ACCEPT +-P OUTPUT ACCEPT diff --git a/integration/networking/testdata/TestSkipRawRules/skip=true_ipv6.golden b/integration/networking/testdata/TestSkipRawRules/skip=true_ipv6.golden new file mode 100644 index 0000000000..5ca8147135 --- /dev/null +++ b/integration/networking/testdata/TestSkipRawRules/skip=true_ipv6.golden @@ -0,0 +1,2 @@ +-P PREROUTING ACCEPT +-P OUTPUT ACCEPT diff --git a/libnetwork/drivers/bridge/port_mapping_linux.go b/libnetwork/drivers/bridge/port_mapping_linux.go index bd7e52e9af..2a80950372 100644 --- a/libnetwork/drivers/bridge/port_mapping_linux.go +++ b/libnetwork/drivers/bridge/port_mapping_linux.go @@ -203,7 +203,7 @@ func (n *bridgeNetwork) addPortMappings( return nil, err } } - if err := n.setPerPortIptables(b, true); err != nil { + if err := n.setPerPortIptables(ctx, b, true); err != nil { return nil, err } } @@ -745,7 +745,7 @@ func (n *bridgeNetwork) releasePortBindings(pbs []portBinding) error { errs = append(errs, fmt.Errorf("failed to stop userland proxy for port mapping %s: %w", pb, err)) } } - if err := n.setPerPortIptables(pb, false); err != nil { + if err := n.setPerPortIptables(context.TODO(), pb, false); err != nil { errs = append(errs, fmt.Errorf("failed to remove iptables rules for port mapping %s: %w", pb, err)) } if pb.HostPort > 0 { @@ -755,7 +755,7 @@ func (n *bridgeNetwork) releasePortBindings(pbs []portBinding) error { return errors.Join(errs...) } -func (n *bridgeNetwork) setPerPortIptables(b portBinding, enable bool) error { +func (n *bridgeNetwork) setPerPortIptables(ctx context.Context, b portBinding, enable bool) error { v := iptables.IPv4 if b.IP.To4() == nil { v = iptables.IPv6 @@ -765,11 +765,11 @@ func (n *bridgeNetwork) setPerPortIptables(b portBinding, enable bool) error { return nil } - if err := n.filterPortMappedOnLoopback(b, enable); err != nil { + if err := n.filterPortMappedOnLoopback(ctx, b, enable); err != nil { return err } - if err := n.filterDirectAccess(b, enable); err != nil { + if err := n.filterDirectAccess(ctx, b, enable); err != nil { return err } @@ -884,7 +884,10 @@ func setPerPortForwarding(b portBinding, ipv iptables.IPVersion, bridgeName stri // This is a no-ip if the portBinding is for IPv6 (IPv6 loopback address is // non-routable), or over a network with gw_mode=routed (PBs in routed mode // don't map ports on the host). -func (n *bridgeNetwork) filterPortMappedOnLoopback(b portBinding, enable bool) error { +func (n *bridgeNetwork) filterPortMappedOnLoopback(ctx context.Context, b portBinding, enable bool) error { + if rawRulesDisabled(ctx) { + return nil + } hostIP := b.childHostIP if b.HostPort == 0 || !hostIP.IsLoopback() || b.childHostIP.To4() == nil { return nil @@ -921,7 +924,10 @@ func (n *bridgeNetwork) filterPortMappedOnLoopback(b portBinding, enable bool) e // mode is "nat". // // This is a no-op if the gw_mode is "nat-unprotected" or "routed". -func (n *bridgeNetwork) filterDirectAccess(b portBinding, enable bool) error { +func (n *bridgeNetwork) filterDirectAccess(ctx context.Context, b portBinding, enable bool) error { + if rawRulesDisabled(ctx) { + return nil + } ipv := iptables.IPv4 if b.IP.To4() == nil { ipv = iptables.IPv6 @@ -948,6 +954,14 @@ func (n *bridgeNetwork) filterDirectAccess(b portBinding, enable bool) error { return nil } +func rawRulesDisabled(ctx context.Context) bool { + if os.Getenv("DOCKER_INSECURE_NO_IPTABLES_RAW") == "1" { + log.G(ctx).Debug("DOCKER_INSECURE_NO_IPTABLES_RAW=1 - skipping raw rules") + return true + } + return false +} + func (n *bridgeNetwork) reapplyPerPortIptables4() { n.reapplyPerPortIptables(func(b portBinding) bool { return b.IP.To4() != nil }) } @@ -966,7 +980,7 @@ func (n *bridgeNetwork) reapplyPerPortIptables(needsReconfig func(portBinding) b for _, b := range allPBs { if needsReconfig(b) { - if err := n.setPerPortIptables(b, true); err != nil { + if err := n.setPerPortIptables(context.Background(), b, true); err != nil { log.G(context.TODO()).Warnf("Failed to reconfigure iptables on firewalld reload %s: %s", b, err) } } diff --git a/libnetwork/drivers/bridge/setup_ip_tables_linux_test.go b/libnetwork/drivers/bridge/setup_ip_tables_linux_test.go index 54a6ccf4f3..0827378576 100644 --- a/libnetwork/drivers/bridge/setup_ip_tables_linux_test.go +++ b/libnetwork/drivers/bridge/setup_ip_tables_linux_test.go @@ -1,6 +1,7 @@ package bridge import ( + "context" "net" "os" "os/exec" @@ -541,7 +542,7 @@ func TestMirroredWSL2LoopbackFiltering(t *testing.T) { config: configuration{EnableIPTables: true}, }, } - err := nw.filterPortMappedOnLoopback(portBinding{ + err := nw.filterPortMappedOnLoopback(context.TODO(), portBinding{ PortBinding: types.PortBinding{ Proto: types.TCP, IP: net.ParseIP("127.0.0.1"),