Skip to content

Always allow access to published ports on addresses in gateway mode "routed" networks#50140

Merged
robmry merged 1 commit intomoby:masterfrom
robmry:non-gateway_routed_access
Jul 23, 2025
Merged

Always allow access to published ports on addresses in gateway mode "routed" networks#50140
robmry merged 1 commit intomoby:masterfrom
robmry:non-gateway_routed_access

Conversation

@robmry
Copy link
Contributor

@robmry robmry commented Jun 4, 2025

- What I did

When an endpoint in a gateway mode "nat" network is selected as a container's default gateway, the bridge driver sets up
bindings between host and container ports (NAT, userland proxy etc).

When gateway mode "routed" was added as an alternative to the default "nat" mode - port bindings followed the same rules. But, unlike "nat" mode, there's no host port binding to set up - there's routing between remote client and the container, so it doesn't matter what the default gateway is.

So, in "routed" mode, set up the rules to make a container's published ports accessible when the endpoint is added, and remove those rules when the endpoint is removed (when the container is disconnected from the endpoint's network).

- How I did it

- How to verify it

Existing tests, particularly the ones in integration/networking/port_mapping_linux_test.go, including

New integration test that checks ports in routed-mode networks are accessible when a nat-mode network is the gateway.

- Human readable description for the release notes

- Published ports are now always accessible in networks with gateway mode "routed". Previously, rules to open those ports were only added when the routed mode network was selected as the container's default gateway.

@robmry robmry force-pushed the non-gateway_routed_access branch 2 times, most recently from 3c5d730 to 97baaa3 Compare June 5, 2025 10:25
@robmry robmry self-assigned this Jun 5, 2025
@robmry robmry force-pushed the non-gateway_routed_access branch from 97baaa3 to 908a742 Compare June 5, 2025 12:04
@robmry robmry changed the title Always allow access to published ports when gateway mode is "routed" Always allow access to published ports on addresses in gateway mode "routed" networks Jun 5, 2025
@robmry robmry force-pushed the non-gateway_routed_access branch 2 times, most recently from 566f890 to 6d1c709 Compare June 6, 2025 08:40
@robmry robmry force-pushed the non-gateway_routed_access branch from 6d1c709 to 6722745 Compare July 11, 2025 10:36
@robmry robmry marked this pull request as ready for review July 11, 2025 19:13
@robmry robmry requested review from akerouanton and corhere July 11, 2025 19:13
@robmry robmry force-pushed the non-gateway_routed_access branch from 6722745 to f6f8444 Compare July 15, 2025 08:25
Comment on lines +1616 to +1619
// If the network is IPv4-only, this endpoint is the container's IPv6 gateway,
// and IPv4 addresses are published to the host - when it's acting as the IPv6
// gateway, bindings may be set up to proxy from host IPv6 to endpoint IPv4.
proxy4To6 := ep.addr != nil && ep.addrv6 == nil && !n.gwMode(firewaller.IPv4).routed()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

proxy4To6 should be proxy6To4 to follow existing code (e.g. NoProxy6To4).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!

@robmry robmry force-pushed the non-gateway_routed_access branch from f6f8444 to 561ac3e Compare July 22, 2025 14:44
Comment on lines +1598 to +1610
// If the network is IPv4-only, this endpoint is the container's IPv6 gateway,
// and IPv4 addresses are published to the host - when it's acting as the IPv6
// gateway, bindings may be set up to proxy from host IPv6 to endpoint IPv4.
proxy6To4 := ep.addr != nil && ep.addrv6 == nil && !n.gwMode(firewaller.IPv4).routed()

// Drop IPv4 bindings if this endpoint is not the IPv4 gateway, unless the
// network is "routed" (routed bindings get dropped unconditionally by Leave).
drop4 := !pbmReq.ipv4 && !n.gwMode(firewaller.IPv4).routed()

// Drop IPv6 bindings if this endpoint is not the IPv6 gateway, unless the IPv6
// network is "routed" (routed bindings get dropped unconditionally by Leave),
// or there may be mappings proxying between host IPv6 and container IPv4.
drop6 := !pbmReq.ipv6 && (!n.gwMode(firewaller.IPv6).routed() || proxy6To4)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on our Slack discussion, I think you don't need to check proxy6To4 in drop6, because pbmReq.ipv6 is set to true when the current endpoint is the IPv6 gateway, or when the container has no IPv6 gateway at all and the current endpoint is the IPv4 gateway.

// Act as the IPv6 gateway if explicitly selected - or if there's no IPv6
// gateway, but this endpoint is the IPv4 gateway (in which case, the userland
// proxy may proxy between host v6 and container v4 addresses.)
if gw6Id == eid || (gw6Id == "" && gw4Id == eid) {
pbmReq.ipv6 = true
}

So, you can probably replace drop6 with:

drop6 := !pbmReq.ipv6 && !n.gwMode(firewaller.IPv6).routed()

Also, I think it's quite confusing to say Drop IPv6 bindings if this endpoint is not the IPv6 gateway while pbmReq.ipv6 is set true when there's no IPv6 gateway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

When an endpoint in a gateway mode "nat" network is selected
as a container's default gateway, the bridge driver sets up
bindings between host and container ports (NAT, userland proxy
etc).

When gateway mode "routed" was added as an alternative to
the default "nat" mode - port bindings followed the same rules.

But, unlike "nat" mode, there's no host port binding to set
up - there's routing between remote client and the container,
so it doesn't matter what the default gateway is.

So, in "routed" mode, set up the rules to make a container's
published ports accessible when the endpoint is added, and
remove those rules when the endpoint is removed (when the
container is disconnected from the endpoint's network).

Port mappings are only provided by ProgramExternalConnectivity,
they can't be set up during the Join. So, include routed
bindings in the port bindings mode that's stored as part of
endpoint state - and use that to work out whether to add or
remove bindings.

Signed-off-by: Rob Murray <rob.murray@docker.com>
@robmry robmry force-pushed the non-gateway_routed_access branch from 561ac3e to 30752f0 Compare July 22, 2025 15:54
@robmry robmry merged commit 2dbde13 into moby:master Jul 23, 2025
249 of 250 checks passed
drop4 := !pbmReq.ipv4 && !n.gwMode(firewaller.IPv4).routed()

// Drop IPv6 bindings if this endpoint is not the IPv6 gateway, and not proxying
// from host IPv6 to container IPv6 because there is no IPv6 gateway - unless the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😅

Suggested change
// from host IPv6 to container IPv6 because there is no IPv6 gateway - unless the
// from host IPv6 to container IPv4 because there is no IPv6 gateway - unless the

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v28: ports inaccessible on secondary interfaces

4 participants