Browse code

Add chain DOCKER-FORWARD

In 28.0.0, Docker appended to the FORWARD chain - breaking other
applications that had appended their own rules that needed to execute
after Docker's rules.

Move most of Docker's rules out of the filter-FORWARD chain into a
new DOCKER-FORWARD chain, so that Docker can append to DOCKER-FORWARD
without affecting the order of rules in the FORWARD chain.

After daemon startup inserts jumps to DOCKER-USER and DOCKER-FORWARD,
the bridge driver will not touch the FORWARD chain again. DOCKER-INGRESS
is still added to the FORWARD chain, if used, as it was in 27.x and
earlier.

Signed-off-by: Rob Murray <rob.murray@docker.com>

Rob Murray authored on 2025/02/21 21:17:07
Showing 35 changed files
... ...
@@ -30,6 +30,7 @@ import (
30 30
 	"github.com/docker/docker/integration-cli/cli"
31 31
 	"github.com/docker/docker/integration-cli/cli/build"
32 32
 	"github.com/docker/docker/integration-cli/daemon"
33
+	"github.com/docker/docker/libnetwork/drivers/bridge"
33 34
 	"github.com/docker/docker/libnetwork/iptables"
34 35
 	"github.com/docker/docker/opts"
35 36
 	"github.com/docker/docker/testutil"
... ...
@@ -718,7 +719,7 @@ func (s *DockerDaemonSuite) TestDaemonICCPing(c *testing.T) {
718 718
 	d.StartWithBusybox(testutil.GetContext(c), c, "--bridge", bridgeName, "--icc=false")
719 719
 	defer d.Restart(c)
720 720
 
721
-	result := icmd.RunCommand("sh", "-c", "iptables -vL FORWARD | grep DROP")
721
+	result := icmd.RunCommand("sh", "-c", "iptables -vL "+bridge.DockerForwardChain+" | grep DROP")
722 722
 	result.Assert(c, icmd.Success)
723 723
 
724 724
 	// strip whitespace and newlines to verify we only found a single DROP
... ...
@@ -769,7 +770,7 @@ func (s *DockerDaemonSuite) TestDaemonICCLinkExpose(c *testing.T) {
769 769
 	d.StartWithBusybox(testutil.GetContext(c), c, "--bridge", bridgeName, "--icc=false")
770 770
 	defer d.Restart(c)
771 771
 
772
-	result := icmd.RunCommand("sh", "-c", "iptables -vL FORWARD | grep DROP")
772
+	result := icmd.RunCommand("sh", "-c", "iptables -vL "+bridge.DockerForwardChain+" | grep DROP")
773 773
 	result.Assert(c, icmd.Success)
774 774
 
775 775
 	// strip whitespace and newlines to verify we only found a single DROP
... ...
@@ -11,10 +11,7 @@ Table `filter`:
11 11
     Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
12 12
     num   pkts bytes target     prot opt in     out     source               destination         
13 13
     1        0     0 DOCKER-USER  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
14
-    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
15
-    3        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
16
-    4        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
17
-    5        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
14
+    2        0     0 DOCKER-FORWARD  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
18 15
     
19 16
     Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
20 17
     num   pkts bytes target     prot opt in     out     source               destination         
... ...
@@ -23,6 +20,13 @@ Table `filter`:
23 23
     num   pkts bytes target     prot opt in     out     source               destination         
24 24
     1        0     0 DROP       0    --  !docker0 docker0  0.0.0.0/0            0.0.0.0/0           
25 25
     
26
+    Chain DOCKER-FORWARD (1 references)
27
+    num   pkts bytes target     prot opt in     out     source               destination         
28
+    1        0     0 ACCEPT     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst ctstate RELATED,ESTABLISHED
29
+    2        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
30
+    3        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
31
+    4        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
32
+    
26 33
     Chain DOCKER-ISOLATION-STAGE-1 (1 references)
27 34
     num   pkts bytes target     prot opt in     out     source               destination         
28 35
     1        0     0 DOCKER-ISOLATION-STAGE-2  0    --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
... ...
@@ -43,15 +47,17 @@ Table `filter`:
43 43
     -P FORWARD ACCEPT
44 44
     -P OUTPUT ACCEPT
45 45
     -N DOCKER
46
+    -N DOCKER-FORWARD
46 47
     -N DOCKER-ISOLATION-STAGE-1
47 48
     -N DOCKER-ISOLATION-STAGE-2
48 49
     -N DOCKER-USER
49 50
     -A FORWARD -j DOCKER-USER
50
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
51
-    -A FORWARD -j DOCKER-ISOLATION-STAGE-1
52
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
53
-    -A FORWARD -i docker0 -j ACCEPT
51
+    -A FORWARD -j DOCKER-FORWARD
54 52
     -A DOCKER ! -i docker0 -o docker0 -j DROP
53
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
54
+    -A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
55
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
56
+    -A DOCKER-FORWARD -i docker0 -j ACCEPT
55 57
     -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
56 58
     -A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
57 59
     -A DOCKER-USER -j RETURN
... ...
@@ -76,18 +82,29 @@ The FORWARD chain rules are numbered in the output above, they are:
76 76
      Docker won't add rules to the DOCKER-USER chain, it's only for user-defined rules.
77 77
      It's (mostly) kept at the top of the by deleting it and re-creating after each
78 78
      new network is created, while traffic may be running for other networks.
79
-  2. Early ACCEPT for any RELATED,ESTABLISHED traffic to a docker bridge. This rule
79
+  2. Unconditional jump to DOCKER-FORWARD.
80
+     This is set up by libnetwork, in [setupUserChain][10].
81
+
82
+Once the daemon has initialised, it doesn't touch these rules. Users are free to
83
+append rules to the FORWARD chain, and they'll run after DOCKER's rules (or to
84
+the DOCKER-USER chain, for rules that run before DOCKER's).
85
+
86
+The DOCKER-FORWARD chain contains the first stage of Docker's filter rules. Initial
87
+rules are inserted at the top of the table, then not touched. Per-network rules
88
+are appended.
89
+
90
+  1. Early ACCEPT for any RELATED,ESTABLISHED traffic to a docker bridge. This rule
80 91
      matches against an `ipset` called `docker-ext-bridges-v4` (`v6` for IPv6). The
81 92
      set contains the CIDR address of each docker network, and it is updated as networks
82 93
      are created and deleted. This rule is created during driver initialisation, in
83 94
      `setupIPChains`.
84
-  3. Unconditional jump to DOCKER-ISOLATION-STAGE-1.
95
+  2. Unconditional jump to DOCKER-ISOLATION-STAGE-1.
85 96
      Also created during driver initialisation, in `setupIPChains`.
86
-  4. Jump to DOCKER, for any packet destined for any bridge network, identified by
97
+  3. Jump to DOCKER, for any packet destined for any bridge network, identified by
87 98
      matching against the `docker-ext-bridge-v[46]` set.
88 99
      Also created during driver initialisation, in `setupIPChains`.
89 100
      The DOCKER chain implements per-port/protocol filtering for each container.
90
-  5. ACCEPT any packet leaving a network, set up when the network is created, in
101
+  4. ACCEPT any packet leaving a network, set up when the network is created, in
91 102
      `setupIPTablesInternal`. Note that this accepts any packet leaving the
92 103
      network that's made it through the DOCKER and isolation chains, whether the
93 104
      destination is external or another network.
... ...
@@ -13,12 +13,7 @@ The filter table is:
13 13
     num   pkts bytes target     prot opt in     out     source               destination         
14 14
     1        0     0 DOCKER-USER  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
15 15
     2        0     0 DOCKER-INGRESS  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
16
-    3        0     0 ACCEPT     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst ctstate RELATED,ESTABLISHED
17
-    4        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
18
-    5        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
19
-    6        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
20
-    7        0     0 DROP       0    --  docker_gwbridge docker_gwbridge  0.0.0.0/0            0.0.0.0/0           
21
-    8        0     0 ACCEPT     0    --  docker_gwbridge !docker_gwbridge  0.0.0.0/0            0.0.0.0/0           
16
+    3        0     0 DOCKER-FORWARD  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
22 17
     
23 18
     Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
24 19
     num   pkts bytes target     prot opt in     out     source               destination         
... ...
@@ -28,6 +23,15 @@ The filter table is:
28 28
     1        0     0 DROP       0    --  !docker0 docker0  0.0.0.0/0            0.0.0.0/0           
29 29
     2        0     0 DROP       0    --  !docker_gwbridge docker_gwbridge  0.0.0.0/0            0.0.0.0/0           
30 30
     
31
+    Chain DOCKER-FORWARD (1 references)
32
+    num   pkts bytes target     prot opt in     out     source               destination         
33
+    1        0     0 ACCEPT     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst ctstate RELATED,ESTABLISHED
34
+    2        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
35
+    3        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
36
+    4        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
37
+    5        0     0 DROP       0    --  docker_gwbridge docker_gwbridge  0.0.0.0/0            0.0.0.0/0           
38
+    6        0     0 ACCEPT     0    --  docker_gwbridge !docker_gwbridge  0.0.0.0/0            0.0.0.0/0           
39
+    
31 40
     Chain DOCKER-INGRESS (1 references)
32 41
     num   pkts bytes target     prot opt in     out     source               destination         
33 42
     1        0     0 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:8080
... ...
@@ -56,20 +60,22 @@ The filter table is:
56 56
     -P FORWARD ACCEPT
57 57
     -P OUTPUT ACCEPT
58 58
     -N DOCKER
59
+    -N DOCKER-FORWARD
59 60
     -N DOCKER-INGRESS
60 61
     -N DOCKER-ISOLATION-STAGE-1
61 62
     -N DOCKER-ISOLATION-STAGE-2
62 63
     -N DOCKER-USER
63 64
     -A FORWARD -j DOCKER-USER
64 65
     -A FORWARD -j DOCKER-INGRESS
65
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
66
-    -A FORWARD -j DOCKER-ISOLATION-STAGE-1
67
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
68
-    -A FORWARD -i docker0 -j ACCEPT
69
-    -A FORWARD -i docker_gwbridge -o docker_gwbridge -j DROP
70
-    -A FORWARD -i docker_gwbridge ! -o docker_gwbridge -j ACCEPT
66
+    -A FORWARD -j DOCKER-FORWARD
71 67
     -A DOCKER ! -i docker0 -o docker0 -j DROP
72 68
     -A DOCKER ! -i docker_gwbridge -o docker_gwbridge -j DROP
69
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
70
+    -A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
71
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
72
+    -A DOCKER-FORWARD -i docker0 -j ACCEPT
73
+    -A DOCKER-FORWARD -i docker_gwbridge -o docker_gwbridge -j DROP
74
+    -A DOCKER-FORWARD -i docker_gwbridge ! -o docker_gwbridge -j ACCEPT
73 75
     -A DOCKER-INGRESS -p tcp -m tcp --dport 8080 -j ACCEPT
74 76
     -A DOCKER-INGRESS -p tcp -m tcp --sport 8080 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
75 77
     -A DOCKER-INGRESS -j RETURN
... ...
@@ -87,7 +93,7 @@ Note that:
87 87
  - There's a bridge network called `docker_gwbridge` for swarm ingress.
88 88
    - Its rules follow the usual pattern for a network with inter-container communication disabled.
89 89
 - There's an additional chain `DOCKER-INGRESS`.
90
-  - The jump to `DOCKER-INGRESS` is in the `FORWARD` chain, after the jump to `DOCKER-USER`.
90
+  - The jump to `DOCKER-INGRESS` is in the `FORWARD` chain.
91 91
 
92 92
 And the corresponding nat table:
93 93
 
... ...
@@ -26,12 +26,7 @@ The filter table is updated as follows:
26 26
     Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
27 27
     num   pkts bytes target     prot opt in     out     source               destination         
28 28
     1        0     0 DOCKER-USER  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
29
-    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
30
-    3        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
31
-    4        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
32
-    5        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
33
-    6        0     0 ACCEPT     0    --  bridgeICC bridgeICC  0.0.0.0/0            0.0.0.0/0           
34
-    7        0     0 DROP       0    --  bridgeNoICC bridgeNoICC  0.0.0.0/0            0.0.0.0/0           
29
+    2        0     0 DOCKER-FORWARD  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
35 30
     
36 31
     Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
37 32
     num   pkts bytes target     prot opt in     out     source               destination         
... ...
@@ -40,6 +35,15 @@ The filter table is updated as follows:
40 40
     num   pkts bytes target     prot opt in     out     source               destination         
41 41
     1        0     0 DROP       0    --  !docker0 docker0  0.0.0.0/0            0.0.0.0/0           
42 42
     
43
+    Chain DOCKER-FORWARD (1 references)
44
+    num   pkts bytes target     prot opt in     out     source               destination         
45
+    1        0     0 ACCEPT     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst ctstate RELATED,ESTABLISHED
46
+    2        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
47
+    3        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
48
+    4        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
49
+    5        0     0 ACCEPT     0    --  bridgeICC bridgeICC  0.0.0.0/0            0.0.0.0/0           
50
+    6        0     0 DROP       0    --  bridgeNoICC bridgeNoICC  0.0.0.0/0            0.0.0.0/0           
51
+    
43 52
     Chain DOCKER-ISOLATION-STAGE-1 (1 references)
44 53
     num   pkts bytes target     prot opt in     out     source               destination         
45 54
     1        0     0 DROP       0    --  *      bridgeNoICC !198.51.100.0/24      0.0.0.0/0           
... ...
@@ -64,17 +68,19 @@ The filter table is updated as follows:
64 64
     -P FORWARD ACCEPT
65 65
     -P OUTPUT ACCEPT
66 66
     -N DOCKER
67
+    -N DOCKER-FORWARD
67 68
     -N DOCKER-ISOLATION-STAGE-1
68 69
     -N DOCKER-ISOLATION-STAGE-2
69 70
     -N DOCKER-USER
70 71
     -A FORWARD -j DOCKER-USER
71
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
72
-    -A FORWARD -j DOCKER-ISOLATION-STAGE-1
73
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
74
-    -A FORWARD -i docker0 -j ACCEPT
75
-    -A FORWARD -i bridgeICC -o bridgeICC -j ACCEPT
76
-    -A FORWARD -i bridgeNoICC -o bridgeNoICC -j DROP
72
+    -A FORWARD -j DOCKER-FORWARD
77 73
     -A DOCKER ! -i docker0 -o docker0 -j DROP
74
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
75
+    -A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
76
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
77
+    -A DOCKER-FORWARD -i docker0 -j ACCEPT
78
+    -A DOCKER-FORWARD -i bridgeICC -o bridgeICC -j ACCEPT
79
+    -A DOCKER-FORWARD -i bridgeNoICC -o bridgeNoICC -j DROP
78 80
     -A DOCKER-ISOLATION-STAGE-1 ! -s 198.51.100.0/24 -o bridgeNoICC -j DROP
79 81
     -A DOCKER-ISOLATION-STAGE-1 ! -d 198.51.100.0/24 -i bridgeNoICC -j DROP
80 82
     -A DOCKER-ISOLATION-STAGE-1 ! -s 192.0.2.0/24 -o bridgeICC -j DROP
... ...
@@ -88,7 +94,7 @@ The filter table is updated as follows:
88 88
 
89 89
 By comparison with the [network with external access][1]:
90 90
 
91
-- In the FORWARD chain, there is no ACCEPT rule for outgoing packets (`-i bridgeINC`).
91
+- In the DOCKER-FORWARD chain, there is no ACCEPT rule for outgoing packets (`-i bridgeINC`).
92 92
 - There are no rules for this network in the DOCKER chain.
93 93
 - In DOCKER-ISOLATION-STAGE-1:
94 94
   - Rule 1 drops any packet routed to the network that does not have a source address in the network's subnet.
... ...
@@ -96,7 +102,7 @@ By comparison with the [network with external access][1]:
96 96
   - There is no jump to DOCKER-ISOLATION-STAGE-2.
97 97
 - DOCKER-ISOLATION-STAGE-2 is unused.
98 98
 
99
-The only difference between `bridgeICC` and `bridgeNoICC` is the rule in the FORWARD
99
+The only difference between `bridgeICC` and `bridgeNoICC` is the rule in the DOCKER-FORWARD
100 100
 chain. To enable ICC, the rule for packets looping through the bridge is ACCEPT. For
101 101
 no-ICC it's DROP.
102 102
 
... ...
@@ -18,11 +18,7 @@ The filter and nat tables are identical to [nat mode][0]:
18 18
     Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
19 19
     num   pkts bytes target     prot opt in     out     source               destination         
20 20
     1        0     0 DOCKER-USER  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
21
-    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
22
-    3        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
23
-    4        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
24
-    5        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
25
-    6        0     0 ACCEPT     0    --  bridge1 *       0.0.0.0/0            0.0.0.0/0           
21
+    2        0     0 DOCKER-FORWARD  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
26 22
     
27 23
     Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
28 24
     num   pkts bytes target     prot opt in     out     source               destination         
... ...
@@ -33,6 +29,14 @@ The filter and nat tables are identical to [nat mode][0]:
33 33
     2        0     0 DROP       0    --  !docker0 docker0  0.0.0.0/0            0.0.0.0/0           
34 34
     3        0     0 DROP       0    --  !bridge1 bridge1  0.0.0.0/0            0.0.0.0/0           
35 35
     
36
+    Chain DOCKER-FORWARD (1 references)
37
+    num   pkts bytes target     prot opt in     out     source               destination         
38
+    1        0     0 ACCEPT     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst ctstate RELATED,ESTABLISHED
39
+    2        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
40
+    3        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
41
+    4        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
42
+    5        0     0 ACCEPT     0    --  bridge1 *       0.0.0.0/0            0.0.0.0/0           
43
+    
36 44
     Chain DOCKER-ISOLATION-STAGE-1 (1 references)
37 45
     num   pkts bytes target     prot opt in     out     source               destination         
38 46
     1        0     0 DOCKER-ISOLATION-STAGE-2  0    --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
... ...
@@ -52,18 +56,20 @@ The filter and nat tables are identical to [nat mode][0]:
52 52
     -P FORWARD ACCEPT
53 53
     -P OUTPUT ACCEPT
54 54
     -N DOCKER
55
+    -N DOCKER-FORWARD
55 56
     -N DOCKER-ISOLATION-STAGE-1
56 57
     -N DOCKER-ISOLATION-STAGE-2
57 58
     -N DOCKER-USER
58 59
     -A FORWARD -j DOCKER-USER
59
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
60
-    -A FORWARD -j DOCKER-ISOLATION-STAGE-1
61
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
62
-    -A FORWARD -i docker0 -j ACCEPT
63
-    -A FORWARD -i bridge1 -j ACCEPT
60
+    -A FORWARD -j DOCKER-FORWARD
64 61
     -A DOCKER -d 192.0.2.2/32 ! -i bridge1 -o bridge1 -p tcp -m tcp --dport 80 -j ACCEPT
65 62
     -A DOCKER ! -i docker0 -o docker0 -j DROP
66 63
     -A DOCKER ! -i bridge1 -o bridge1 -j DROP
64
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
65
+    -A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
66
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
67
+    -A DOCKER-FORWARD -i docker0 -j ACCEPT
68
+    -A DOCKER-FORWARD -i bridge1 -j ACCEPT
67 69
     -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
68 70
     -A DOCKER-ISOLATION-STAGE-1 -i bridge1 ! -o bridge1 -j DOCKER-ISOLATION-STAGE-2
69 71
     -A DOCKER-ISOLATION-STAGE-2 -o bridge1 -j DROP
... ...
@@ -16,11 +16,7 @@ The filter table is:
16 16
     Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
17 17
     num   pkts bytes target     prot opt in     out     source               destination         
18 18
     1        0     0 DOCKER-USER  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
19
-    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
20
-    3        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
21
-    4        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
22
-    5        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
23
-    6        0     0 ACCEPT     0    --  bridge1 *       0.0.0.0/0            0.0.0.0/0           
19
+    2        0     0 DOCKER-FORWARD  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
24 20
     
25 21
     Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
26 22
     num   pkts bytes target     prot opt in     out     source               destination         
... ...
@@ -30,6 +26,14 @@ The filter table is:
30 30
     1        0     0 DROP       0    --  !docker0 docker0  0.0.0.0/0            0.0.0.0/0           
31 31
     2        0     0 ACCEPT     0    --  !bridge1 bridge1  0.0.0.0/0            0.0.0.0/0           
32 32
     
33
+    Chain DOCKER-FORWARD (1 references)
34
+    num   pkts bytes target     prot opt in     out     source               destination         
35
+    1        0     0 ACCEPT     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst ctstate RELATED,ESTABLISHED
36
+    2        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
37
+    3        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
38
+    4        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
39
+    5        0     0 ACCEPT     0    --  bridge1 *       0.0.0.0/0            0.0.0.0/0           
40
+    
33 41
     Chain DOCKER-ISOLATION-STAGE-1 (1 references)
34 42
     num   pkts bytes target     prot opt in     out     source               destination         
35 43
     1        0     0 DOCKER-ISOLATION-STAGE-2  0    --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
... ...
@@ -52,17 +56,19 @@ The filter table is:
52 52
     -P FORWARD ACCEPT
53 53
     -P OUTPUT ACCEPT
54 54
     -N DOCKER
55
+    -N DOCKER-FORWARD
55 56
     -N DOCKER-ISOLATION-STAGE-1
56 57
     -N DOCKER-ISOLATION-STAGE-2
57 58
     -N DOCKER-USER
58 59
     -A FORWARD -j DOCKER-USER
59
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
60
-    -A FORWARD -j DOCKER-ISOLATION-STAGE-1
61
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
62
-    -A FORWARD -i docker0 -j ACCEPT
63
-    -A FORWARD -i bridge1 -j ACCEPT
60
+    -A FORWARD -j DOCKER-FORWARD
64 61
     -A DOCKER ! -i docker0 -o docker0 -j DROP
65 62
     -A DOCKER ! -i bridge1 -o bridge1 -j ACCEPT
63
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
64
+    -A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
65
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
66
+    -A DOCKER-FORWARD -i docker0 -j ACCEPT
67
+    -A DOCKER-FORWARD -i bridge1 -j ACCEPT
66 68
     -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
67 69
     -A DOCKER-ISOLATION-STAGE-1 -i bridge1 ! -o bridge1 -j DOCKER-ISOLATION-STAGE-2
68 70
     -A DOCKER-ISOLATION-STAGE-2 -o bridge1 -j DROP
... ...
@@ -16,12 +16,7 @@ The filter table is:
16 16
     Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
17 17
     num   pkts bytes target     prot opt in     out     source               destination         
18 18
     1        0     0 DOCKER-USER  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
19
-    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
20
-    3        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
21
-    4        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
22
-    5        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
23
-    6        0     0 DROP       0    --  bridge1 bridge1  0.0.0.0/0            0.0.0.0/0           
24
-    7        0     0 ACCEPT     0    --  bridge1 !bridge1  0.0.0.0/0            0.0.0.0/0           
19
+    2        0     0 DOCKER-FORWARD  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
25 20
     
26 21
     Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
27 22
     num   pkts bytes target     prot opt in     out     source               destination         
... ...
@@ -32,6 +27,15 @@ The filter table is:
32 32
     2        0     0 DROP       0    --  !docker0 docker0  0.0.0.0/0            0.0.0.0/0           
33 33
     3        0     0 DROP       0    --  !bridge1 bridge1  0.0.0.0/0            0.0.0.0/0           
34 34
     
35
+    Chain DOCKER-FORWARD (1 references)
36
+    num   pkts bytes target     prot opt in     out     source               destination         
37
+    1        0     0 ACCEPT     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst ctstate RELATED,ESTABLISHED
38
+    2        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
39
+    3        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
40
+    4        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
41
+    5        0     0 DROP       0    --  bridge1 bridge1  0.0.0.0/0            0.0.0.0/0           
42
+    6        0     0 ACCEPT     0    --  bridge1 !bridge1  0.0.0.0/0            0.0.0.0/0           
43
+    
35 44
     Chain DOCKER-ISOLATION-STAGE-1 (1 references)
36 45
     num   pkts bytes target     prot opt in     out     source               destination         
37 46
     1        0     0 DOCKER-ISOLATION-STAGE-2  0    --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
... ...
@@ -54,19 +58,21 @@ The filter table is:
54 54
     -P FORWARD ACCEPT
55 55
     -P OUTPUT ACCEPT
56 56
     -N DOCKER
57
+    -N DOCKER-FORWARD
57 58
     -N DOCKER-ISOLATION-STAGE-1
58 59
     -N DOCKER-ISOLATION-STAGE-2
59 60
     -N DOCKER-USER
60 61
     -A FORWARD -j DOCKER-USER
61
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
62
-    -A FORWARD -j DOCKER-ISOLATION-STAGE-1
63
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
64
-    -A FORWARD -i docker0 -j ACCEPT
65
-    -A FORWARD -i bridge1 -o bridge1 -j DROP
66
-    -A FORWARD -i bridge1 ! -o bridge1 -j ACCEPT
62
+    -A FORWARD -j DOCKER-FORWARD
67 63
     -A DOCKER -d 192.0.2.2/32 ! -i bridge1 -o bridge1 -p tcp -m tcp --dport 80 -j ACCEPT
68 64
     -A DOCKER ! -i docker0 -o docker0 -j DROP
69 65
     -A DOCKER ! -i bridge1 -o bridge1 -j DROP
66
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
67
+    -A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
68
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
69
+    -A DOCKER-FORWARD -i docker0 -j ACCEPT
70
+    -A DOCKER-FORWARD -i bridge1 -o bridge1 -j DROP
71
+    -A DOCKER-FORWARD -i bridge1 ! -o bridge1 -j ACCEPT
70 72
     -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
71 73
     -A DOCKER-ISOLATION-STAGE-1 -i bridge1 ! -o bridge1 -j DOCKER-ISOLATION-STAGE-2
72 74
     -A DOCKER-ISOLATION-STAGE-2 -o bridge1 -j DROP
... ...
@@ -19,11 +19,7 @@ The filter table is the same as with the userland proxy enabled.
19 19
     Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
20 20
     num   pkts bytes target     prot opt in     out     source               destination         
21 21
     1        0     0 DOCKER-USER  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
22
-    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
23
-    3        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
24
-    4        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
25
-    5        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
26
-    6        0     0 ACCEPT     0    --  bridge1 *       0.0.0.0/0            0.0.0.0/0           
22
+    2        0     0 DOCKER-FORWARD  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
27 23
     
28 24
     Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
29 25
     num   pkts bytes target     prot opt in     out     source               destination         
... ...
@@ -34,6 +30,14 @@ The filter table is the same as with the userland proxy enabled.
34 34
     2        0     0 DROP       0    --  !docker0 docker0  0.0.0.0/0            0.0.0.0/0           
35 35
     3        0     0 DROP       0    --  !bridge1 bridge1  0.0.0.0/0            0.0.0.0/0           
36 36
     
37
+    Chain DOCKER-FORWARD (1 references)
38
+    num   pkts bytes target     prot opt in     out     source               destination         
39
+    1        0     0 ACCEPT     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst ctstate RELATED,ESTABLISHED
40
+    2        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
41
+    3        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
42
+    4        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
43
+    5        0     0 ACCEPT     0    --  bridge1 *       0.0.0.0/0            0.0.0.0/0           
44
+    
37 45
     Chain DOCKER-ISOLATION-STAGE-1 (1 references)
38 46
     num   pkts bytes target     prot opt in     out     source               destination         
39 47
     1        0     0 DOCKER-ISOLATION-STAGE-2  0    --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
... ...
@@ -53,18 +57,20 @@ The filter table is the same as with the userland proxy enabled.
53 53
     -P FORWARD ACCEPT
54 54
     -P OUTPUT ACCEPT
55 55
     -N DOCKER
56
+    -N DOCKER-FORWARD
56 57
     -N DOCKER-ISOLATION-STAGE-1
57 58
     -N DOCKER-ISOLATION-STAGE-2
58 59
     -N DOCKER-USER
59 60
     -A FORWARD -j DOCKER-USER
60
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
61
-    -A FORWARD -j DOCKER-ISOLATION-STAGE-1
62
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
63
-    -A FORWARD -i docker0 -j ACCEPT
64
-    -A FORWARD -i bridge1 -j ACCEPT
61
+    -A FORWARD -j DOCKER-FORWARD
65 62
     -A DOCKER -d 192.0.2.2/32 ! -i bridge1 -o bridge1 -p tcp -m tcp --dport 80 -j ACCEPT
66 63
     -A DOCKER ! -i docker0 -o docker0 -j DROP
67 64
     -A DOCKER ! -i bridge1 -o bridge1 -j DROP
65
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
66
+    -A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
67
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
68
+    -A DOCKER-FORWARD -i docker0 -j ACCEPT
69
+    -A DOCKER-FORWARD -i bridge1 -j ACCEPT
68 70
     -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
69 71
     -A DOCKER-ISOLATION-STAGE-1 -i bridge1 ! -o bridge1 -j DOCKER-ISOLATION-STAGE-2
70 72
     -A DOCKER-ISOLATION-STAGE-2 -o bridge1 -j DROP
... ...
@@ -16,11 +16,7 @@ The filter table is:
16 16
     Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
17 17
     num   pkts bytes target     prot opt in     out     source               destination         
18 18
     1        0     0 DOCKER-USER  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
19
-    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
20
-    3        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
21
-    4        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
22
-    5        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
23
-    6        0     0 ACCEPT     0    --  bridge1 *       0.0.0.0/0            0.0.0.0/0           
19
+    2        0     0 DOCKER-FORWARD  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
24 20
     
25 21
     Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
26 22
     num   pkts bytes target     prot opt in     out     source               destination         
... ...
@@ -32,6 +28,14 @@ The filter table is:
32 32
     3        0     0 ACCEPT     1    --  *      bridge1  0.0.0.0/0            0.0.0.0/0           
33 33
     4        0     0 DROP       0    --  !bridge1 bridge1  0.0.0.0/0            0.0.0.0/0           
34 34
     
35
+    Chain DOCKER-FORWARD (1 references)
36
+    num   pkts bytes target     prot opt in     out     source               destination         
37
+    1        0     0 ACCEPT     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst ctstate RELATED,ESTABLISHED
38
+    2        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
39
+    3        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
40
+    4        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
41
+    5        0     0 ACCEPT     0    --  bridge1 *       0.0.0.0/0            0.0.0.0/0           
42
+    
35 43
     Chain DOCKER-ISOLATION-STAGE-1 (1 references)
36 44
     num   pkts bytes target     prot opt in     out     source               destination         
37 45
     1        0     0 ACCEPT     0    --  bridge1 *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
... ...
@@ -56,19 +60,21 @@ The filter table is:
56 56
     -P FORWARD ACCEPT
57 57
     -P OUTPUT ACCEPT
58 58
     -N DOCKER
59
+    -N DOCKER-FORWARD
59 60
     -N DOCKER-ISOLATION-STAGE-1
60 61
     -N DOCKER-ISOLATION-STAGE-2
61 62
     -N DOCKER-USER
62 63
     -A FORWARD -j DOCKER-USER
63
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
64
-    -A FORWARD -j DOCKER-ISOLATION-STAGE-1
65
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
66
-    -A FORWARD -i docker0 -j ACCEPT
67
-    -A FORWARD -i bridge1 -j ACCEPT
64
+    -A FORWARD -j DOCKER-FORWARD
68 65
     -A DOCKER -d 192.0.2.2/32 ! -i bridge1 -o bridge1 -p tcp -m tcp --dport 80 -j ACCEPT
69 66
     -A DOCKER ! -i docker0 -o docker0 -j DROP
70 67
     -A DOCKER -o bridge1 -p icmp -j ACCEPT
71 68
     -A DOCKER ! -i bridge1 -o bridge1 -j DROP
69
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
70
+    -A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
71
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
72
+    -A DOCKER-FORWARD -i docker0 -j ACCEPT
73
+    -A DOCKER-FORWARD -i bridge1 -j ACCEPT
72 74
     -A DOCKER-ISOLATION-STAGE-1 -i bridge1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
73 75
     -A DOCKER-ISOLATION-STAGE-1 -o bridge1 -j RETURN
74 76
     -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
... ...
@@ -15,11 +15,7 @@ The filter table is updated as follows:
15 15
     Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
16 16
     num   pkts bytes target     prot opt in     out     source               destination         
17 17
     1        0     0 DOCKER-USER  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
18
-    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
19
-    3        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
20
-    4        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
21
-    5        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
22
-    6        0     0 ACCEPT     0    --  bridge1 *       0.0.0.0/0            0.0.0.0/0           
18
+    2        0     0 DOCKER-FORWARD  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
23 19
     
24 20
     Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
25 21
     num   pkts bytes target     prot opt in     out     source               destination         
... ...
@@ -30,6 +26,14 @@ The filter table is updated as follows:
30 30
     2        0     0 DROP       0    --  !docker0 docker0  0.0.0.0/0            0.0.0.0/0           
31 31
     3        0     0 DROP       0    --  !bridge1 bridge1  0.0.0.0/0            0.0.0.0/0           
32 32
     
33
+    Chain DOCKER-FORWARD (1 references)
34
+    num   pkts bytes target     prot opt in     out     source               destination         
35
+    1        0     0 ACCEPT     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst ctstate RELATED,ESTABLISHED
36
+    2        0     0 DOCKER-ISOLATION-STAGE-1  0    --  *      *       0.0.0.0/0            0.0.0.0/0           
37
+    3        0     0 DOCKER     0    --  *      *       0.0.0.0/0            0.0.0.0/0            match-set docker-ext-bridges-v4 dst
38
+    4        0     0 ACCEPT     0    --  docker0 *       0.0.0.0/0            0.0.0.0/0           
39
+    5        0     0 ACCEPT     0    --  bridge1 *       0.0.0.0/0            0.0.0.0/0           
40
+    
33 41
     Chain DOCKER-ISOLATION-STAGE-1 (1 references)
34 42
     num   pkts bytes target     prot opt in     out     source               destination         
35 43
     1        0     0 DOCKER-ISOLATION-STAGE-2  0    --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
... ...
@@ -52,18 +56,20 @@ The filter table is updated as follows:
52 52
     -P FORWARD ACCEPT
53 53
     -P OUTPUT ACCEPT
54 54
     -N DOCKER
55
+    -N DOCKER-FORWARD
55 56
     -N DOCKER-ISOLATION-STAGE-1
56 57
     -N DOCKER-ISOLATION-STAGE-2
57 58
     -N DOCKER-USER
58 59
     -A FORWARD -j DOCKER-USER
59
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
60
-    -A FORWARD -j DOCKER-ISOLATION-STAGE-1
61
-    -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
62
-    -A FORWARD -i docker0 -j ACCEPT
63
-    -A FORWARD -i bridge1 -j ACCEPT
60
+    -A FORWARD -j DOCKER-FORWARD
64 61
     -A DOCKER -d 192.0.2.2/32 ! -i bridge1 -o bridge1 -p tcp -m tcp --dport 80 -j ACCEPT
65 62
     -A DOCKER ! -i docker0 -o docker0 -j DROP
66 63
     -A DOCKER ! -i bridge1 -o bridge1 -j DROP
64
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
65
+    -A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
66
+    -A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
67
+    -A DOCKER-FORWARD -i docker0 -j ACCEPT
68
+    -A DOCKER-FORWARD -i bridge1 -j ACCEPT
67 69
     -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
68 70
     -A DOCKER-ISOLATION-STAGE-1 -i bridge1 ! -o bridge1 -j DOCKER-ISOLATION-STAGE-2
69 71
     -A DOCKER-ISOLATION-STAGE-2 -o bridge1 -j DROP
... ...
@@ -75,7 +81,7 @@ The filter table is updated as follows:
75 75
 
76 76
 Note that:
77 77
 
78
- - In the FORWARD chain, rule 6 for outgoing traffic from the new network has been
78
+ - In the DOCKER-FORWARD chain, rule 5 for outgoing traffic from the new network has been
79 79
    appended to the end of the chain.
80 80
  - In the DOCKER-ISOLATION chains, rules equivalent to the docker0 rules have
81 81
    also been inserted for the new bridge.
... ...
@@ -193,27 +193,25 @@ var index = []section{
193 193
 type iptCmdType = string
194 194
 
195 195
 const (
196
-	iptCmdLFilter4        iptCmdType = "LFilter4"
197
-	iptCmdSFilter4        iptCmdType = "SFilter4"
198
-	iptCmdLFilterDocker4  iptCmdType = "LFilterDocker4"
199
-	iptCmdSFilterForward4 iptCmdType = "SFilterForward4"
200
-	iptCmdSFilterDocker4  iptCmdType = "SFilterDocker4"
201
-	iptCmdLNat4           iptCmdType = "LNat4"
202
-	iptCmdSNat4           iptCmdType = "SNat4"
203
-	iptCmdLRaw4           iptCmdType = "LRaw4"
204
-	iptCmdSRaw4           iptCmdType = "SRaw4"
196
+	iptCmdLFilter4       iptCmdType = "LFilter4"
197
+	iptCmdSFilter4       iptCmdType = "SFilter4"
198
+	iptCmdLFilterDocker4 iptCmdType = "LFilterDocker4"
199
+	iptCmdSFilterDocker4 iptCmdType = "SFilterDocker4"
200
+	iptCmdLNat4          iptCmdType = "LNat4"
201
+	iptCmdSNat4          iptCmdType = "SNat4"
202
+	iptCmdLRaw4          iptCmdType = "LRaw4"
203
+	iptCmdSRaw4          iptCmdType = "SRaw4"
205 204
 )
206 205
 
207 206
 var iptCmds = map[iptCmdType][]string{
208
-	iptCmdLFilter4:        {"iptables", "-nvL", "--line-numbers", "-t", "filter"},
209
-	iptCmdSFilter4:        {"iptables", "-S", "-t", "filter"},
210
-	iptCmdSFilterForward4: {"iptables", "-S", "FORWARD"},
211
-	iptCmdLFilterDocker4:  {"iptables", "-nvL", "DOCKER", "--line-numbers", "-t", "filter"},
212
-	iptCmdSFilterDocker4:  {"iptables", "-S", "DOCKER"},
213
-	iptCmdLNat4:           {"iptables", "-nvL", "--line-numbers", "-t", "nat"},
214
-	iptCmdSNat4:           {"iptables", "-S", "-t", "nat"},
215
-	iptCmdLRaw4:           {"iptables", "-nvL", "--line-numbers", "-t", "raw"},
216
-	iptCmdSRaw4:           {"iptables", "-S", "-t", "raw"},
207
+	iptCmdLFilter4:       {"iptables", "-nvL", "--line-numbers", "-t", "filter"},
208
+	iptCmdSFilter4:       {"iptables", "-S", "-t", "filter"},
209
+	iptCmdLFilterDocker4: {"iptables", "-nvL", "DOCKER", "--line-numbers", "-t", "filter"},
210
+	iptCmdSFilterDocker4: {"iptables", "-S", "DOCKER"},
211
+	iptCmdLNat4:          {"iptables", "-nvL", "--line-numbers", "-t", "nat"},
212
+	iptCmdSNat4:          {"iptables", "-S", "-t", "nat"},
213
+	iptCmdLRaw4:          {"iptables", "-nvL", "--line-numbers", "-t", "raw"},
214
+	iptCmdSRaw4:          {"iptables", "-S", "-t", "raw"},
217 215
 }
218 216
 
219 217
 func TestBridgeIptablesDoc(t *testing.T) {
... ...
@@ -31,18 +31,29 @@ The FORWARD chain rules are numbered in the output above, they are:
31 31
      Docker won't add rules to the DOCKER-USER chain, it's only for user-defined rules.
32 32
      It's (mostly) kept at the top of the by deleting it and re-creating after each
33 33
      new network is created, while traffic may be running for other networks.
34
-  2. Early ACCEPT for any RELATED,ESTABLISHED traffic to a docker bridge. This rule
34
+  2. Unconditional jump to DOCKER-FORWARD.
35
+     This is set up by libnetwork, in [setupUserChain][10].
36
+
37
+Once the daemon has initialised, it doesn't touch these rules. Users are free to
38
+append rules to the FORWARD chain, and they'll run after DOCKER's rules (or to
39
+the DOCKER-USER chain, for rules that run before DOCKER's).
40
+
41
+The DOCKER-FORWARD chain contains the first stage of Docker's filter rules. Initial
42
+rules are inserted at the top of the table, then not touched. Per-network rules
43
+are appended.
44
+
45
+  1. Early ACCEPT for any RELATED,ESTABLISHED traffic to a docker bridge. This rule
35 46
      matches against an `ipset` called `docker-ext-bridges-v4` (`v6` for IPv6). The
36 47
      set contains the CIDR address of each docker network, and it is updated as networks
37 48
      are created and deleted. This rule is created during driver initialisation, in
38 49
      `setupIPChains`.
39
-  3. Unconditional jump to DOCKER-ISOLATION-STAGE-1.
50
+  2. Unconditional jump to DOCKER-ISOLATION-STAGE-1.
40 51
      Also created during driver initialisation, in `setupIPChains`.
41
-  4. Jump to DOCKER, for any packet destined for any bridge network, identified by
52
+  3. Jump to DOCKER, for any packet destined for any bridge network, identified by
42 53
      matching against the `docker-ext-bridge-v[46]` set.
43 54
      Also created during driver initialisation, in `setupIPChains`.
44 55
      The DOCKER chain implements per-port/protocol filtering for each container.
45
-  5. ACCEPT any packet leaving a network, set up when the network is created, in
56
+  4. ACCEPT any packet leaving a network, set up when the network is created, in
46 57
      `setupIPTablesInternal`. Note that this accepts any packet leaving the
47 58
      network that's made it through the DOCKER and isolation chains, whether the
48 59
      destination is external or another network.
... ...
@@ -20,7 +20,7 @@ Note that:
20 20
  - There's a bridge network called `docker_gwbridge` for swarm ingress.
21 21
    - Its rules follow the usual pattern for a network with inter-container communication disabled.
22 22
 - There's an additional chain `DOCKER-INGRESS`.
23
-  - The jump to `DOCKER-INGRESS` is in the `FORWARD` chain, after the jump to `DOCKER-USER`.
23
+  - The jump to `DOCKER-INGRESS` is in the `FORWARD` chain.
24 24
 
25 25
 And the corresponding nat table:
26 26
 
... ...
@@ -31,7 +31,7 @@ The filter table is updated as follows:
31 31
 
32 32
 By comparison with the [network with external access][1]:
33 33
 
34
-- In the FORWARD chain, there is no ACCEPT rule for outgoing packets (`-i bridgeINC`).
34
+- In the DOCKER-FORWARD chain, there is no ACCEPT rule for outgoing packets (`-i bridgeINC`).
35 35
 - There are no rules for this network in the DOCKER chain.
36 36
 - In DOCKER-ISOLATION-STAGE-1:
37 37
   - Rule 1 drops any packet routed to the network that does not have a source address in the network's subnet.
... ...
@@ -39,7 +39,7 @@ By comparison with the [network with external access][1]:
39 39
   - There is no jump to DOCKER-ISOLATION-STAGE-2.
40 40
 - DOCKER-ISOLATION-STAGE-2 is unused.
41 41
 
42
-The only difference between `bridgeICC` and `bridgeNoICC` is the rule in the FORWARD
42
+The only difference between `bridgeICC` and `bridgeNoICC` is the rule in the DOCKER-FORWARD
43 43
 chain. To enable ICC, the rule for packets looping through the bridge is ACCEPT. For
44 44
 no-ICC it's DROP.
45 45
 
... ...
@@ -20,7 +20,7 @@ The filter table is updated as follows:
20 20
 
21 21
 Note that:
22 22
 
23
- - In the FORWARD chain, rule 6 for outgoing traffic from the new network has been
23
+ - In the DOCKER-FORWARD chain, rule 5 for outgoing traffic from the new network has been
24 24
    appended to the end of the chain.
25 25
  - In the DOCKER-ISOLATION chains, rules equivalent to the docker0 rules have
26 26
    also been inserted for the new bridge.
... ...
@@ -18,7 +18,8 @@ import (
18 18
 
19 19
 // DockerChain: DOCKER iptable chain name
20 20
 const (
21
-	DockerChain = "DOCKER"
21
+	DockerChain        = "DOCKER"
22
+	DockerForwardChain = "DOCKER-FORWARD"
22 23
 
23 24
 	// Isolation between bridge networks is achieved in two stages by means
24 25
 	// of the following two chains in the filter table. The first chain matches
... ...
@@ -79,6 +80,18 @@ func setupIPChains(config configuration, version iptables.IPVersion) (retErr err
79 79
 		}
80 80
 	}()
81 81
 
82
+	_, err = iptable.NewChain(DockerForwardChain, iptables.Filter)
83
+	if err != nil {
84
+		return fmt.Errorf("failed to create FILTER chain %s: %v", DockerForwardChain, err)
85
+	}
86
+	defer func() {
87
+		if retErr != nil {
88
+			if err := iptable.RemoveExistingChain(DockerForwardChain, iptables.Filter); err != nil {
89
+				log.G(context.TODO()).Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", DockerForwardChain, err)
90
+			}
91
+		}
92
+	}()
93
+
82 94
 	_, err = iptable.NewChain(IsolationChain1, iptables.Filter)
83 95
 	if err != nil {
84 96
 		return fmt.Errorf("failed to create FILTER isolation chain: %v", err)
... ...
@@ -121,14 +134,32 @@ func setupIPChains(config configuration, version iptables.IPVersion) (retErr err
121 121
 	if version == iptables.IPv6 {
122 122
 		ipsetName = ipsetExtBridges6
123 123
 	}
124
-	if err := iptable.EnsureJumpRule("FORWARD", DockerChain,
124
+	// Delete rules that may have been added to the FORWARD chain by moby 28.0.0.
125
+	if err := iptable.DeleteJumpRule("FORWARD", DockerChain,
125 126
 		"-m", "set", "--match-set", ipsetName, "dst"); err != nil {
126 127
 		return fmt.Errorf("%w (kernel module netfilter_xt_set is required)", err)
127 128
 	}
128
-	if err := iptable.EnsureJumpRule("FORWARD", IsolationChain1); err != nil {
129
+	if err := iptable.DeleteJumpRule("FORWARD", IsolationChain1); err != nil {
129 130
 		return err
130 131
 	}
131
-	if err := iptable.EnsureJumpRule("FORWARD", "ACCEPT",
132
+	if err := iptable.DeleteJumpRule("FORWARD", "ACCEPT",
133
+		"-m", "set", "--match-set", ipsetName, "dst",
134
+		"-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED",
135
+	); err != nil {
136
+		return err
137
+	}
138
+	// Create rules in the DockerForward chain.
139
+	if err := iptable.EnsureJumpRule("FORWARD", DockerForwardChain); err != nil {
140
+		return err
141
+	}
142
+	if err := iptable.EnsureJumpRule(DockerForwardChain, DockerChain,
143
+		"-m", "set", "--match-set", ipsetName, "dst"); err != nil {
144
+		return err
145
+	}
146
+	if err := iptable.EnsureJumpRule(DockerForwardChain, IsolationChain1); err != nil {
147
+		return err
148
+	}
149
+	if err := iptable.EnsureJumpRule(DockerForwardChain, "ACCEPT",
132 150
 		"-m", "set", "--match-set", ipsetName, "dst",
133 151
 		"-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED",
134 152
 	); err != nil {
... ...
@@ -452,25 +483,32 @@ func setupNonInternalNetworkRules(ipVer iptables.IPVersion, config *networkConfi
452 452
 	// to ACCEPT packets that weren't ICC - an extra rule was needed to enable
453 453
 	// ICC if needed. Those rules are now combined. So, outRuleNoICC is only
454 454
 	// needed for ICC=false, along with the DROP rule for ICC added by setIcc.
455
-	outRuleNoICC := iptables.Rule{IPVer: ipVer, Table: iptables.Filter, Chain: "FORWARD", Args: []string{
455
+	outRuleNoICC := iptables.Rule{IPVer: ipVer, Table: iptables.Filter, Chain: DockerForwardChain, Args: []string{
456 456
 		"-i", config.BridgeName,
457 457
 		"!", "-o", config.BridgeName,
458 458
 		"-j", "ACCEPT",
459 459
 	}}
460
-	if config.EnableICC {
461
-		// Remove the legacy rule for ICC (which didn't accept outgoing traffic), if one has been
462
-		// left behind by an old daemon.
463
-		if err := outRuleNoICC.Delete(); err != nil {
464
-			return err
460
+	// If there's a version of outRuleNoICC in the FORWARD chain, created by moby 28.0.0 or older, delete it.
461
+	if enable {
462
+		if err := outRuleNoICC.WithChain("FORWARD").Delete(); err != nil {
463
+			return fmt.Errorf("deleting FORWARD chain outRuleNoICC: %w", err)
465 464
 		}
465
+	}
466
+	if config.EnableICC {
466 467
 		// Accept outgoing traffic to anywhere, including other containers on this bridge.
467
-		outRuleICC := iptables.Rule{IPVer: ipVer, Table: iptables.Filter, Chain: "FORWARD", Args: []string{
468
+		outRuleICC := iptables.Rule{IPVer: ipVer, Table: iptables.Filter, Chain: DockerForwardChain, Args: []string{
468 469
 			"-i", config.BridgeName,
469 470
 			"-j", "ACCEPT",
470 471
 		}}
471 472
 		if err := appendOrDelChainRule(outRuleICC, "ACCEPT OUTGOING", enable); err != nil {
472 473
 			return err
473 474
 		}
475
+		// If there's a version of outRuleICC in the FORWARD chain, created by moby 28.0.0 or older, delete it.
476
+		if enable {
477
+			if err := outRuleICC.WithChain("FORWARD").Delete(); err != nil {
478
+				return fmt.Errorf("deleting FORWARD chain outRuleICC: %w", err)
479
+			}
480
+		}
474 481
 	} else {
475 482
 		// Accept outgoing traffic to anywhere, apart from other containers on this bridge.
476 483
 		// setIcc added a DROP rule for ICC traffic.
... ...
@@ -510,8 +548,8 @@ func appendOrDelChainRule(rule iptables.Rule, ruleDescr string, append bool) err
510 510
 
511 511
 func setIcc(version iptables.IPVersion, bridgeIface string, iccEnable, internal, insert bool) error {
512 512
 	args := []string{"-i", bridgeIface, "-o", bridgeIface, "-j"}
513
-	acceptRule := iptables.Rule{IPVer: version, Table: iptables.Filter, Chain: "FORWARD", Args: append(args, "ACCEPT")}
514
-	dropRule := iptables.Rule{IPVer: version, Table: iptables.Filter, Chain: "FORWARD", Args: append(args, "DROP")}
513
+	acceptRule := iptables.Rule{IPVer: version, Table: iptables.Filter, Chain: DockerForwardChain, Args: append(args, "ACCEPT")}
514
+	dropRule := iptables.Rule{IPVer: version, Table: iptables.Filter, Chain: DockerForwardChain, Args: append(args, "DROP")}
515 515
 
516 516
 	// The accept rule is no longer required for a bridge with external connectivity, because
517 517
 	// ICC traffic is allowed by the outgoing-packets rule created by setupIptablesInternal.
... ...
@@ -537,6 +575,16 @@ func setIcc(version iptables.IPVersion, bridgeIface string, iccEnable, internal,
537 537
 			log.G(context.TODO()).WithError(err).Warn("Failed to delete ICC drop rule")
538 538
 		}
539 539
 	}
540
+
541
+	// Delete rules that may have been inserted into the FORWARD chain by moby 28.0.0 or older.
542
+	if insert {
543
+		if err := acceptRule.WithChain("FORWARD").Delete(); err != nil {
544
+			return fmt.Errorf("deleting FORWARD chain accept rule: %w", err)
545
+		}
546
+		if err := dropRule.WithChain("FORWARD").Delete(); err != nil {
547
+			return fmt.Errorf("deleting FORWARD chain drop rule: %w", err)
548
+		}
549
+	}
540 550
 	return nil
541 551
 }
542 552
 
... ...
@@ -617,6 +665,7 @@ func removeIPChains(version iptables.IPVersion) {
617 617
 	for _, chainInfo := range []iptables.ChainInfo{
618 618
 		{Name: DockerChain, Table: iptables.Nat, IPVersion: version},
619 619
 		{Name: DockerChain, Table: iptables.Filter, IPVersion: version},
620
+		{Name: DockerForwardChain, Table: iptables.Filter, IPVersion: version},
620 621
 		{Name: IsolationChain1, Table: iptables.Filter, IPVersion: version},
621 622
 		{Name: IsolationChain2, Table: iptables.Filter, IPVersion: version},
622 623
 		{Name: oldIsolationChain, Table: iptables.Filter, IPVersion: version},
... ...
@@ -53,18 +53,20 @@ func TestProgramIPTable(t *testing.T) {
53 53
 	}
54 54
 
55 55
 	createTestBridge(getBasicTestConfig(), &bridgeInterface{nlh: nh}, t)
56
+	_, err = iptables.GetIptable(iptables.IPv4).NewChain(DockerForwardChain, iptables.Filter)
57
+	assert.NilError(t, err)
56 58
 
57 59
 	// Store various iptables chain rules we care for.
58 60
 	rules := []struct {
59 61
 		rule  iptables.Rule
60 62
 		descr string
61 63
 	}{
62
-		{iptables.Rule{IPVer: iptables.IPv4, Table: iptables.Filter, Chain: "FORWARD", Args: []string{"-d", "127.1.2.3", "-i", "lo", "-o", "lo", "-j", "DROP"}}, "Test Loopback"},
64
+		{iptables.Rule{IPVer: iptables.IPv4, Table: iptables.Filter, Chain: DockerForwardChain, Args: []string{"-d", "127.1.2.3", "-i", "lo", "-o", "lo", "-j", "DROP"}}, "Test Loopback"},
63 65
 		{iptables.Rule{IPVer: iptables.IPv4, Table: iptables.Nat, Chain: "POSTROUTING", Args: []string{"-s", iptablesTestBridgeIP, "!", "-o", DefaultBridgeName, "-j", "MASQUERADE"}}, "NAT Test"},
64
-		{iptables.Rule{IPVer: iptables.IPv4, Table: iptables.Filter, Chain: "FORWARD", Args: []string{"-o", DefaultBridgeName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}}, "Test ACCEPT INCOMING"},
65
-		{iptables.Rule{IPVer: iptables.IPv4, Table: iptables.Filter, Chain: "FORWARD", Args: []string{"-i", DefaultBridgeName, "!", "-o", DefaultBridgeName, "-j", "ACCEPT"}}, "Test ACCEPT NON_ICC OUTGOING"},
66
-		{iptables.Rule{IPVer: iptables.IPv4, Table: iptables.Filter, Chain: "FORWARD", Args: []string{"-i", DefaultBridgeName, "-o", DefaultBridgeName, "-j", "ACCEPT"}}, "Test enable ICC"},
67
-		{iptables.Rule{IPVer: iptables.IPv4, Table: iptables.Filter, Chain: "FORWARD", Args: []string{"-i", DefaultBridgeName, "-o", DefaultBridgeName, "-j", "DROP"}}, "Test disable ICC"},
66
+		{iptables.Rule{IPVer: iptables.IPv4, Table: iptables.Filter, Chain: DockerForwardChain, Args: []string{"-o", DefaultBridgeName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}}, "Test ACCEPT INCOMING"},
67
+		{iptables.Rule{IPVer: iptables.IPv4, Table: iptables.Filter, Chain: DockerForwardChain, Args: []string{"-i", DefaultBridgeName, "!", "-o", DefaultBridgeName, "-j", "ACCEPT"}}, "Test ACCEPT NON_ICC OUTGOING"},
68
+		{iptables.Rule{IPVer: iptables.IPv4, Table: iptables.Filter, Chain: DockerForwardChain, Args: []string{"-i", DefaultBridgeName, "-o", DefaultBridgeName, "-j", "ACCEPT"}}, "Test enable ICC"},
69
+		{iptables.Rule{IPVer: iptables.IPv4, Table: iptables.Filter, Chain: DockerForwardChain, Args: []string{"-i", DefaultBridgeName, "-o", DefaultBridgeName, "-j", "DROP"}}, "Test disable ICC"},
68 70
 	}
69 71
 
70 72
 	// Assert the chain rules' insertion and removal.
... ...
@@ -138,6 +140,8 @@ func createTestBridge(config *networkConfiguration, br *bridgeInterface, t *test
138 138
 
139 139
 // Assert base function which pushes iptables chain rules on insertion and removal.
140 140
 func assertIPTableChainProgramming(rule iptables.Rule, descr string, t *testing.T) {
141
+	t.Helper()
142
+
141 143
 	// Add
142 144
 	if err := programChainRule(rule, descr, true); err != nil {
143 145
 		t.Fatalf("Failed to program iptable rule %s: %s", descr, err.Error())
... ...
@@ -4,6 +4,8 @@ import (
4 4
 	"fmt"
5 5
 	"testing"
6 6
 
7
+	"github.com/docker/docker/libnetwork/drivers/bridge"
8
+
7 9
 	"github.com/docker/docker/internal/testutils/netnsutils"
8 10
 	"github.com/docker/docker/libnetwork/config"
9 11
 	"github.com/docker/docker/libnetwork/iptables"
... ...
@@ -62,6 +64,15 @@ func TestUserChain(t *testing.T) {
62 62
 				fmt.Sprintf("TestUserChain_iptables-%v_append-%v_fwdinit4", tc.iptables, tc.append))
63 63
 			golden.Assert(t, getRules(t, iptable6, fwdChainName),
64 64
 				fmt.Sprintf("TestUserChain_iptables-%v_append-%v_fwdinit6", tc.iptables, tc.append))
65
+			if tc.iptables {
66
+				golden.Assert(t, getRules(t, iptable4, bridge.DockerForwardChain),
67
+					fmt.Sprintf("TestUserChain_iptables-%v_append-%v_dockerfwdinit4", tc.iptables, tc.append))
68
+				golden.Assert(t, getRules(t, iptable6, bridge.DockerForwardChain),
69
+					fmt.Sprintf("TestUserChain_iptables-%v_append-%v_dockerfwdinit6", tc.iptables, tc.append))
70
+			} else {
71
+				assert.Check(t, !iptables.GetIptable(iptables.IPv4).ExistChain(bridge.DockerForwardChain, fwdChainName),
72
+					"Chain %s should not exist", bridge.DockerForwardChain)
73
+			}
65 74
 
66 75
 			if tc.append {
67 76
 				_, err := iptable4.Raw("-A", fwdChainName, "-j", "DROP")
... ...
@@ -75,6 +86,15 @@ func TestUserChain(t *testing.T) {
75 75
 				fmt.Sprintf("TestUserChain_iptables-%v_append-%v_fwdafter4", tc.iptables, tc.append))
76 76
 			golden.Assert(t, getRules(t, iptable6, fwdChainName),
77 77
 				fmt.Sprintf("TestUserChain_iptables-%v_append-%v_fwdafter6", tc.iptables, tc.append))
78
+			if tc.iptables {
79
+				golden.Assert(t, getRules(t, iptable4, bridge.DockerForwardChain),
80
+					fmt.Sprintf("TestUserChain_iptables-%v_append-%v_dockerfwdafter4", tc.iptables, tc.append))
81
+				golden.Assert(t, getRules(t, iptable6, bridge.DockerForwardChain),
82
+					fmt.Sprintf("TestUserChain_iptables-%v_append-%v_dockerfwdafter6", tc.iptables, tc.append))
83
+			} else {
84
+				assert.Check(t, !iptables.GetIptable(iptables.IPv4).ExistChain(bridge.DockerForwardChain, fwdChainName),
85
+					"Chain %s should not exist", bridge.DockerForwardChain)
86
+			}
78 87
 
79 88
 			if tc.iptables {
80 89
 				golden.Assert(t, getRules(t, iptable4, usrChainName),
... ...
@@ -1,4 +1,5 @@
1
-//go:build linux
1
+// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
2
+//go:build go1.22 && linux
2 3
 
3 4
 package iptables
4 5
 
... ...
@@ -8,6 +9,7 @@ import (
8 8
 	"fmt"
9 9
 	"net"
10 10
 	"os/exec"
11
+	"slices"
11 12
 	"strconv"
12 13
 	"strings"
13 14
 	"sync"
... ...
@@ -415,15 +417,25 @@ func (iptable IPTable) AddReturnRule(chain string) error {
415 415
 
416 416
 // EnsureJumpRule ensures the jump rule is on top
417 417
 func (iptable IPTable) EnsureJumpRule(fromChain, toChain string, rule ...string) error {
418
+	if err := iptable.DeleteJumpRule(fromChain, toChain, rule...); err != nil {
419
+		return err
420
+	}
421
+	rule = append(rule, "-j", toChain)
422
+	if err := iptable.RawCombinedOutput(append([]string{"-I", fromChain}, rule...)...); err != nil {
423
+		return fmt.Errorf("unable to insert jump to %s rule in %s chain: %v", toChain, fromChain, err)
424
+	}
425
+	return nil
426
+}
427
+
428
+// DeleteJumpRule deletes a rule added by EnsureJumpRule. It's a no-op if the rule
429
+// doesn't exist.
430
+func (iptable IPTable) DeleteJumpRule(fromChain, toChain string, rule ...string) error {
418 431
 	rule = append(rule, "-j", toChain)
419 432
 	if iptable.Exists(Filter, fromChain, rule...) {
420 433
 		if err := iptable.RawCombinedOutput(append([]string{"-D", fromChain}, rule...)...); err != nil {
421 434
 			return fmt.Errorf("unable to remove jump to %s rule in %s chain: %v", toChain, fromChain, err)
422 435
 		}
423 436
 	}
424
-	if err := iptable.RawCombinedOutput(append([]string{"-I", fromChain}, rule...)...); err != nil {
425
-		return fmt.Errorf("unable to insert jump to %s rule in %s chain: %v", toChain, fromChain, err)
426
-	}
427 437
 	return nil
428 438
 }
429 439
 
... ...
@@ -447,6 +459,14 @@ func (r Rule) exec(op Action) error {
447 447
 	return GetIptable(r.IPVer).RawCombinedOutput(r.cmdArgs(op)...)
448 448
 }
449 449
 
450
+// WithChain returns a version of the rule with its Chain field set to chain.
451
+func (r Rule) WithChain(chain string) Rule {
452
+	wc := r
453
+	wc.Args = slices.Clone(r.Args)
454
+	wc.Chain = chain
455
+	return wc
456
+}
457
+
450 458
 // Append appends the rule to the end of the chain. If the rule already exists anywhere in the
451 459
 // chain, this is a no-op.
452 460
 func (r Rule) Append() error {
453 461
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+-N DOCKER-FORWARD
1
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
2
+-A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
3
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
0 4
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+-N DOCKER-FORWARD
1
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v6 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
2
+-A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
3
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v6 dst -j DOCKER
0 4
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+-N DOCKER-FORWARD
1
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
2
+-A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
3
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
0 4
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+-N DOCKER-FORWARD
1
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v6 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
2
+-A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
3
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v6 dst -j DOCKER
... ...
@@ -1,5 +1,3 @@
1 1
 -P FORWARD ACCEPT
2 2
 -A FORWARD -j DOCKER-USER
3
--A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
4
--A FORWARD -j DOCKER-ISOLATION-STAGE-1
5
--A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
3
+-A FORWARD -j DOCKER-FORWARD
... ...
@@ -1,5 +1,3 @@
1 1
 -P FORWARD ACCEPT
2 2
 -A FORWARD -j DOCKER-USER
3
--A FORWARD -m set --match-set docker-ext-bridges-v6 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
4
--A FORWARD -j DOCKER-ISOLATION-STAGE-1
5
--A FORWARD -m set --match-set docker-ext-bridges-v6 dst -j DOCKER
3
+-A FORWARD -j DOCKER-FORWARD
... ...
@@ -1,5 +1,3 @@
1 1
 -P FORWARD ACCEPT
2 2
 -A FORWARD -j DOCKER-USER
3
--A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
4
--A FORWARD -j DOCKER-ISOLATION-STAGE-1
5
--A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
3
+-A FORWARD -j DOCKER-FORWARD
... ...
@@ -1,5 +1,3 @@
1 1
 -P FORWARD ACCEPT
2 2
 -A FORWARD -j DOCKER-USER
3
--A FORWARD -m set --match-set docker-ext-bridges-v6 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
4
--A FORWARD -j DOCKER-ISOLATION-STAGE-1
5
--A FORWARD -m set --match-set docker-ext-bridges-v6 dst -j DOCKER
3
+-A FORWARD -j DOCKER-FORWARD
6 4
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+-N DOCKER-FORWARD
1
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
2
+-A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
3
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
0 4
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+-N DOCKER-FORWARD
1
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v6 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
2
+-A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
3
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v6 dst -j DOCKER
0 4
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+-N DOCKER-FORWARD
1
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
2
+-A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
3
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
0 4
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+-N DOCKER-FORWARD
1
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v6 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
2
+-A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1
3
+-A DOCKER-FORWARD -m set --match-set docker-ext-bridges-v6 dst -j DOCKER
... ...
@@ -1,6 +1,4 @@
1 1
 -P FORWARD ACCEPT
2 2
 -A FORWARD -j DOCKER-USER
3
--A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
4
--A FORWARD -j DOCKER-ISOLATION-STAGE-1
5
--A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
3
+-A FORWARD -j DOCKER-FORWARD
6 4
 -A FORWARD -j DROP
... ...
@@ -1,6 +1,4 @@
1 1
 -P FORWARD ACCEPT
2 2
 -A FORWARD -j DOCKER-USER
3
--A FORWARD -m set --match-set docker-ext-bridges-v6 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
4
--A FORWARD -j DOCKER-ISOLATION-STAGE-1
5
--A FORWARD -m set --match-set docker-ext-bridges-v6 dst -j DOCKER
3
+-A FORWARD -j DOCKER-FORWARD
6 4
 -A FORWARD -j DROP
... ...
@@ -1,5 +1,3 @@
1 1
 -P FORWARD ACCEPT
2 2
 -A FORWARD -j DOCKER-USER
3
--A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
4
--A FORWARD -j DOCKER-ISOLATION-STAGE-1
5
--A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER
3
+-A FORWARD -j DOCKER-FORWARD
... ...
@@ -1,5 +1,3 @@
1 1
 -P FORWARD ACCEPT
2 2
 -A FORWARD -j DOCKER-USER
3
--A FORWARD -m set --match-set docker-ext-bridges-v6 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
4
--A FORWARD -j DOCKER-ISOLATION-STAGE-1
5
--A FORWARD -m set --match-set docker-ext-bridges-v6 dst -j DOCKER
3
+-A FORWARD -j DOCKER-FORWARD