diff --git a/integration/network/bridge/iptablesdoc/generated/new-daemon.md b/integration/network/bridge/iptablesdoc/generated/new-daemon.md index 1d094dc0de..5988c1193a 100644 --- a/integration/network/bridge/iptablesdoc/generated/new-daemon.md +++ b/integration/network/bridge/iptablesdoc/generated/new-daemon.md @@ -112,7 +112,7 @@ bigger change than it should be._ The DOCKER chain has a single DROP rule for the bridge network, to drop any packets routed to the network that have not originated in the network. Added by -[defaultDrop][21]. +[setDefaultForwardRule][21]. _This means there is no dependency on the filter-FORWARD chain's default policy. Even if it is ACCEPT, packets will be dropped unless container ports/protocols are published._ diff --git a/integration/network/bridge/iptablesdoc/generated/usernet-portmap-natunprot.md b/integration/network/bridge/iptablesdoc/generated/usernet-portmap-natunprot.md new file mode 100644 index 0000000000..85cbeaf047 --- /dev/null +++ b/integration/network/bridge/iptablesdoc/generated/usernet-portmap-natunprot.md @@ -0,0 +1,138 @@ +## Container on a nat-unprotected network, with a published port + +Running the daemon with the userland proxy disable then, as before, adding a network running a container with a mapped port, equivalent to: + + docker network create \ + -o com.docker.network.bridge.name=bridge1 \ + -o com.docker.network.bridge.gateway_mode_ipv4=nat-unprotected \ + --subnet 192.0.2.0/24 --gateway 192.0.2.1 bridge1 + docker run --network bridge1 -p 8080:80 --name c1 busybox + +The filter table is: + + Chain INPUT (policy ACCEPT 0 packets, 0 bytes) + num pkts bytes target prot opt in out source destination + + Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) + num pkts bytes target prot opt in out source destination + 1 0 0 DOCKER-USER 0 -- * * 0.0.0.0/0 0.0.0.0/0 + 2 0 0 ACCEPT 0 -- * * 0.0.0.0/0 0.0.0.0/0 match-set docker-ext-bridges-v4 dst ctstate RELATED,ESTABLISHED + 3 0 0 DOCKER-ISOLATION-STAGE-1 0 -- * * 0.0.0.0/0 0.0.0.0/0 + 4 0 0 DOCKER 0 -- * * 0.0.0.0/0 0.0.0.0/0 match-set docker-ext-bridges-v4 dst + 5 0 0 ACCEPT 0 -- bridge1 !bridge1 0.0.0.0/0 0.0.0.0/0 + 6 0 0 ACCEPT 0 -- bridge1 bridge1 0.0.0.0/0 0.0.0.0/0 + 7 0 0 ACCEPT 0 -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0 + 8 0 0 ACCEPT 0 -- docker0 docker0 0.0.0.0/0 0.0.0.0/0 + + Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) + num pkts bytes target prot opt in out source destination + + Chain DOCKER (1 references) + num pkts bytes target prot opt in out source destination + 1 0 0 DROP 0 -- !docker0 docker0 0.0.0.0/0 0.0.0.0/0 + 2 0 0 ACCEPT 0 -- !bridge1 bridge1 0.0.0.0/0 0.0.0.0/0 + + Chain DOCKER-ISOLATION-STAGE-1 (1 references) + num pkts bytes target prot opt in out source destination + 1 0 0 DOCKER-ISOLATION-STAGE-2 0 -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0 + 2 0 0 DOCKER-ISOLATION-STAGE-2 0 -- bridge1 !bridge1 0.0.0.0/0 0.0.0.0/0 + + Chain DOCKER-ISOLATION-STAGE-2 (2 references) + num pkts bytes target prot opt in out source destination + 1 0 0 DROP 0 -- * bridge1 0.0.0.0/0 0.0.0.0/0 + 2 0 0 DROP 0 -- * docker0 0.0.0.0/0 0.0.0.0/0 + + Chain DOCKER-USER (1 references) + num pkts bytes target prot opt in out source destination + 1 0 0 RETURN 0 -- * * 0.0.0.0/0 0.0.0.0/0 + + +
+iptables commands + + -P INPUT ACCEPT + -P FORWARD ACCEPT + -P OUTPUT ACCEPT + -N DOCKER + -N DOCKER-ISOLATION-STAGE-1 + -N DOCKER-ISOLATION-STAGE-2 + -N DOCKER-USER + -A FORWARD -j DOCKER-USER + -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT + -A FORWARD -j DOCKER-ISOLATION-STAGE-1 + -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER + -A FORWARD -i bridge1 ! -o bridge1 -j ACCEPT + -A FORWARD -i bridge1 -o bridge1 -j ACCEPT + -A FORWARD -i docker0 ! -o docker0 -j ACCEPT + -A FORWARD -i docker0 -o docker0 -j ACCEPT + -A DOCKER ! -i docker0 -o docker0 -j DROP + -A DOCKER ! -i bridge1 -o bridge1 -j ACCEPT + -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2 + -A DOCKER-ISOLATION-STAGE-1 -i bridge1 ! -o bridge1 -j DOCKER-ISOLATION-STAGE-2 + -A DOCKER-ISOLATION-STAGE-2 -o bridge1 -j DROP + -A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP + -A DOCKER-USER -j RETURN + + +
+ +Differences from [nat mode][400]: + + - In the DOCKER chain: + - Where `nat` mode appended a default-DROP rule for any packets not accepted + by the per-port/protocol rules, `nat-unprotected` appends a default-ACCEPT + rule. [setDefaultForwardRule][402] + - The ACCEPT rule is needed in case the filter-FORWARD chain's default + policy is DROP. + - Because the default for this network is ACCEPT, there is no per-port/protocol + rule to ACCEPT packets for the published port `80/tcp`, [setPerPortIptables][401] + doesn't set it up. + - _If the userland proxy is enabled, it is still started._ + +The nat table is identical to [nat mode][400]. + +
+nat table + + Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes) + num pkts bytes target prot opt in out source destination + 1 0 0 DOCKER 0 -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL + + Chain INPUT (policy ACCEPT 0 packets, 0 bytes) + num pkts bytes target prot opt in out source destination + + Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) + num pkts bytes target prot opt in out source destination + 1 0 0 DOCKER 0 -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL + + Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes) + num pkts bytes target prot opt in out source destination + 1 0 0 MASQUERADE 0 -- * !bridge1 192.0.2.0/24 0.0.0.0/0 + 2 0 0 MASQUERADE 0 -- * !docker0 172.17.0.0/16 0.0.0.0/0 + + Chain DOCKER (2 references) + num pkts bytes target prot opt in out source destination + 1 0 0 RETURN 0 -- bridge1 * 0.0.0.0/0 0.0.0.0/0 + 2 0 0 RETURN 0 -- docker0 * 0.0.0.0/0 0.0.0.0/0 + 3 0 0 DNAT 6 -- !bridge1 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:192.0.2.2:80 + + + -P PREROUTING ACCEPT + -P INPUT ACCEPT + -P OUTPUT ACCEPT + -P POSTROUTING ACCEPT + -N DOCKER + -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER + -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER + -A POSTROUTING -s 192.0.2.0/24 ! -o bridge1 -j MASQUERADE + -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE + -A DOCKER -i bridge1 -j RETURN + -A DOCKER -i docker0 -j RETURN + -A DOCKER ! -i bridge1 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 192.0.2.2:80 + + +
+ +[400]: usernet-portmap.md +[401]: https://github.com/robmry/moby/blob/52c89d467fc5326149e4bbb8903d23589b66ff0d/libnetwork/drivers/bridge/port_mapping_linux.go#L747 +[402]: https://github.com/robmry/moby/blob/52c89d467fc5326149e4bbb8903d23589b66ff0d/libnetwork/drivers/bridge/setup_ip_tables_linux.go#L261-L266 diff --git a/integration/network/bridge/iptablesdoc/generated/usernet-portmap.md b/integration/network/bridge/iptablesdoc/generated/usernet-portmap.md index 792b29fd84..23fad3ad76 100644 --- a/integration/network/bridge/iptablesdoc/generated/usernet-portmap.md +++ b/integration/network/bridge/iptablesdoc/generated/usernet-portmap.md @@ -90,8 +90,8 @@ Note that: (unlike all the other rules so-far, which were created during driver or network initialisation). [setPerPortForwarding][1] - These per-port rules are inserted at the head of the chain, so that they - appear before the network's DROP rule [defaultDrop][2] which is always - appended to the end of the chain. In this case, because `docker0` was + appear before the network's DROP rule [setDefaultForwardRule][2] which is + always appended to the end of the chain. In this case, because `docker0` was created before `bridge1`, the `bridge1` rules appear above and below the `docker0` DROP rule. diff --git a/integration/network/bridge/iptablesdoc/index.md b/integration/network/bridge/iptablesdoc/index.md index 4a15e0b4b6..87b9292392 100644 --- a/integration/network/bridge/iptablesdoc/index.md +++ b/integration/network/bridge/iptablesdoc/index.md @@ -44,3 +44,4 @@ Scenarios: - [Container on a user-defined network with inter-container communication disabled, with a published port](generated/usernet-portmap-noicc.md) - [Container on a user-defined --internal network](generated/usernet-internal.md) - [Container on a routed-mode network, with a published port](generated/usernet-portmap-routed.md) + - [Container on a nat-unprotected network, with a published port](generated/usernet-portmap-natunprot.md) diff --git a/integration/network/bridge/iptablesdoc/iptablesdoc_linux_test.go b/integration/network/bridge/iptablesdoc/iptablesdoc_linux_test.go index 780ec31aef..7355cba59a 100644 --- a/integration/network/bridge/iptablesdoc/iptablesdoc_linux_test.go +++ b/integration/network/bridge/iptablesdoc/iptablesdoc_linux_test.go @@ -133,6 +133,19 @@ var index = []section{ }, }}, }, + { + name: "usernet-portmap-natunprot.md", + networks: []bridgeNetwork{{ + bridge: "bridge1", + gwMode: "nat-unprotected", + containers: []ctr{ + { + name: "c1", + portMappings: nat.PortMap{"80/tcp": {{HostPort: "8080"}}}, + }, + }, + }}, + }, } // iptCmdType is used to look up iptCmds in the markdown (can't use an int diff --git a/integration/network/bridge/iptablesdoc/templates/new-daemon.md b/integration/network/bridge/iptablesdoc/templates/new-daemon.md index 8a4795b665..1c5ce2a673 100644 --- a/integration/network/bridge/iptablesdoc/templates/new-daemon.md +++ b/integration/network/bridge/iptablesdoc/templates/new-daemon.md @@ -65,7 +65,7 @@ bigger change than it should be._ The DOCKER chain has a single DROP rule for the bridge network, to drop any packets routed to the network that have not originated in the network. Added by -[defaultDrop][21]. +[setDefaultForwardRule][21]. _This means there is no dependency on the filter-FORWARD chain's default policy. Even if it is ACCEPT, packets will be dropped unless container ports/protocols are published._ diff --git a/integration/network/bridge/iptablesdoc/templates/usernet-portmap-natunprot.md b/integration/network/bridge/iptablesdoc/templates/usernet-portmap-natunprot.md new file mode 100644 index 0000000000..cf48059217 --- /dev/null +++ b/integration/network/bridge/iptablesdoc/templates/usernet-portmap-natunprot.md @@ -0,0 +1,48 @@ +## Container on a nat-unprotected network, with a published port + +Running the daemon with the userland proxy disable then, as before, adding a network running a container with a mapped port, equivalent to: + + docker network create \ + -o com.docker.network.bridge.name=bridge1 \ + -o com.docker.network.bridge.gateway_mode_ipv4=nat-unprotected \ + --subnet 192.0.2.0/24 --gateway 192.0.2.1 bridge1 + docker run --network bridge1 -p 8080:80 --name c1 busybox + +The filter table is: + + {{index . "LFilter4"}} + +
+iptables commands + + {{index . "SFilter4"}} + +
+ +Differences from [nat mode][400]: + + - In the DOCKER chain: + - Where `nat` mode appended a default-DROP rule for any packets not accepted + by the per-port/protocol rules, `nat-unprotected` appends a default-ACCEPT + rule. [setDefaultForwardRule][402] + - The ACCEPT rule is needed in case the filter-FORWARD chain's default + policy is DROP. + - Because the default for this network is ACCEPT, there is no per-port/protocol + rule to ACCEPT packets for the published port `80/tcp`, [setPerPortIptables][401] + doesn't set it up. + - _If the userland proxy is enabled, it is still started._ + +The nat table is identical to [nat mode][400]. + +
+nat table + + {{index . "LNat4"}} + + {{index . "SNat4"}} + +
+ +[400]: usernet-portmap.md +[401]: https://github.com/robmry/moby/blob/52c89d467fc5326149e4bbb8903d23589b66ff0d/libnetwork/drivers/bridge/port_mapping_linux.go#L747 +[402]: https://github.com/robmry/moby/blob/52c89d467fc5326149e4bbb8903d23589b66ff0d/libnetwork/drivers/bridge/setup_ip_tables_linux.go#L261-L266 diff --git a/integration/network/bridge/iptablesdoc/templates/usernet-portmap.md b/integration/network/bridge/iptablesdoc/templates/usernet-portmap.md index ecfee8c920..10eae93a56 100644 --- a/integration/network/bridge/iptablesdoc/templates/usernet-portmap.md +++ b/integration/network/bridge/iptablesdoc/templates/usernet-portmap.md @@ -31,8 +31,8 @@ Note that: (unlike all the other rules so-far, which were created during driver or network initialisation). [setPerPortForwarding][1] - These per-port rules are inserted at the head of the chain, so that they - appear before the network's DROP rule [defaultDrop][2] which is always - appended to the end of the chain. In this case, because `docker0` was + appear before the network's DROP rule [setDefaultForwardRule][2] which is + always appended to the end of the chain. In this case, because `docker0` was created before `bridge1`, the `bridge1` rules appear above and below the `docker0` DROP rule. diff --git a/integration/networking/port_mapping_linux_test.go b/integration/networking/port_mapping_linux_test.go index 297ef0f5ee..3e2abd318d 100644 --- a/integration/networking/port_mapping_linux_test.go +++ b/integration/networking/port_mapping_linux_test.go @@ -10,6 +10,7 @@ import ( "os/exec" "strconv" "strings" + "syscall" "testing" "time" @@ -671,10 +672,14 @@ func TestDirectRoutingOpenPorts(t *testing.T) { // Run http servers on ports 80 and 81, but only map/open port 80. createNet := func(gwMode string) ctrDesc { netName := "test-" + gwMode + brName := "br-" + gwMode + if len(brName) > syscall.IFNAMSIZ { + brName = brName[:syscall.IFNAMSIZ-1] + } network.CreateNoError(ctx, t, c, netName, network.WithDriver("bridge"), network.WithIPv6(), - network.WithOption(bridge.BridgeName, "br-"+gwMode), + network.WithOption(bridge.BridgeName, brName), network.WithOption(bridge.IPv4GatewayMode, gwMode), network.WithOption(bridge.IPv6GatewayMode, gwMode), ) @@ -711,12 +716,19 @@ func TestDirectRoutingOpenPorts(t *testing.T) { ) networks := map[string]ctrDesc{ - "nat": createNet("nat"), - "routed": createNet("routed"), + "nat": createNet("nat"), + "nat-unprotected": createNet("nat-unprotected"), + "routed": createNet("routed"), } expPingExit := map[string]int{ - "nat": pingFail, - "routed": pingSuccess, + "nat": pingFail, + "nat-unprotected": pingSuccess, + "routed": pingSuccess, + } + expUnmappedPortHTTP := map[string]string{ + "nat": httpFail, + "nat-unprotected": httpSuccess, + "routed": httpFail, } testPing := func(t *testing.T, cmd, addr string, expExit int) { @@ -746,7 +758,7 @@ func TestDirectRoutingOpenPorts(t *testing.T) { for _, fwdPolicy := range []string{"ACCEPT", "DROP"} { networking.SetFilterForwardPolicies(t, fwdPolicy) t.Run(fwdPolicy, func(t *testing.T) { - for _, gwMode := range []string{"nat", "routed"} { + for gwMode := range networks { t.Run(gwMode+"/v4/ping", func(t *testing.T) { testPing(t, "ping", networks[gwMode].ipv4, expPingExit[gwMode]) }) @@ -757,13 +769,13 @@ func TestDirectRoutingOpenPorts(t *testing.T) { testHttp(t, networks[gwMode].ipv4, "80", httpSuccess) }) t.Run(gwMode+"/v4/http/81", func(t *testing.T) { - testHttp(t, networks[gwMode].ipv4, "81", httpFail) + testHttp(t, networks[gwMode].ipv4, "81", expUnmappedPortHTTP[gwMode]) }) t.Run(gwMode+"/v6/http/80", func(t *testing.T) { testHttp(t, networks[gwMode].ipv6, "80", httpSuccess) }) t.Run(gwMode+"/v6/http/81", func(t *testing.T) { - testHttp(t, networks[gwMode].ipv6, "81", httpFail) + testHttp(t, networks[gwMode].ipv6, "81", expUnmappedPortHTTP[gwMode]) }) } }) diff --git a/libnetwork/drivers/bridge/bridge_linux.go b/libnetwork/drivers/bridge/bridge_linux.go index 8170d8dd4a..8b3e83374a 100644 --- a/libnetwork/drivers/bridge/bridge_linux.go +++ b/libnetwork/drivers/bridge/bridge_linux.go @@ -167,9 +167,10 @@ type driver struct { type gwMode string const ( - gwModeDefault gwMode = "" - gwModeNAT gwMode = "nat" - gwModeRouted gwMode = "routed" + gwModeDefault gwMode = "" + gwModeNAT gwMode = "nat" + gwModeNATUnprot gwMode = "nat-unprotected" + gwModeRouted gwMode = "routed" ) // New constructs a new bridge driver @@ -366,6 +367,8 @@ func newGwMode(gwMode string) (gwMode, error) { switch gwMode { case "nat": return gwModeNAT, nil + case "nat-unprotected": + return gwModeNATUnprot, nil case "routed": return gwModeRouted, nil } @@ -376,6 +379,10 @@ func (m gwMode) routed() bool { return m == gwModeRouted } +func (m gwMode) unprotected() bool { + return m == gwModeNATUnprot +} + func parseErr(label, value, errString string) error { return types.InvalidParameterErrorf("failed to parse %s value: %v (%s)", label, value, errString) } @@ -428,6 +435,15 @@ func (n *bridgeNetwork) getNATDisabled() (ipv4, ipv6 bool) { return n.config.GwModeIPv4.routed(), n.config.GwModeIPv6.routed() } +func (n *bridgeNetwork) gwMode(v iptables.IPVersion) gwMode { + n.Lock() + defer n.Unlock() + if v == iptables.IPv4 { + return n.config.GwModeIPv4 + } + return n.config.GwModeIPv6 +} + func (n *bridgeNetwork) userlandProxyPath() string { n.Lock() defer n.Unlock() diff --git a/libnetwork/drivers/bridge/port_mapping_linux.go b/libnetwork/drivers/bridge/port_mapping_linux.go index 5bd6493713..0255c3c42c 100644 --- a/libnetwork/drivers/bridge/port_mapping_linux.go +++ b/libnetwork/drivers/bridge/port_mapping_linux.go @@ -776,8 +776,11 @@ func (n *bridgeNetwork) setPerPortIptables(b portBinding, enable bool) error { if err := setPerPortNAT(b, v, proxyPath, bridgeName, enable); err != nil { return err } - if err := setPerPortForwarding(b, v, bridgeName, enable); err != nil { - return err + + if !n.gwMode(v).unprotected() { + if err := setPerPortForwarding(b, v, bridgeName, enable); err != nil { + return err + } } return nil } diff --git a/libnetwork/drivers/bridge/setup_ip_tables_linux.go b/libnetwork/drivers/bridge/setup_ip_tables_linux.go index cb3d850017..17e0cc643e 100644 --- a/libnetwork/drivers/bridge/setup_ip_tables_linux.go +++ b/libnetwork/drivers/bridge/setup_ip_tables_linux.go @@ -166,14 +166,14 @@ func (n *bridgeNetwork) setupIPTables(ipVersion iptables.IPVersion, maskedAddr * if config.Internal { if err = setupInternalNetworkRules(config.BridgeName, maskedAddr, config.EnableICC, true); err != nil { - return fmt.Errorf("Failed to Setup IP tables: %s", err.Error()) + return fmt.Errorf("Failed to Setup IP tables: %w", err) } n.registerIptCleanFunc(func() error { return setupInternalNetworkRules(config.BridgeName, maskedAddr, config.EnableICC, false) }) } else { if err = setupIPTablesInternal(ipVersion, config, maskedAddr, hairpinMode, true); err != nil { - return fmt.Errorf("Failed to Setup IP tables: %s", err.Error()) + return fmt.Errorf("Failed to Setup IP tables: %w", err) } n.registerIptCleanFunc(func() error { return setupIPTablesInternal(ipVersion, config, maskedAddr, hairpinMode, false) @@ -181,28 +181,25 @@ func (n *bridgeNetwork) setupIPTables(ipVersion iptables.IPVersion, maskedAddr * natChain, filterChain, _, _, err := n.getDriverChains(ipVersion) if err != nil { - return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error()) + return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %w", err) } err = iptable.ProgramChain(natChain, config.BridgeName, hairpinMode, true) if err != nil { - return fmt.Errorf("Failed to program NAT chain: %s", err.Error()) + return fmt.Errorf("Failed to program NAT chain: %w", err) } err = iptable.ProgramChain(filterChain, config.BridgeName, hairpinMode, true) if err != nil { - return fmt.Errorf("Failed to program FILTER chain: %s", err.Error()) + return fmt.Errorf("Failed to program FILTER chain: %w", err) } n.registerIptCleanFunc(func() error { return iptable.ProgramChain(filterChain, config.BridgeName, hairpinMode, false) }) - if err := defaultDrop(ipVersion, config.BridgeName, true); err != nil { - return fmt.Errorf("failed to add default-drop rule: %s", err.Error()) + if err := n.setDefaultForwardRule(ipVersion, config.BridgeName); err != nil { + return err } - n.registerIptCleanFunc(func() error { - return defaultDrop(ipVersion, config.BridgeName, false) - }) cidr, _ := maskedAddr.Mask.Size() if cidr == 0 { @@ -252,15 +249,37 @@ func setICMP(ipv iptables.IPVersion, bridgeName string, enable bool) error { return appendOrDelChainRule(icmpRule, "ICMP", enable) } -// Append to the filter table's DOCKER chain (the default DROP rule must follow -// per-port ACCEPT rules, which will be inserted at the top of the chain). -func defaultDrop(ipv iptables.IPVersion, bridgeName string, enable bool) error { - dropRule := iptRule{ipv: ipv, table: iptables.Filter, chain: DockerChain, args: []string{ +func (n *bridgeNetwork) setDefaultForwardRule( + ipVersion iptables.IPVersion, + bridgeName string, +) error { + // Normally, DROP anything that hasn't been ACCEPTed by a per-port/protocol + // rule. This prevents direct access to un-mapped ports from remote hosts + // that can route directly to the container's address (by setting up a + // route via the host's address). + action := "DROP" + if n.gwMode(ipVersion).unprotected() { + // If the user really wants to allow all access from the wider network, + // explicitly ACCEPT anything so that the filter-FORWARD chain's + // default policy can't interfere. + action = "ACCEPT" + } + + rule := iptRule{ipv: ipVersion, table: iptables.Filter, chain: DockerChain, args: []string{ "!", "-i", bridgeName, "-o", bridgeName, - "-j", "DROP", + "-j", action, }} - return appendOrDelChainRule(dropRule, "DEFAULT DROP", enable) + + // Append to the filter table's DOCKER chain (the default rule must follow + // per-port ACCEPT rules, which will be inserted at the top of the chain). + if err := appendOrDelChainRule(rule, "DEFAULT FWD", true); err != nil { + return fmt.Errorf("failed to add default-drop rule: %w", err) + } + n.registerIptCleanFunc(func() error { + return appendOrDelChainRule(rule, "DEFAULT FWD", false) + }) + return nil } type iptRule struct { @@ -387,7 +406,7 @@ func programChainRule(rule iptRule, ruleDescr string, insert bool) error { fn = rule.Insert } if err := fn(); err != nil { - return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error()) + return fmt.Errorf("Unable to %s %s rule: %w", operation, ruleDescr, err) } return nil } @@ -400,7 +419,7 @@ func appendOrDelChainRule(rule iptRule, ruleDescr string, append bool) error { fn = rule.Append } if err := fn(); err != nil { - return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error()) + return fmt.Errorf("Unable to %s %s rule: %w", operation, ruleDescr, err) } return nil } @@ -413,12 +432,12 @@ func setIcc(version iptables.IPVersion, bridgeIface string, iccEnable, insert bo if !iccEnable { acceptRule.Delete() if err := dropRule.Append(); err != nil { - return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error()) + return fmt.Errorf("Unable to prevent intercontainer communication: %w", err) } } else { dropRule.Delete() if err := acceptRule.Insert(); err != nil { - return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error()) + return fmt.Errorf("Unable to allow intercontainer communication: %w", err) } } } else {