Files
moby/integration/service
Cory Snider 30d5aee166 libnetwork/d/bridge: icc=false: allow port hairpin
A published port is meant to be reachable via the host's addresses from
anywhere that can route to them. On a bridge with inter-container
communication disabled, another container on the same bridge could not
reach it.

The two halves of a port binding disagreed about hairpinned traffic.
setPerPortNAT only skips packets arriving on the bridge when the
userland proxy is in use. With the proxy disabled, a peer's packet to a
host address is DNATed back across the bridge, but the rule that opens
the port always excludes packets arriving on the bridge. No rule
accepted the packet, and the network's ICC rule dropped it. With the
proxy enabled, the packet isn't DNATed and docker-proxy serves it,
which hides the problem. So whether a peer could reach a published port
depended on a setting that has nothing to do with access control.

Add a rule that accepts packets arriving on the bridge for a published
port, but only if they were DNATed: the connection's original
destination must not be the container's own address. Without that
condition the rule would also allow direct access to the container's
address, which is what disabling ICC prevents. By the time the two
kinds of packet reach the filter rules, they are otherwise the same:
same interfaces, destination and port.

The existing rule for packets arriving from outside the bridge is left
as it is, because it is also what permits direct access when the daemon
runs with --allow-direct-routing. Nothing is added when ICC is enabled,
because the network-level rules already accept this traffic.

The new rule is added in every gateway mode that DNATs, including
nat-unprotected. That mode skips the per-port forwarding rules, but its
blanket accept also excludes packets arriving on the bridge, so it
doesn't accept hairpinned packets either.

Swarm ingress ports are published on the ingress load balancer's
docker_gwbridge endpoint, and docker_gwbridge is created with ICC
disabled. So this is also what stopped a task from reaching a service's
published port via its own node's address.

This isn't enough for IPv6 yet. To reply, the container also has to
resolve the peer's link-layer address, and ICMPv6 Neighbour Discovery
between containers is dropped when ICC is disabled. IPv4 is unaffected
because ARP isn't filtered here. So for IPv6, the tests added here only
check that direct access is still blocked.

Signed-off-by: Cory Snider <csnider@mirantis.com>
Assisted-By: Claude Opus 5
Assisted-By: Claude Opus 5.5
2026-09-25 10:15:39 -04:00
..
2025-12-15 18:56:34 +01:00
2025-09-08 10:08:30 +02:00