Remove libnet's logic to track a driver's port mapping state

Change the semantics of ProgramExternalConnectivity, libnet
can now call it whenever an endpoint is selected or deselected
as a container's gateway endpoint.

It's the driver's responsibility to remember what bindings it's
set up, and to work out what needs to change.

So, calling ProgramExternalConnectivity to tell the driver
an endpoint is no longer a gateway has the same effect as
RevokeExternalConnectivity - bindings need to be removed.

That means libnet no longer needs to work out whether to
Program/Revoke, it can just call ProgramExternalConnectivity.
RevokeExternalConnectivity has been removed.

Signed-off-by: Rob Murray <rob.murray@docker.com>
This commit is contained in:
Rob Murray
2025-06-04 12:31:01 +01:00
parent 10f3491546
commit 4f7afb8ac9
7 changed files with 168 additions and 228 deletions

View File

@@ -81,13 +81,22 @@ type Driver interface {
// ExtConner is an optional interface for a network driver. // ExtConner is an optional interface for a network driver.
type ExtConner interface { type ExtConner interface {
// ProgramExternalConnectivity invokes the driver method which does the necessary // ProgramExternalConnectivity tells the driver a container's options (including
// programming to allow the external connectivity dictated by the passed options // port mapping options), so that it can configure the endpoint eid in network
// nid.
//
// Ids of the endpoints currently acting as the container's default gateway for
// IPv4 and IPv6 are passed as gw4Id/gw6Id. (Those endpoints may be managed by
// different network drivers. If there is no gateway, the id will be the
// empty string.)
//
// This method is called after Driver.Join, before Driver.Leave, and when eid
// is or was equal to gw4Id or gw6Id, and there's a change.
//
// When an endpoint acting as a gateway is deleted, this function is called
// with that endpoint's id in eid, and empty gateway ids (even if another
// is present and will shortly be selected as the gateway).
ProgramExternalConnectivity(ctx context.Context, nid, eid string, options map[string]interface{}, gw4Id, gw6Id string) error ProgramExternalConnectivity(ctx context.Context, nid, eid string, options map[string]interface{}, gw4Id, gw6Id string) error
// RevokeExternalConnectivity asks the driver to remove any external connectivity
// programming that was done so far
RevokeExternalConnectivity(nid, eid string) error
} }
// GwAllocChecker is an optional interface for a network driver. // GwAllocChecker is an optional interface for a network driver.

View File

@@ -1,3 +1,6 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
package bridge package bridge
import ( import (
@@ -6,6 +9,7 @@ import (
"net" "net"
"net/netip" "net/netip"
"os" "os"
"slices"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -30,6 +34,7 @@ import (
"github.com/docker/docker/libnetwork/options" "github.com/docker/docker/libnetwork/options"
"github.com/docker/docker/libnetwork/scope" "github.com/docker/docker/libnetwork/scope"
"github.com/docker/docker/libnetwork/types" "github.com/docker/docker/libnetwork/types"
"github.com/docker/docker/pkg/stringid"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
"github.com/vishvananda/netns" "github.com/vishvananda/netns"
@@ -1494,7 +1499,7 @@ type portBindingMode struct {
ipv6 bool ipv6 bool
} }
func (d *driver) ProgramExternalConnectivity(ctx context.Context, nid, eid string, options map[string]interface{}, gw4Id, gw6Id string) error { func (d *driver) ProgramExternalConnectivity(ctx context.Context, nid, eid string, options map[string]interface{}, gw4Id, gw6Id string) (retErr error) {
ctx, span := otel.Tracer("").Start(ctx, spanPrefix+".ProgramExternalConnectivity", trace.WithAttributes( ctx, span := otel.Tracer("").Start(ctx, spanPrefix+".ProgramExternalConnectivity", trace.WithAttributes(
attribute.String("nid", nid), attribute.String("nid", nid),
attribute.String("eid", eid), attribute.String("eid", eid),
@@ -1521,9 +1526,13 @@ func (d *driver) ProgramExternalConnectivity(ctx context.Context, nid, eid strin
return endpointNotFoundError(eid) return endpointNotFoundError(eid)
} }
endpoint.extConnConfig, err = parseConnectivityOptions(options) // Only parse options if given (options may be nil when revoking gateway-ness, but that
if err != nil { // doesn't mean the endpoint's config has changed).
return err if options != nil {
endpoint.extConnConfig, err = parseConnectivityOptions(options)
if err != nil {
return err
}
} }
var pbmReq portBindingMode var pbmReq portBindingMode
@@ -1538,30 +1547,31 @@ func (d *driver) ProgramExternalConnectivity(ctx context.Context, nid, eid strin
pbmReq.ipv6 = true pbmReq.ipv6 = true
} }
// Program any required port mapping and store them in the endpoint // If no change is needed, return.
if endpoint.extConnConfig != nil && endpoint.extConnConfig.PortBindings != nil { if endpoint.portBindingState == pbmReq {
endpoint.portMapping, err = network.addPortMappings( return nil
ctx, }
endpoint,
endpoint.extConnConfig.PortBindings, // Remove port bindings that aren't needed due to a change in mode.
network.config.DefaultBindingIP, undoTrim, err := endpoint.trimPortBindings(ctx, network, pbmReq)
pbmReq, if err != nil {
) return err
}
defer func() {
if retErr != nil && undoTrim != nil {
endpoint.portMapping = append(endpoint.portMapping, undoTrim()...)
}
}()
// Set up new port bindings, and store them in the endpoint.
if (pbmReq.ipv4 || pbmReq.ipv6) && endpoint.extConnConfig != nil && endpoint.extConnConfig.PortBindings != nil {
newPMs, err := network.addPortMappings(ctx, endpoint, endpoint.extConnConfig.PortBindings, network.config.DefaultBindingIP, pbmReq)
if err != nil { if err != nil {
return err return err
} }
endpoint.portMapping = append(endpoint.portMapping, newPMs...)
} }
defer func() {
if err != nil {
if e := network.releasePorts(endpoint); e != nil {
log.G(ctx).Errorf("Failed to release ports allocated for the bridge endpoint %s on failure %v because of %v",
eid, err, e)
}
endpoint.portMapping = nil
}
}()
// Remember the new port binding state. // Remember the new port binding state.
endpoint.portBindingState = pbmReq endpoint.portBindingState = pbmReq
@@ -1580,43 +1590,59 @@ func (d *driver) ProgramExternalConnectivity(ctx context.Context, nid, eid strin
return nil return nil
} }
func (d *driver) RevokeExternalConnectivity(nid, eid string) error { // trimPortBindings compares pbmReq with the current port bindings, and removes
// Make sure this function isn't deleting iptables rules while handleFirewalldReloadNw // port bindings that are no longer required.
// is restoring those same rules. //
d.configNetwork.Lock() // ep.portMapping is updated when bindings are removed.
defer d.configNetwork.Unlock() func (ep *bridgeEndpoint) trimPortBindings(ctx context.Context, n *bridgeNetwork, pbmReq portBindingMode) (func() []portBinding, error) {
// If the endpoint is the gateway for IPv4 and IPv6, there's nothing to drop.
network, err := d.getNetwork(nid) if pbmReq.ipv4 && pbmReq.ipv6 {
if err != nil { return nil, nil
return err
} }
endpoint, err := network.getEndpoint(eid) toDrop := make([]portBinding, 0, len(ep.portMapping))
if err != nil { toKeep := slices.DeleteFunc(ep.portMapping, func(pb portBinding) bool {
return err is4 := pb.HostIP.To4() != nil
if (is4 && !pbmReq.ipv4) || (!is4 && !pbmReq.ipv6) {
toDrop = append(toDrop, pb)
return true
}
return false
})
if len(toDrop) == 0 {
return nil, nil
} }
if endpoint == nil { if err := releasePortBindings(toDrop, n.firewallerNetwork); err != nil {
return endpointNotFoundError(eid) log.G(ctx).WithFields(log.Fields{
"error": err,
"gw4": pbmReq.ipv4,
"gw6": pbmReq.ipv6,
"nid": stringid.TruncateID(n.id),
"eid": stringid.TruncateID(ep.id),
}).Error("Failed to release port bindings")
return nil, err
}
ep.portMapping = toKeep
undo := func() []portBinding {
pbReq := make([]types.PortBinding, 0, len(toDrop))
for _, pb := range toDrop {
pbReq = append(pbReq, pb.PortBinding)
}
pbs, err := n.addPortMappings(ctx, ep, pbReq, n.config.DefaultBindingIP, ep.portBindingState)
if err != nil {
log.G(ctx).WithFields(log.Fields{
"error": err,
"nid": stringid.TruncateID(n.id),
"eid": stringid.TruncateID(ep.id),
}).Error("Failed to restore port bindings following join failure")
return nil
}
return pbs
} }
err = network.releasePorts(endpoint) return undo, nil
if err != nil {
log.G(context.TODO()).Warn(err)
}
endpoint.portMapping = nil
// Clean the connection tracker state of the host for the specific endpoint. This is a precautionary measure to
// avoid new endpoints getting the same IP address to receive unexpected packets due to bad conntrack state leading
// to bad NATing.
clearConntrackEntries(d.nlh, endpoint)
if err = d.storeUpdate(context.TODO(), endpoint); err != nil {
return fmt.Errorf("failed to update bridge endpoint %.7s to store: %v", endpoint.id, err)
}
return nil
} }
// clearConntrackEntries flushes conntrack entries matching endpoint IP address // clearConntrackEntries flushes conntrack entries matching endpoint IP address
@@ -1677,8 +1703,8 @@ func (d *driver) handleFirewalldReloadNw(nid string) {
return return
} }
// Make sure the network isn't being deleted, and ProgramExternalConnectivity/RevokeExternalConnectivity // Make sure the network isn't being deleted, and ProgramExternalConnectivity
// aren't modifying iptables rules, while restoring the rules. // isn't modifying iptables rules, while restoring the rules.
d.configNetwork.Lock() d.configNetwork.Lock()
defer d.configNetwork.Unlock() defer d.configNetwork.Unlock()

View File

@@ -874,7 +874,7 @@ func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) {
} }
} }
err = d.RevokeExternalConnectivity("net1", "ep1") err = d.ProgramExternalConnectivity(context.Background(), "net1", "ep1", nil, "", "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -997,7 +997,7 @@ func TestLinkContainers(t *testing.T) {
} }
checkLink(true) checkLink(true)
err = d.RevokeExternalConnectivity("net1", "ep2") err = d.ProgramExternalConnectivity(context.Background(), "net1", "ep2", nil, "", "")
if err != nil { if err != nil {
t.Fatalf("Failed to revoke external connectivity: %v", err) t.Fatalf("Failed to revoke external connectivity: %v", err)
} }

View File

@@ -477,4 +477,5 @@ func (n *bridgeNetwork) restorePortAllocations(ep *bridgeEndpoint) {
if err != nil { if err != nil {
log.G(context.TODO()).Warnf("Failed to reserve existing port mapping for endpoint %.7s:%v", ep.id, err) log.G(context.TODO()).Warnf("Failed to reserve existing port mapping for endpoint %.7s:%v", ep.id, err)
} }
ep.portBindingState = pbm
} }

