Skip to content

Commit 0d189dd

Browse files
authored
Merge pull request moby#50321 from robmry/simplify_gateway_programming
Simplify gateway programming
2 parents 1b072f7 + 30b9480 commit 0d189dd

File tree

7 files changed

+178
-242
lines changed

7 files changed

+178
-242
lines changed

libnetwork/driverapi/driverapi.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,19 @@ type Driver interface {
8181

8282
// ExtConner is an optional interface for a network driver.
8383
type ExtConner interface {
84-
// ProgramExternalConnectivity invokes the driver method which does the necessary
85-
// programming to allow the external connectivity dictated by the passed options
86-
ProgramExternalConnectivity(ctx context.Context, nid, eid string, options map[string]interface{}, gw4Id, gw6Id string) error
87-
88-
// RevokeExternalConnectivity asks the driver to remove any external connectivity
89-
// programming that was done so far
90-
RevokeExternalConnectivity(nid, eid string) error
84+
// ProgramExternalConnectivity tells the driver the ids of the endpoints
85+
// currently acting as the container's default gateway for IPv4 and IPv6,
86+
// passed as gw4Id/gw6Id. (Those endpoints may be managed by different network
87+
// drivers. If there is no gateway, the id will be the empty string.)
88+
//
89+
// This method is called after Driver.Join, before Driver.Leave, and when eid
90+
// is or was equal to gw4Id or gw6Id, and there's a change. It may also be
91+
// called when the gateways have not changed.
92+
//
93+
// When an endpoint acting as a gateway is deleted, this function is called
94+
// with that endpoint's id in eid, and empty gateway ids (even if another
95+
// is present and will shortly be selected as the gateway).
96+
ProgramExternalConnectivity(ctx context.Context, nid, eid string, gw4Id, gw6Id string) error
9197
}
9298

9399
// GwAllocChecker is an optional interface for a network driver.

libnetwork/drivers/bridge/bridge_linux.go

Lines changed: 80 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
2+
//go:build go1.23
3+
14
package bridge
25

36
import (
@@ -6,6 +9,7 @@ import (
69
"net"
710
"net/netip"
811
"os"
12+
"slices"
913
"strconv"
1014
"strings"
1115
"sync"
@@ -30,6 +34,7 @@ import (
3034
"github.com/docker/docker/libnetwork/options"
3135
"github.com/docker/docker/libnetwork/scope"
3236
"github.com/docker/docker/libnetwork/types"
37+
"github.com/docker/docker/pkg/stringid"
3338
"github.com/pkg/errors"
3439
"github.com/vishvananda/netlink"
3540
"github.com/vishvananda/netns"
@@ -1442,6 +1447,10 @@ func (d *driver) Join(ctx context.Context, nid, eid string, sboxKey string, jinf
14421447
if err != nil {
14431448
return err
14441449
}
1450+
endpoint.extConnConfig, err = parseConnectivityOptions(sbOpts)
1451+
if err != nil {
1452+
return err
1453+
}
14451454

14461455
iNames := jinfo.InterfaceName()
14471456
containerVethPrefix := defaultContainerVethPrefix
@@ -1461,6 +1470,10 @@ func (d *driver) Join(ctx context.Context, nid, eid string, sboxKey string, jinf
14611470
}
14621471
}
14631472

1473+
if !network.config.EnableICC {
1474+
return d.link(network, endpoint, true)
1475+
}
1476+
14641477
return nil
14651478
}
14661479

@@ -1494,7 +1507,7 @@ type portBindingMode struct {
14941507
ipv6 bool
14951508
}
14961509

1497-
func (d *driver) ProgramExternalConnectivity(ctx context.Context, nid, eid string, options map[string]interface{}, gw4Id, gw6Id string) error {
1510+
func (d *driver) ProgramExternalConnectivity(ctx context.Context, nid, eid string, gw4Id, gw6Id string) (retErr error) {
14981511
ctx, span := otel.Tracer("").Start(ctx, spanPrefix+".ProgramExternalConnectivity", trace.WithAttributes(
14991512
attribute.String("nid", nid),
15001513
attribute.String("eid", eid),
@@ -1521,11 +1534,6 @@ func (d *driver) ProgramExternalConnectivity(ctx context.Context, nid, eid strin
15211534
return endpointNotFoundError(eid)
15221535
}
15231536

1524-
endpoint.extConnConfig, err = parseConnectivityOptions(options)
1525-
if err != nil {
1526-
return err
1527-
}
1528-
15291537
var pbmReq portBindingMode
15301538
// Act as the IPv4 gateway if explicitly selected.
15311539
if gw4Id == eid {
@@ -1538,30 +1546,31 @@ func (d *driver) ProgramExternalConnectivity(ctx context.Context, nid, eid strin
15381546
pbmReq.ipv6 = true
15391547
}
15401548

1541-
// Program any required port mapping and store them in the endpoint
1542-
if endpoint.extConnConfig != nil && endpoint.extConnConfig.PortBindings != nil {
1543-
endpoint.portMapping, err = network.addPortMappings(
1544-
ctx,
1545-
endpoint,
1546-
endpoint.extConnConfig.PortBindings,
1547-
network.config.DefaultBindingIP,
1548-
pbmReq,
1549-
)
1550-
if err != nil {
1551-
return err
1552-
}
1549+
// If no change is needed, return.
1550+
if endpoint.portBindingState == pbmReq {
1551+
return nil
15531552
}
15541553

1554+
// Remove port bindings that aren't needed due to a change in mode.
1555+
undoTrim, err := endpoint.trimPortBindings(ctx, network, pbmReq)
1556+
if err != nil {
1557+
return err
1558+
}
15551559
defer func() {
1556-
if err != nil {
1557-
if e := network.releasePorts(endpoint); e != nil {
1558-
log.G(ctx).Errorf("Failed to release ports allocated for the bridge endpoint %s on failure %v because of %v",
1559-
eid, err, e)
1560-
}
1561-
endpoint.portMapping = nil
1560+
if retErr != nil && undoTrim != nil {
1561+
endpoint.portMapping = append(endpoint.portMapping, undoTrim()...)
15621562
}
15631563
}()
15641564

1565+
// Set up new port bindings, and store them in the endpoint.
1566+
if (pbmReq.ipv4 || pbmReq.ipv6) && endpoint.extConnConfig != nil && endpoint.extConnConfig.PortBindings != nil {
1567+
newPMs, err := network.addPortMappings(ctx, endpoint, endpoint.extConnConfig.PortBindings, network.config.DefaultBindingIP, pbmReq)
1568+
if err != nil {
1569+
return err
1570+
}
1571+
endpoint.portMapping = append(endpoint.portMapping, newPMs...)
1572+
}
1573+
15651574
// Remember the new port binding state.
15661575
endpoint.portBindingState = pbmReq
15671576

@@ -1573,50 +1582,62 @@ func (d *driver) ProgramExternalConnectivity(ctx context.Context, nid, eid strin
15731582
return fmt.Errorf("failed to update bridge endpoint %.7s to store: %v", endpoint.id, err)
15741583
}
15751584

1576-
if !network.config.EnableICC {
1577-
return d.link(network, endpoint, true)
1578-
}
1579-
15801585
return nil
15811586
}
15821587

1583-
func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
1584-
// Make sure this function isn't deleting iptables rules while handleFirewalldReloadNw
1585-
// is restoring those same rules.
1586-
d.configNetwork.Lock()
1587-
defer d.configNetwork.Unlock()
1588-
1589-
network, err := d.getNetwork(nid)
1590-
if err != nil {
1591-
return err
1592-
}
1593-
1594-
endpoint, err := network.getEndpoint(eid)
1595-
if err != nil {
1596-
return err
1588+
// trimPortBindings compares pbmReq with the current port bindings, and removes
1589+
// port bindings that are no longer required.
1590+
//
1591+
// ep.portMapping is updated when bindings are removed.
1592+
func (ep *bridgeEndpoint) trimPortBindings(ctx context.Context, n *bridgeNetwork, pbmReq portBindingMode) (func() []portBinding, error) {
1593+
// If the endpoint is the gateway for IPv4 and IPv6, there's nothing to drop.
1594+
if pbmReq.ipv4 && pbmReq.ipv6 {
1595+
return nil, nil
15971596
}
15981597

1599-
if endpoint == nil {
1600-
return endpointNotFoundError(eid)
1598+
toDrop := make([]portBinding, 0, len(ep.portMapping))
1599+
toKeep := slices.DeleteFunc(ep.portMapping, func(pb portBinding) bool {
1600+
is4 := pb.HostIP.To4() != nil
1601+
if (is4 && !pbmReq.ipv4) || (!is4 && !pbmReq.ipv6) {
1602+
toDrop = append(toDrop, pb)
1603+
return true
1604+
}
1605+
return false
1606+
})
1607+
if len(toDrop) == 0 {
1608+
return nil, nil
16011609
}
16021610

1603-
err = network.releasePorts(endpoint)
1604-
if err != nil {
1605-
log.G(context.TODO()).Warn(err)
1611+
if err := releasePortBindings(toDrop, n.firewallerNetwork); err != nil {
1612+
log.G(ctx).WithFields(log.Fields{
1613+
"error": err,
1614+
"gw4": pbmReq.ipv4,
1615+
"gw6": pbmReq.ipv6,
1616+
"nid": stringid.TruncateID(n.id),
1617+
"eid": stringid.TruncateID(ep.id),
1618+
}).Error("Failed to release port bindings")
1619+
return nil, err
16061620
}
1621+
ep.portMapping = toKeep
16071622

1608-
endpoint.portMapping = nil
1609-
1610-
// Clean the connection tracker state of the host for the specific endpoint. This is a precautionary measure to
1611-
// avoid new endpoints getting the same IP address to receive unexpected packets due to bad conntrack state leading
1612-
// to bad NATing.
1613-
clearConntrackEntries(d.nlh, endpoint)
1614-
1615-
if err = d.storeUpdate(context.TODO(), endpoint); err != nil {
1616-
return fmt.Errorf("failed to update bridge endpoint %.7s to store: %v", endpoint.id, err)
1623+
undo := func() []portBinding {
1624+
pbReq := make([]types.PortBinding, 0, len(toDrop))
1625+
for _, pb := range toDrop {
1626+
pbReq = append(pbReq, pb.PortBinding)
1627+
}
1628+
pbs, err := n.addPortMappings(ctx, ep, pbReq, n.config.DefaultBindingIP, ep.portBindingState)
1629+
if err != nil {
1630+
log.G(ctx).WithFields(log.Fields{
1631+
"error": err,
1632+
"nid": stringid.TruncateID(n.id),
1633+
"eid": stringid.TruncateID(ep.id),
1634+
}).Error("Failed to restore port bindings following join failure")
1635+
return nil
1636+
}
1637+
return pbs
16171638
}
16181639

1619-
return nil
1640+
return undo, nil
16201641
}
16211642

16221643
// clearConntrackEntries flushes conntrack entries matching endpoint IP address
@@ -1677,8 +1698,8 @@ func (d *driver) handleFirewalldReloadNw(nid string) {
16771698
return
16781699
}
16791700

1680-
// Make sure the network isn't being deleted, and ProgramExternalConnectivity/RevokeExternalConnectivity
1681-
// aren't modifying iptables rules, while restoring the rules.
1701+
// Make sure the network isn't being deleted, and ProgramExternalConnectivity
1702+
// isn't modifying iptables rules, while restoring the rules.
16821703
d.configNetwork.Lock()
16831704
defer d.configNetwork.Unlock()
16841705

libnetwork/drivers/bridge/bridge_linux_test.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) {
843843
t.Fatalf("Failed to join the endpoint: %v", err)
844844
}
845845

846-
err = d.ProgramExternalConnectivity(context.Background(), "net1", "ep1", sbOptions, "ep1", "")
846+
err = d.ProgramExternalConnectivity(context.Background(), "net1", "ep1", "ep1", "")
847847
if err != nil {
848848
t.Fatalf("Failed to program external connectivity: %v", err)
849849
}
@@ -874,7 +874,7 @@ func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) {
874874
}
875875
}
876876

877-
err = d.RevokeExternalConnectivity("net1", "ep1")
877+
err = d.ProgramExternalConnectivity(context.Background(), "net1", "ep1", "", "")
878878
if err != nil {
879879
t.Fatal(err)
880880
}
@@ -947,7 +947,7 @@ func TestLinkContainers(t *testing.T) {
947947
t.Fatalf("Failed to join the endpoint: %v", err)
948948
}
949949

950-
err = d.ProgramExternalConnectivity(context.Background(), "net1", "ep1", sbOptions, "ep1", "")
950+
err = d.ProgramExternalConnectivity(context.Background(), "net1", "ep1", "ep1", "")
951951
if err != nil {
952952
t.Fatalf("Failed to program external connectivity: %v", err)
953953
}
@@ -978,7 +978,7 @@ func TestLinkContainers(t *testing.T) {
978978
t.Fatal("Failed to link ep1 and ep2")
979979
}
980980

981-
err = d.ProgramExternalConnectivity(context.Background(), "net1", "ep2", sbOptions, "ep2", "ep2")
981+
err = d.ProgramExternalConnectivity(context.Background(), "net1", "ep2", "ep2", "ep2")
982982
if err != nil {
983983
t.Fatalf("Failed to program external connectivity: %v", err)
984984
}
@@ -997,7 +997,7 @@ func TestLinkContainers(t *testing.T) {
997997
}
998998
checkLink(true)
999999

1000-
err = d.RevokeExternalConnectivity("net1", "ep2")
1000+
err = d.ProgramExternalConnectivity(context.Background(), "net1", "ep2", "", "")
10011001
if err != nil {
10021002
t.Fatalf("Failed to revoke external connectivity: %v", err)
10031003
}
@@ -1014,10 +1014,6 @@ func TestLinkContainers(t *testing.T) {
10141014
}
10151015

10161016
err = d.Join(context.Background(), "net1", "ep2", "", te2, nil, sbOptions)
1017-
if err != nil {
1018-
t.Fatal(err)
1019-
}
1020-
err = d.ProgramExternalConnectivity(context.Background(), "net1", "ep2", sbOptions, "ep2", "ep2")
10211017
assert.Check(t, err != nil, "Expected Join to fail given link conditions are not satisfied")
10221018
checkLink(false)
10231019
}

libnetwork/drivers/bridge/bridge_store.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,4 +477,5 @@ func (n *bridgeNetwork) restorePortAllocations(ep *bridgeEndpoint) {
477477
if err != nil {
478478
log.G(context.TODO()).Warnf("Failed to reserve existing port mapping for endpoint %.7s:%v", ep.id, err)
479479
}
480+
ep.portBindingState = pbm
480481
}

libnetwork/drivers/bridge/port_mapping_linux_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func TestPortMappingConfig(t *testing.T) {
7373
t.Fatalf("Failed to join the endpoint: %v", err)
7474
}
7575

76-
if err = d.ProgramExternalConnectivity(context.Background(), "dummy", "ep1", sbOptions, "ep1", ""); err != nil {
76+
if err = d.ProgramExternalConnectivity(context.Background(), "dummy", "ep1", "ep1", ""); err != nil {
7777
t.Fatalf("Failed to program external connectivity: %v", err)
7878
}
7979

@@ -102,7 +102,7 @@ func TestPortMappingConfig(t *testing.T) {
102102
t.Fatal(err)
103103
}
104104

105-
err = d.RevokeExternalConnectivity("dummy", "ep1")
105+
err = d.ProgramExternalConnectivity(context.Background(), "dummy", "ep1", "", "")
106106
if err != nil {
107107
t.Fatal(err)
108108
}
@@ -159,7 +159,7 @@ func TestPortMappingV6Config(t *testing.T) {
159159
t.Fatalf("Failed to join the endpoint: %v", err)
160160
}
161161

162-
if err = d.ProgramExternalConnectivity(context.Background(), "dummy", "ep1", sbOptions, "ep1", ""); err != nil {
162+
if err = d.ProgramExternalConnectivity(context.Background(), "dummy", "ep1", "ep1", "ep1"); err != nil {
163163
t.Fatalf("Failed to program external connectivity: %v", err)
164164
}
165165

@@ -178,7 +178,7 @@ func TestPortMappingV6Config(t *testing.T) {
178178
t.Fatal(err)
179179
}
180180

181-
err = d.RevokeExternalConnectivity("dummy", "ep1")
181+
err = d.ProgramExternalConnectivity(context.Background(), "dummy", "ep1", "", "")
182182
if err != nil {
183183
t.Fatal(err)
184184
}

0 commit comments

Comments
 (0)