Back to Open Source
moby/mobymerged

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.

+52-142 files changedMerged April 9, 2026

Files Changed

  • daemon/libnetwork/default_gateway.go
  • daemon/libnetwork/sandbox_unix_test.go
golangnetworkingdockerlibnetworkbugfix

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)

Developed by Varun Hotani
© 2026. All rights reserved.