View File

@@ -102,7 +102,7 @@ func TestPortMappingConfig(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
err = d.RevokeExternalConnectivity("dummy", "ep1") err = d.ProgramExternalConnectivity(context.Background(), "dummy", "ep1", nil, "", "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -159,7 +159,7 @@ func TestPortMappingV6Config(t *testing.T) {
t.Fatalf("Failed to join the endpoint: %v", err) t.Fatalf("Failed to join the endpoint: %v", err)
} }
if err = d.ProgramExternalConnectivity(context.Background(), "dummy", "ep1", sbOptions, "ep1", ""); err != nil { if err = d.ProgramExternalConnectivity(context.Background(), "dummy", "ep1", sbOptions, "ep1", "ep1"); err != nil {
t.Fatalf("Failed to program external connectivity: %v", err) t.Fatalf("Failed to program external connectivity: %v", err)
} }
@@ -178,7 +178,7 @@ func TestPortMappingV6Config(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
err = d.RevokeExternalConnectivity("dummy", "ep1") err = d.ProgramExternalConnectivity(context.Background(), "dummy", "ep1", nil, "", "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -384,7 +384,7 @@ func (d *driver) ProgramExternalConnectivity(_ context.Context, nid, eid string,
return nil return nil
} }
if !isGw4 && !isGw6 { if !isGw4 && !isGw6 {
return nil return d.revokeExternalConnectivity(nid, eid)
} }
ep.isGateway4, ep.isGateway6 = isGw4, isGw6 ep.isGateway4, ep.isGateway6 = isGw4, isGw6
if !isGw6 && gw6Id != "" { if !isGw6 && gw6Id != "" {
@@ -410,9 +410,8 @@ func (d *driver) ProgramExternalConnectivity(_ context.Context, nid, eid string,
return err return err
} }
// RevokeExternalConnectivity method is invoked to remove any external connectivity programming related to the endpoint. // revokeExternalConnectivity method is invoked to remove any external connectivity programming related to the endpoint.
func (d *driver) RevokeExternalConnectivity(nid, eid string) error { func (d *driver) revokeExternalConnectivity(nid, eid string) error {
d.nwEndpointsMu.Lock()
ep, ok := d.nwEndpoints[eid] ep, ok := d.nwEndpoints[eid]
d.nwEndpointsMu.Unlock() d.nwEndpointsMu.Unlock()
if !ok { if !ok {

View File

@@ -23,6 +23,7 @@ import (
"github.com/docker/docker/libnetwork/options" "github.com/docker/docker/libnetwork/options"
"github.com/docker/docker/libnetwork/scope" "github.com/docker/docker/libnetwork/scope"
"github.com/docker/docker/libnetwork/types" "github.com/docker/docker/libnetwork/types"
"github.com/docker/docker/pkg/stringid"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
) )
@@ -499,6 +500,10 @@ func epId(ep *Endpoint) string {
return ep.id return ep.id
} }
func epShortId(ep *Endpoint) string {
return stringid.TruncateID(epId(ep))
}
func (ep *Endpoint) sbJoin(ctx context.Context, sb *Sandbox, options ...EndpointOption) (retErr error) { func (ep *Endpoint) sbJoin(ctx context.Context, sb *Sandbox, options ...EndpointOption) (retErr error) {
ctx, span := otel.Tracer("").Start(ctx, "libnetwork.sbJoin") ctx, span := otel.Tracer("").Start(ctx, "libnetwork.sbJoin")
defer span.End() defer span.End()
@@ -514,9 +519,9 @@ func (ep *Endpoint) sbJoin(ctx context.Context, sb *Sandbox, options ...Endpoint
} }
ctx = log.WithLogger(ctx, log.G(ctx).WithFields(log.Fields{ ctx = log.WithLogger(ctx, log.G(ctx).WithFields(log.Fields{
"nid": n.ID(), "nid": stringid.TruncateID(n.ID()),
"net": n.Name(), "net": n.Name(),
"eid": ep.ID(), "eid": stringid.TruncateID(ep.ID()),
"ep": ep.Name(), "ep": ep.Name(),
})) }))
@@ -625,95 +630,50 @@ func (ep *Endpoint) sbJoin(ctx context.Context, sb *Sandbox, options ...Endpoint
} }
gwepAfter4, gwepAfter6 := sb.getGatewayEndpoint() gwepAfter4, gwepAfter6 := sb.getGatewayEndpoint()
if ep == gwepAfter4 || ep == gwepAfter6 {
// When the driver programs external connectivity for a Sandbox to use an
// IPv4-only Endpoint, it may choose to map ports from host IPv6 addresses (as
// well as host IPv4) to the Endpoint's IPv4 address. But, it must not do that if
// there is an IPv6-only Endpoint acting as the IPv6 gateway.
//
// So, for an IPv4-only Endpoint acting as a gateway, "noProxy6To4=true" must be
// set if the Sandbox has a different Endpoint acting as IPv6 gateway. And, if ep
// becoming the gateway changes noProxy6To4, the IPv4 gateway must be reset.
//
// This happens naturally in most cases. For example, if ep is dual-stack, sb had
// an IPv4-only gateway and no IPv6 gateway - connectivity will be revoked from
// the original IPv4-only gateway Endpoint and given to ep. So the old gateway
// won't be mapping from the host-IPv6 address.
//
// But, if ep is IPv6 only, an existing IPv4 only gateway may be proxying 6To4.
// So, its connectivity needs to be revoked and re-added with noProxy6To4 set.
// Similarly, when an IPv6-only gateway is disconnected from the Sandbox,
// gwepAfter6 will become nil and noProxy6To4 needs to be cleared in the
// configuration of an IPv4-only gateway.
//
// Note that revoking/restoring external connectivity will result in the bridge
// driver assigning new host ports for port mappings where the host port is not
// specified.
noProxy6To4Before := gwepBefore4 != nil && gwepBefore6 != nil && gwepBefore4 != gwepBefore6
noProxy6To4After := gwepAfter4 != nil && gwepAfter6 != nil && gwepAfter4 != gwepAfter6
restartGw4 := ep != gwepAfter4 && noProxy6To4Before != noProxy6To4After
// If ep is the new IPv4 gateway, remove the old IPv4 gateway. log.G(ctx).Infof("sbJoin: gwep4 '%s'->'%s', gwep6 '%s'->'%s'",
if gwepBefore4 != nil && (ep == gwepAfter4 || restartGw4) { epShortId(gwepBefore4), epShortId(gwepAfter4),
role := "IPv4" epShortId(gwepBefore6), epShortId(gwepAfter6))
if gwepAfter6 == gwepAfter4 {
role = "dual-stack" // If ep has taken over as a gateway and there were gateways before, update them.
} if ep == gwepAfter4 || ep == gwepAfter6 {
log.G(ctx).WithFields(log.Fields{ if gwepBefore4 != nil {
"noProxy6To4": noProxy6To4Before, if err := gwepBefore4.programExternalConnectivity(ctx, sb.Labels(), gwepAfter4, gwepAfter6); err != nil {
}).Debug("Revoking external connectivity on endpoint") return fmt.Errorf("updating external connectivity for IPv4 endpoint %s: %v", epShortId(gwepBefore4), err)
undoFunc, err := gwepBefore4.revokeExternalConnectivity()
if err != nil {
return err
}
if restartGw4 {
// The IPv4 gateway hasn't changed, but its noProxy6To4 setting has. So,
// restore it as the gateway with that new setting.
log.G(ctx).WithFields(log.Fields{
"noProxy6To4": noProxy6To4After,
"role": role,
}).Debug("Programming IPv4 gateway endpoint")
if err := undoFunc(ctx, sb.Labels(), epId(gwepAfter4), epId(gwepAfter6)); err != nil {
log.G(ctx).WithError(err).Warn("Failed to restore IPv4 connectivity")
}
} else {
defer func() {
if retErr != nil {
if err := undoFunc(ctx, sb.Labels(), epId(gwepBefore4), epId(gwepBefore6)); err != nil {
log.G(ctx).WithError(err).Warn("Failed to restore connectivity during rollback")
}
}
}()
}
}
// If ep is the new IPv6 gateway, there's an old IPv6 gateway to remove, and it
// wasn't also the IPv4 gateway (removed already) - remove the old gateway.
if ep == gwepAfter6 && gwepBefore6 != nil && gwepBefore6 != gwepBefore4 {
log.G(ctx).Debug("Programming IPv6 gateway endpoint")
undoFunc, err := gwepBefore6.revokeExternalConnectivity()
if err != nil {
return err
} }
defer func() { defer func() {
if retErr != nil { if retErr != nil {
if err := undoFunc(ctx, sb.Labels(), epId(gwepBefore4), epId(gwepBefore6)); err != nil { if err := gwepBefore4.programExternalConnectivity(ctx, sb.Labels(), gwepBefore4, gwepBefore6); err != nil {
log.G(ctx).WithError(err).Warn("Failed to restore IPv6 connectivity during rollback") log.G(ctx).WithFields(log.Fields{
"error": err,
"restoreEp": epShortId(gwepBefore4),
}).Errorf("Failed to restore external IPv4 connectivity")
} }
} }
}() }()
} }
if !n.internal { if gwepBefore6 != nil {
if ecd, ok := d.(driverapi.ExtConner); ok { if err := gwepBefore6.programExternalConnectivity(ctx, sb.Labels(), gwepAfter4, gwepAfter6); err != nil {
log.G(ctx).Debugf("Programming external connectivity on endpoint") return fmt.Errorf("updating external connectivity for IPv6 endpoint %s: %v", epShortId(gwepBefore6), err)
if err := ecd.ProgramExternalConnectivity(ctx, n.ID(), ep.ID(), sb.Labels(), epId(gwepAfter4), epId(gwepAfter6)); err != nil {
return errdefs.System(fmt.Errorf(
"driver failed programming external connectivity on endpoint %s (%s): %v",
ep.Name(), ep.ID(), err))
}
} }
defer func() {
if retErr != nil {
if err := gwepBefore6.programExternalConnectivity(ctx, sb.Labels(), gwepBefore4, gwepBefore6); err != nil {
log.G(ctx).WithFields(log.Fields{
"error": err,
"restoreEp": epShortId(gwepBefore6),
}).Errorf("Failed to restore external IPv6 connectivity")
}
}
}()
} }
} }
// Tell the new endpoint its port mappings, and whether it's a gateway.
if err := ep.programExternalConnectivity(ctx, sb.Labels(), gwepAfter4, gwepAfter6); err != nil {
return err
}
if !sb.needDefaultGW() { if !sb.needDefaultGW() {
if e := sb.clearDefaultGW(); e != nil { if e := sb.clearDefaultGW(); e != nil {
log.G(ctx).WithFields(log.Fields{ log.G(ctx).WithFields(log.Fields{
@@ -727,18 +687,23 @@ func (ep *Endpoint) sbJoin(ctx context.Context, sb *Sandbox, options ...Endpoint
return nil return nil
} }
func (ep *Endpoint) programExternalConnectivity(ctx context.Context, labels map[string]any, gw4, gw6 string) error { func (ep *Endpoint) programExternalConnectivity(ctx context.Context, sbLabels map[string]any, gwep4, gwep6 *Endpoint) error {
log.G(ctx).Debugf("Programming external connectivity on endpoint %s (%s)", ep.Name(), ep.ID()) n, err := ep.getNetworkFromStore()
extN, err := ep.getNetworkFromStore()
if err != nil { if err != nil {
return types.InternalErrorf("failed to get network from store for programming external connectivity: %v", err) return types.InternalErrorf("failed to get network from store for programming external connectivity: %v", err)
} }
extD, err := extN.driver(true) d, err := n.driver(true)
if err != nil { if err != nil {
return types.InternalErrorf("failed to get driver for programming external connectivity: %v", err) return types.InternalErrorf("failed to get driver for programming external connectivity: %v", err)
} }
if ecd, ok := extD.(driverapi.ExtConner); ok { if ecd, ok := d.(driverapi.ExtConner); ok {
if err := ecd.ProgramExternalConnectivity(context.WithoutCancel(ctx), ep.network.ID(), ep.ID(), labels, gw4, gw6); err != nil { log.G(ctx).WithFields(log.Fields{
"ep": ep.Name(),
"epid": epShortId(ep),
"gw4": epShortId(gwep4),
"gw6": epShortId(gwep6),
}).Debug("Programming external connectivity on endpoint")
if err := ecd.ProgramExternalConnectivity(context.WithoutCancel(ctx), n.ID(), ep.ID(), sbLabels, epId(gwep4), epId(gwep6)); err != nil {
return types.InternalErrorf("driver failed programming external connectivity on endpoint %s (%s): %v", return types.InternalErrorf("driver failed programming external connectivity on endpoint %s (%s): %v",
ep.Name(), ep.ID(), err) ep.Name(), ep.ID(), err)
} }
@@ -746,29 +711,6 @@ func (ep *Endpoint) programExternalConnectivity(ctx context.Context, labels map[
return nil return nil
} }
func (ep *Endpoint) revokeExternalConnectivity() (func(context.Context, map[string]any, string, string) error, error) {
extN, err := ep.getNetworkFromStore()
if err != nil {
return nil, types.InternalErrorf("failed to get network from store for revoking external connectivity: %v", err)
}
extD, err := extN.driver(true)
if err != nil {
return nil, types.InternalErrorf("failed to get driver for revoking external connectivity: %v", err)
}
ecd, ok := extD.(driverapi.ExtConner)
if !ok {
return nil, nil
}
if err = ecd.RevokeExternalConnectivity(ep.network.ID(), ep.ID()); err != nil {
return nil, types.InternalErrorf(
"driver failed revoking external connectivity on endpoint %s (%s): %v",
ep.Name(), ep.ID(), err)
}
return func(ctx context.Context, labels map[string]any, gw4, gw6 string) error {
return ecd.ProgramExternalConnectivity(context.WithoutCancel(ctx), ep.network.ID(), ep.ID(), labels, gw4, gw6)
}, nil
}
func (ep *Endpoint) rename(name string) error { func (ep *Endpoint) rename(name string) error {
ep.mu.Lock() ep.mu.Lock()
ep.name = name ep.name = name
@@ -876,18 +818,10 @@ func (ep *Endpoint) sbLeave(ctx context.Context, sb *Sandbox, force bool) error
ep.network = n ep.network = n
ep.mu.Unlock() ep.mu.Unlock()
// Current endpoint(s) providing external connectivity to the sandbox
gwepBefore4, gwepBefore6 := sb.getGatewayEndpoint()
moveExtConn4 := gwepBefore4 != nil && gwepBefore4.ID() == ep.ID()
moveExtConn6 := gwepBefore6 != nil && gwepBefore6.ID() == ep.ID()
if d != nil { if d != nil {
if moveExtConn4 || moveExtConn6 { if ecd, ok := d.(driverapi.ExtConner); ok {
if ecd, ok := d.(driverapi.ExtConner); ok { if err := ecd.ProgramExternalConnectivity(context.WithoutCancel(ctx), n.ID(), ep.ID(), nil, "", ""); err != nil {
log.G(ctx).Debug("Revoking external connectivity on endpoint") log.G(ctx).WithError(err).Warn("driver failed revoking external connectivity on endpoint")
if err := ecd.RevokeExternalConnectivity(n.id, ep.id); err != nil {
log.G(ctx).WithError(err).Warn("driver failed revoking external connectivity on endpoint")
}
} }
} }
@@ -958,45 +892,16 @@ func (ep *Endpoint) sbLeave(ctx context.Context, sb *Sandbox, force bool) error
sb.resolver.SetForwardingPolicy(sb.hasExternalAccess()) sb.resolver.SetForwardingPolicy(sb.hasExternalAccess())
} }
// New endpoint(s) providing external connectivity for the sandbox // Find new endpoint(s) to provide external connectivity for the sandbox.
if moveExtConn4 || moveExtConn6 { gwepAfter4, gwepAfter6 := sb.getGatewayEndpoint()
gwepAfter4, gwepAfter6 := sb.getGatewayEndpoint() if gwepAfter4 != nil {
if gwepAfter4 != nil { if err := gwepAfter4.programExternalConnectivity(ctx, sb.Labels(), gwepAfter4, gwepAfter6); err != nil {
// If the IPv4 gateway hasn't changed, and there was no IPv6 gateway before but log.G(ctx).WithError(err).Error("Failed to set IPv4 gateway")
// there is now, the driver for the IPv4 gateway must not proxy host-IPv6 to
// container-IPv4 (6To4). Conversely, if there was an IPv6 gateway before but
// there isn't one now, the driver must now be told it can proxy 6To4.
//
// Note that revoking/restoring external connectivity will result in the bridge
// driver assigning new host ports for port mappings where the host port is not
// specified.
restartGw4 := gwepBefore4 == gwepAfter4 && ((gwepBefore6 == nil) != (gwepAfter6 == nil))
noProxy6To4 := gwepAfter6 != nil && gwepAfter6 != gwepAfter4
if restartGw4 {
log.G(ctx).WithFields(log.Fields{"noProxy6To4": noProxy6To4}).Debug("Resetting IPv4 endpoint")
if undoFunc, err := gwepBefore4.revokeExternalConnectivity(); err != nil {
log.G(ctx).WithError(err).Error("Failed to restart IPv4 gateway")
} else if err := undoFunc(ctx, sb.Labels(), epId(gwepAfter4), epId(gwepAfter6)); err != nil {
log.G(ctx).WithError(err).Error("Failed to restore IPv4 gateway")
}
} else if moveExtConn4 {
log.G(ctx).Debugf("Programming IPv6 gateway endpoint %s (%s)", ep.Name(), ep.ID())
if err := gwepAfter4.programExternalConnectivity(ctx, sb.Labels(), epId(gwepAfter4), epId(gwepAfter6)); err != nil {
role := "IPv4"
if gwepAfter6 == gwepAfter4 {
role = "dual-stack"
}
log.G(ctx).WithFields(log.Fields{
"role": role,
"error": err,
}).Error("Failed to set gateway")
}
}
} }
if gwepAfter6 != nil && moveExtConn6 && gwepAfter6 != gwepAfter4 { }
if err := gwepAfter6.programExternalConnectivity(ctx, sb.Labels(), epId(gwepAfter4), epId(gwepAfter6)); err != nil { if gwepAfter6 != nil && gwepAfter6 != gwepAfter4 {
log.G(ctx).WithError(err).Error("Failed to set IPv6 gateway") if err := gwepAfter6.programExternalConnectivity(ctx, sb.Labels(), gwepAfter4, gwepAfter6); err != nil {
} log.G(ctx).WithError(err).Error("Failed to set IPv6 gateway")
} }
} }