diff --git a/integration/network/bridge/iptablesdoc/generated/usernet-portmap-routed.md b/integration/network/bridge/iptablesdoc/generated/usernet-portmap-routed.md
new file mode 100644
index 0000000000000..76b97cfc18401
--- /dev/null
+++ b/integration/network/bridge/iptablesdoc/generated/usernet-portmap-routed.md
@@ -0,0 +1,143 @@
+## Container on a routed-mode network, with a published port
+
+Running the daemon with the userland proxy disabled 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=routed \
+ --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 the same as with the userland proxy enabled.
+
+_Note that this means inter-network communication is disabled as-normal so,
+although published ports will be directly accessible from a remote host
+they are not accessible from containers in neighbouring docker networks
+on the same host._
+
+
+Filter table
+
+ 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 DOCKER-ISOLATION-STAGE-1 0 -- * * 0.0.0.0/0 0.0.0.0/0
+ 3 0 0 ACCEPT 0 -- * bridge1 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
+ 4 0 0 DOCKER 0 -- * bridge1 0.0.0.0/0 0.0.0.0/0
+ 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 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
+ 8 0 0 DOCKER 0 -- * docker0 0.0.0.0/0 0.0.0.0/0
+ 9 0 0 ACCEPT 0 -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
+ 10 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 (2 references)
+ num pkts bytes target prot opt in out source destination
+ 1 0 0 ACCEPT 6 -- !bridge1 bridge1 0.0.0.0/0 192.0.2.2 tcp dpt:80
+
+ 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 -- bridge1 !bridge1 0.0.0.0/0 0.0.0.0/0
+ 2 0 0 DOCKER-ISOLATION-STAGE-2 0 -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
+ 3 0 0 RETURN 0 -- * * 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
+ 3 0 0 RETURN 0 -- * * 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
+
+
+ -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 -j DOCKER-ISOLATION-STAGE-1
+ -A FORWARD -o bridge1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+ -A FORWARD -o bridge1 -j DOCKER
+ -A FORWARD -i bridge1 ! -o bridge1 -j ACCEPT
+ -A FORWARD -i bridge1 -o bridge1 -j ACCEPT
+ -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+ -A FORWARD -o docker0 -j DOCKER
+ -A FORWARD -i docker0 ! -o docker0 -j ACCEPT
+ -A FORWARD -i docker0 -o docker0 -j ACCEPT
+ -A DOCKER -d 192.0.2.2/32 ! -i bridge1 -o bridge1 -p tcp -m tcp --dport 80 -j ACCEPT
+ -A DOCKER-ISOLATION-STAGE-1 -i bridge1 ! -o bridge1 -j DOCKER-ISOLATION-STAGE-2
+ -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
+ -A DOCKER-ISOLATION-STAGE-1 -j RETURN
+ -A DOCKER-ISOLATION-STAGE-2 -o bridge1 -j DROP
+ -A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
+ -A DOCKER-ISOLATION-STAGE-2 -j RETURN
+ -A DOCKER-USER -j RETURN
+
+
+
+
+The nat table is:
+
+ 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 -- * !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 -- docker0 * 0.0.0.0/0 0.0.0.0/0
+
+
+
+iptables commands
+
+ -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 172.17.0.0/16 ! -o docker0 -j MASQUERADE
+ -A DOCKER -i docker0 -j RETURN
+
+
+
+
+Differences from [nat mode][1]:
+
+ - In the POSTROUTING chain:
+ - No MASQUERADE rule for traffic from the bridge network to elsewhere. [setupIPTablesInternal][2]
+ - No MASQUERADE rule for traffic from the bridge network to itself on published port 80 (port
+ mapping is skipped). [attemptBindHostPorts][3]
+ - In the DOCKER chain:
+ - No early return ("skip DNAT") for traffic from the bridge network. [setupIPTablesInternal][4]
+ - No DNAT rule for the published port (port mapping is skipped). [attemptBindHostPorts][3]
+
+_And, the userland proxy won't be started for mapped ports._
+
+[1]: usernet-portmap.md
+[2]: https://github.com/moby/moby/blob/333cfa640239153477bf635a8131734d0e9d099d/libnetwork/drivers/bridge/setup_ip_tables_linux.go#L294
+[3]: https://github.com/moby/moby/blob/675c2ac2db93e38bb9c5a6615d4155a969535fd9/libnetwork/drivers/bridge/port_mapping_linux.go#L477-L479
+[4]: https://github.com/moby/moby/blob/333cfa640239153477bf635a8131734d0e9d099d/libnetwork/drivers/bridge/setup_ip_tables_linux.go#L290
diff --git a/integration/network/bridge/iptablesdoc/index.md b/integration/network/bridge/iptablesdoc/index.md
index 53e7f7c918a3a..4a15e0b4b68fb 100644
--- a/integration/network/bridge/iptablesdoc/index.md
+++ b/integration/network/bridge/iptablesdoc/index.md
@@ -43,3 +43,4 @@ Scenarios:
- [Container on a user-defined network, with a published port, no userland proxy](generated/usernet-portmap-noproxy.md)
- [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)
diff --git a/integration/network/bridge/iptablesdoc/iptablesdoc_linux_test.go b/integration/network/bridge/iptablesdoc/iptablesdoc_linux_test.go
index 3152469833769..60efde5e9bf0c 100644
--- a/integration/network/bridge/iptablesdoc/iptablesdoc_linux_test.go
+++ b/integration/network/bridge/iptablesdoc/iptablesdoc_linux_test.go
@@ -120,6 +120,19 @@ var index = []section{
},
}},
},
+ {
+ name: "usernet-portmap-routed.md",
+ networks: []bridgeNetwork{{
+ bridge: "bridge1",
+ gwMode: "routed",
+ 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/usernet-portmap-routed.md b/integration/network/bridge/iptablesdoc/templates/usernet-portmap-routed.md
new file mode 100644
index 0000000000000..f0098813966f7
--- /dev/null
+++ b/integration/network/bridge/iptablesdoc/templates/usernet-portmap-routed.md
@@ -0,0 +1,53 @@
+## Container on a routed-mode network, with a published port
+
+Running the daemon with the userland proxy disabled 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=routed \
+ --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 the same as with the userland proxy enabled.
+
+_Note that this means inter-network communication is disabled as-normal so,
+although published ports will be directly accessible from a remote host
+they are not accessible from containers in neighbouring docker networks
+on the same host._
+
+
+Filter table
+
+ {{index . "LFilter4"}}
+
+ {{index . "SFilter4"}}
+
+
+
+The nat table is:
+
+ {{index . "LNat4"}}
+
+
+iptables commands
+
+ {{index . "SNat4"}}
+
+
+
+Differences from [nat mode][1]:
+
+ - In the POSTROUTING chain:
+ - No MASQUERADE rule for traffic from the bridge network to elsewhere. [setupIPTablesInternal][2]
+ - No MASQUERADE rule for traffic from the bridge network to itself on published port 80 (port
+ mapping is skipped). [attemptBindHostPorts][3]
+ - In the DOCKER chain:
+ - No early return ("skip DNAT") for traffic from the bridge network. [setupIPTablesInternal][4]
+ - No DNAT rule for the published port (port mapping is skipped). [attemptBindHostPorts][3]
+
+_And, the userland proxy won't be started for mapped ports._
+
+[1]: usernet-portmap.md
+[2]: https://github.com/moby/moby/blob/333cfa640239153477bf635a8131734d0e9d099d/libnetwork/drivers/bridge/setup_ip_tables_linux.go#L294
+[3]: https://github.com/moby/moby/blob/675c2ac2db93e38bb9c5a6615d4155a969535fd9/libnetwork/drivers/bridge/port_mapping_linux.go#L477-L479
+[4]: https://github.com/moby/moby/blob/333cfa640239153477bf635a8131734d0e9d099d/libnetwork/drivers/bridge/setup_ip_tables_linux.go#L290