libnetwork: respect gw priority per address family
Fixed a bug in Docker's gateway selection logic where a lower-priority dual-stack endpoint would override a higher-priority IPv4-only endpoint, causing incorrect routing in multi-network container setups.
Files Changed
- daemon/libnetwork/default_gateway.go
- daemon/libnetwork/sandbox_unix_test.go
Problem
When a sandbox is attached to both:
- a higher-priority IPv4-only network, and
- a lower-priority dual-stack network,
Moby would pick the dual-stack endpoint as the IPv4 default gateway even though GwPriority said the IPv4-only endpoint should win.
This caused incorrect routing in docker-compose setups mixing single-stack and dual-stack networks with different priorities.
Root Cause
Endpoint.Less() already sorts endpoints correctly using gateway priority, network/internal ordering, gateway capability, and network name.
But getGatewayEndpoint() in daemon/libnetwork/default_gateway.go bypassed that ordering entirely — it returned the first dual-stack endpoint for both address families.
So a lower-priority dual-stack endpoint would take over IPv4 gateway selection just by being dual-stack, regardless of priority.
Fix
Select IPv4 and IPv6 gateway endpoints independently from the already-sorted endpoint list:
// Before: returns first dual-stack ep for both families (ignores priority)
func (sb *Sandbox) getGatewayEndpoint() (*Endpoint, *Endpoint) {
var ipv4Ep, ipv6Ep *Endpoint
for _, ep := range sb.endpoints {
if ep.hasDualStack() {
if ipv4Ep == nil { ipv4Ep = ep }
if ipv6Ep == nil { ipv6Ep = ep }
}
}
// ...
}
// After: walks sorted list, picks first ep per family (respects priority)
func (sb *Sandbox) getGatewayEndpoint() (*Endpoint, *Endpoint) {
var ipv4Ep, ipv6Ep *Endpoint
for _, ep := range sb.endpoints {
if ipv4Ep == nil && ep.hasIPv4() { ipv4Ep = ep }
if ipv6Ep == nil && ep.hasIPv6() { ipv6Ep = ep }
if ipv4Ep != nil && ipv6Ep != nil { break }
}
return ipv4Ep, ipv6Ep
}
First endpoint with IPv4 connectivity wins IPv4. First with IPv6 wins IPv6. Both are selected from the already-sorted list — so priority is respected per address family.
Test
Added a regression test covering the exact scenario: a higher-priority IPv4-only endpoint alongside a lower-priority dual-stack endpoint.
Expected:
- IPv4 gateway → IPv4-only endpoint (higher priority)
- IPv6 gateway → dual-stack endpoint (only one with IPv6)
