From 7666810ca3f3ffedce9f36f911640e8296fff08c Mon Sep 17 00:00:00 2001 From: Dean Chen <862469039@qq.com> Date: Tue, 11 Aug 2026 01:48:17 +0500 Subject: [PATCH 1/4] docs: note unpublished-port filter vs pre-netfilter DNAT Explain that DOCKER-USER cannot re-open unpublished container ports when another datapath already rewrites packets to those addresses. Signed-off-by: Dean Chen <862469039@qq.com> --- content/manuals/engine/network/firewall-iptables.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/content/manuals/engine/network/firewall-iptables.md b/content/manuals/engine/network/firewall-iptables.md index 87d63e9cbbd9..794f38c7c12a 100644 --- a/content/manuals/engine/network/firewall-iptables.md +++ b/content/manuals/engine/network/firewall-iptables.md @@ -53,6 +53,18 @@ additional rules to filter these packets, use the `DOCKER-USER` chain. Rules appended to the `FORWARD` chain will be processed after Docker's rules. +> [!NOTE] +> +> Docker also blocks direct-routed access to **unpublished** container ports in +> the `DOCKER` filter rules (see +> [Port publishing](./port-publishing.md)). Packets that another datapath has +> already rewritten to a container address (for example a Kubernetes CNI that +> DNAT's Service VIPs before host netfilter) still hit that protection. They +> never become "published" traffic you can reopen only with `DOCKER-USER`. To +> allow access, publish the ports you need, or use +> `gateway_mode_ipv4`/`gateway_mode_ipv6=nat-unprotected` when you intentionally +> want all container ports reachable by direct routing. + ### Match the original IP and ports for requests When packets arrive to the `DOCKER-USER` chain, they have already passed through From 18e570f6dcf74b2a46167c74c02636caf9da0b41 Mon Sep 17 00:00:00 2001 From: Dean Chen <862469039@qq.com> Date: Tue, 11 Aug 2026 03:44:30 +0500 Subject: [PATCH 2/4] docs: fix vale spelling for data path Signed-off-by: Dean Chen <862469039@qq.com> --- content/manuals/engine/network/firewall-iptables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/manuals/engine/network/firewall-iptables.md b/content/manuals/engine/network/firewall-iptables.md index 794f38c7c12a..34f10137d446 100644 --- a/content/manuals/engine/network/firewall-iptables.md +++ b/content/manuals/engine/network/firewall-iptables.md @@ -57,7 +57,7 @@ Rules appended to the `FORWARD` chain will be processed after Docker's rules. > > Docker also blocks direct-routed access to **unpublished** container ports in > the `DOCKER` filter rules (see -> [Port publishing](./port-publishing.md)). Packets that another datapath has +> [Port publishing](./port-publishing.md)). Packets that another data path has > already rewritten to a container address (for example a Kubernetes CNI that > DNAT's Service VIPs before host netfilter) still hit that protection. They > never become "published" traffic you can reopen only with `DOCKER-USER`. To From fa671679b149bbfba8d0eff22c09f8baf6636303 Mon Sep 17 00:00:00 2001 From: Dean Chen <862469039@qq.com> Date: Wed, 12 Aug 2026 20:00:24 +0500 Subject: [PATCH 3/4] docs: clarify direct-routed drops vs DOCKER-USER Rewrite the note in plain language (what direct routing is, why raw PREROUTING wins over DOCKER-USER) and mirror it for nftables. Point at trusted_host_interfaces / allow-direct-routing and the gateway_mode docs. Signed-off-by: Dean Chen <862469039@qq.com> --- .../engine/network/firewall-iptables.md | 31 +++++++++++++------ .../engine/network/firewall-nftables.md | 27 ++++++++++++++++ 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/content/manuals/engine/network/firewall-iptables.md b/content/manuals/engine/network/firewall-iptables.md index 34f10137d446..b66c653dcc82 100644 --- a/content/manuals/engine/network/firewall-iptables.md +++ b/content/manuals/engine/network/firewall-iptables.md @@ -55,15 +55,28 @@ Rules appended to the `FORWARD` chain will be processed after Docker's rules. > [!NOTE] > -> Docker also blocks direct-routed access to **unpublished** container ports in -> the `DOCKER` filter rules (see -> [Port publishing](./port-publishing.md)). Packets that another data path has -> already rewritten to a container address (for example a Kubernetes CNI that -> DNAT's Service VIPs before host netfilter) still hit that protection. They -> never become "published" traffic you can reopen only with `DOCKER-USER`. To -> allow access, publish the ports you need, or use -> `gateway_mode_ipv4`/`gateway_mode_ipv6=nat-unprotected` when you intentionally -> want all container ports reachable by direct routing. +> By default, remote hosts can only reach a container through a port published +> to one of the Docker host's addresses. Sending packets to the container's own +> IP address instead ("direct routed" access) is not allowed. +> +> These packets are dropped by a rule in the `raw` table's `PREROUTING` chain, +> which is processed before the `filter` table. So, they never reach the +> `DOCKER-USER` chain, and a rule in `DOCKER-USER` cannot allow them. +> +> Any packet that reaches the host's firewall rules already addressed to a +> container is treated this way. For example, a Kubernetes CNI plugin may +> translate a Service address to a container address before the packet reaches +> the host's `PREROUTING` rules. Docker cannot distinguish the result from a +> packet routed to the container by a remote host. Which host interface these +> packets arrive on depends on the plugin's data path — for an overlay network +> it is normally the tunnel device. +> +> To allow direct routed access to a container's *published* ports, use network +> option `com.docker.network.bridge.trusted_host_interfaces`, or daemon option +> `allow-direct-routing`. Unpublished ports are still protected. See +> [Direct routing to containers in bridge networks](./port-publishing.md#direct-routing-to-containers-in-bridge-networks). +> To allow direct routed access to all container ports, published or not, see +> [Gateway modes](./port-publishing.md#gateway-modes). ### Match the original IP and ports for requests diff --git a/content/manuals/engine/network/firewall-nftables.md b/content/manuals/engine/network/firewall-nftables.md index f868e6c95e75..6cdcfa2c7e41 100644 --- a/content/manuals/engine/network/firewall-nftables.md +++ b/content/manuals/engine/network/firewall-nftables.md @@ -210,6 +210,33 @@ The firewall mark must be added before Docker's rules run. So if the mark is added in a chain with type `filter` and hook `forward`, it must have priority `filter - 1` or lower. +> [!NOTE] +> +> By default, remote hosts can only reach a container through a port published +> to one of the Docker host's addresses. Sending packets to the container's own +> IP address instead ("direct routed" access) is not allowed. +> +> These packets are dropped by a rule in the `raw-PREROUTING` chain of the +> `docker-bridges` tables. Because a drop is final, a rule in one of your own +> tables cannot allow them. And, because that chain runs at the `prerouting` +> hook, before Docker's `filter-FORWARD` rules, a firewall mark added with +> `--bridge-accept-fwmark` has no effect on it. +> +> Any packet that reaches the host's firewall rules already addressed to a +> container is treated this way. For example, a Kubernetes CNI plugin may +> translate a Service address to a container address before the packet reaches +> the host's prerouting rules. Docker cannot distinguish the result from a +> packet routed to the container by a remote host. Which host interface these +> packets arrive on depends on the plugin's data path — for an overlay network +> it is normally the tunnel device. +> +> To allow direct routed access to a container's *published* ports, use network +> option `com.docker.network.bridge.trusted_host_interfaces`, or daemon option +> `allow-direct-routing`. Unpublished ports are still protected. See +> [Direct routing to containers in bridge networks](./port-publishing.md#direct-routing-to-containers-in-bridge-networks). +> To allow direct routed access to all container ports, published or not, see +> [Gateway modes](./port-publishing.md#gateway-modes). + #### Replacing `DOCKER-USER` with an nftables table Because nftables doesn't have pre-defined chains, to replace the `DOCKER-USER` From b5d586e05c5c2c5b4ba1e44bef3aa2971f4611ea Mon Sep 17 00:00:00 2001 From: Dean Chen <862469039@qq.com> Date: Wed, 12 Aug 2026 21:57:32 +0500 Subject: [PATCH 4/4] docs: fix vale spelling for pre-routing Signed-off-by: Dean Chen <862469039@qq.com> --- content/manuals/engine/network/firewall-nftables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/manuals/engine/network/firewall-nftables.md b/content/manuals/engine/network/firewall-nftables.md index 6cdcfa2c7e41..b3440a1f23de 100644 --- a/content/manuals/engine/network/firewall-nftables.md +++ b/content/manuals/engine/network/firewall-nftables.md @@ -225,7 +225,7 @@ priority `filter - 1` or lower. > Any packet that reaches the host's firewall rules already addressed to a > container is treated this way. For example, a Kubernetes CNI plugin may > translate a Service address to a container address before the packet reaches -> the host's prerouting rules. Docker cannot distinguish the result from a +> the host's pre-routing rules. Docker cannot distinguish the result from a > packet routed to the container by a remote host. Which host interface these > packets arrive on depends on the plugin's data path — for an overlay network > it is normally the tunnel device.