libn/d/overlay: extract hashable address types

The macAddr and ipmac types are generally useful within libnetwork. Move
them to a dedicated package and overhaul the API to be more like that of
the net/netip package.

Update the overlay driver to utilize these types, adapting to the new
API.

Signed-off-by: Cory Snider <csnider@mirantis.com>
This commit is contained in:
Cory Snider
2025-07-14 16:25:02 -04:00
parent 844023f794
commit c7b93702b9
8 changed files with 188 additions and 111 deletions

View File

@@ -6,12 +6,12 @@ import (
"context"
"errors"
"fmt"
"net"
"net/netip"
"syscall"
"github.com/containerd/log"
"github.com/docker/docker/daemon/libnetwork/driverapi"
"github.com/docker/docker/daemon/libnetwork/internal/hashable"
"github.com/docker/docker/daemon/libnetwork/internal/netiputil"
"github.com/docker/docker/daemon/libnetwork/netlabel"
"github.com/docker/docker/daemon/libnetwork/ns"
@@ -104,7 +104,7 @@ func (d *driver) Join(ctx context.Context, nid, eid string, sboxKey string, jinf
return err
}
if err = nlh.LinkSetHardwareAddr(veth, ep.mac); err != nil {
if err = nlh.LinkSetHardwareAddr(veth, ep.mac.AsSlice()); err != nil {
return fmt.Errorf("could not set mac address (%v) to the container interface: %v", ep.mac, err)
}
@@ -187,7 +187,7 @@ func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key stri
return
}
mac, err := net.ParseMAC(peer.EndpointMAC)
mac, err := hashable.ParseMAC(peer.EndpointMAC)
if err != nil {
log.G(context.TODO()).WithError(err).Errorf("Invalid mac %s received in event notify", peer.EndpointMAC)
return

View File

@@ -6,11 +6,11 @@ import (
"context"
"errors"
"fmt"
"net"
"net/netip"
"github.com/containerd/log"
"github.com/docker/docker/daemon/libnetwork/driverapi"
"github.com/docker/docker/daemon/libnetwork/internal/hashable"
"github.com/docker/docker/daemon/libnetwork/internal/netiputil"
"github.com/docker/docker/daemon/libnetwork/netutils"
"github.com/docker/docker/daemon/libnetwork/ns"
@@ -22,7 +22,7 @@ type endpoint struct {
id string
nid string
ifName string
mac net.HardwareAddr
mac hashable.MACAddr
addr netip.Prefix
}
@@ -48,7 +48,6 @@ func (d *driver) CreateEndpoint(_ context.Context, nid, eid string, ifInfo drive
ep := &endpoint{
id: eid,
nid: n.id,
mac: ifInfo.MacAddress(),
}
var ok bool
ep.addr, ok = netiputil.ToPrefix(ifInfo.Address())
@@ -60,9 +59,19 @@ func (d *driver) CreateEndpoint(_ context.Context, nid, eid string, ifInfo drive
return fmt.Errorf("no matching subnet for IP %q in network %q", ep.addr, nid)
}
if ep.mac == nil {
ep.mac = netutils.GenerateMACFromIP(ep.addr.Addr().AsSlice())
if err := ifInfo.SetMacAddress(ep.mac); err != nil {
if ifmac := ifInfo.MacAddress(); ifmac != nil {
var ok bool
ep.mac, ok = hashable.MACAddrFromSlice(ifInfo.MacAddress())
if !ok {
return fmt.Errorf("invalid MAC address %q assigned to endpoint: unexpected length", ifmac)
}
} else {
var ok bool
ep.mac, ok = hashable.MACAddrFromSlice(netutils.GenerateMACFromIP(ep.addr.Addr().AsSlice()))
if !ok {
panic("GenerateMACFromIP returned a HardwareAddress that is not a MAC-48")
}
if err := ifInfo.SetMacAddress(ep.mac.AsSlice()); err != nil {
return err
}
}

View File

@@ -19,6 +19,7 @@ import (
"github.com/docker/docker/daemon/libnetwork/driverapi"
"github.com/docker/docker/daemon/libnetwork/drivers/overlay/overlayutils"
"github.com/docker/docker/daemon/libnetwork/internal/countmap"
"github.com/docker/docker/daemon/libnetwork/internal/hashable"
"github.com/docker/docker/daemon/libnetwork/internal/netiputil"
"github.com/docker/docker/daemon/libnetwork/netlabel"
"github.com/docker/docker/daemon/libnetwork/ns"
@@ -65,7 +66,7 @@ type network struct {
endpoints endpointTable
joinCnt int
// Ref count of VXLAN Forwarding Database entries programmed into the kernel
fdbCnt countmap.Map[ipmac]
fdbCnt countmap.Map[hashable.IPMAC]
sboxInit bool
initEpoch int
initErr error
@@ -110,7 +111,7 @@ func (d *driver) CreateNetwork(ctx context.Context, id string, option map[string
driver: d,
endpoints: endpointTable{},
subnets: []*subnet{},
fdbCnt: countmap.Map[ipmac]{},
fdbCnt: countmap.Map[hashable.IPMAC]{},
}
vnis := make([]uint32, 0, len(ipV4Data))
@@ -607,7 +608,7 @@ func (n *network) initSandbox() error {
// this is needed to let the peerAdd configure the sandbox
n.sbox = sbox
n.fdbCnt = countmap.Map[ipmac]{}
n.fdbCnt = countmap.Map[hashable.IPMAC]{}
return nil
}

View File

@@ -7,11 +7,11 @@ import (
"context"
"errors"
"fmt"
"net"
"net/netip"
"syscall"
"github.com/containerd/log"
"github.com/docker/docker/daemon/libnetwork/internal/hashable"
"github.com/docker/docker/daemon/libnetwork/internal/setmatrix"
"github.com/docker/docker/daemon/libnetwork/osl"
)
@@ -20,7 +20,7 @@ const ovPeerTable = "overlay_peer_table"
type peerEntry struct {
eid string
mac macAddr
mac hashable.MACAddr
vtep netip.Addr
}
@@ -49,10 +49,10 @@ func (pm *peerMap) Get(peerIP netip.Prefix) (peerEntry, bool) {
return c[0], true
}
func (pm *peerMap) Add(eid string, peerIP netip.Prefix, peerMac net.HardwareAddr, vtep netip.Addr) (bool, int) {
func (pm *peerMap) Add(eid string, peerIP netip.Prefix, peerMac hashable.MACAddr, vtep netip.Addr) (bool, int) {
pEntry := peerEntry{
eid: eid,
mac: macAddrOf(peerMac),
mac: peerMac,
vtep: vtep,
}
b, i := pm.mp.Insert(peerIP, pEntry)
@@ -64,10 +64,10 @@ func (pm *peerMap) Add(eid string, peerIP netip.Prefix, peerMac net.HardwareAddr
return b, i
}
func (pm *peerMap) Delete(eid string, peerIP netip.Prefix, peerMac net.HardwareAddr, vtep netip.Addr) (bool, int) {
func (pm *peerMap) Delete(eid string, peerIP netip.Prefix, peerMac hashable.MACAddr, vtep netip.Addr) (bool, int) {
pEntry := peerEntry{
eid: eid,
mac: macAddrOf(peerMac),
mac: peerMac,
vtep: vtep,
}
@@ -94,7 +94,7 @@ func (n *network) initSandboxPeerDB() error {
var errs []error
n.peerdb.Walk(func(peerIP netip.Prefix, pEntry peerEntry) {
if !pEntry.isLocal() {
if err := n.addNeighbor(peerIP, pEntry.mac.HardwareAddr(), pEntry.vtep); err != nil {
if err := n.addNeighbor(peerIP, pEntry.mac, pEntry.vtep); err != nil {
errs = append(errs, fmt.Errorf("failed to add neighbor entries for %s: %w", peerIP, err))
}
}
@@ -105,7 +105,7 @@ func (n *network) initSandboxPeerDB() error {
// peerAdd adds a new entry to the peer database.
//
// Local peers are signified by an invalid vtep (i.e. netip.Addr{}).
func (n *network) peerAdd(eid string, peerIP netip.Prefix, peerMac net.HardwareAddr, vtep netip.Addr) error {
func (n *network) peerAdd(eid string, peerIP netip.Prefix, peerMac hashable.MACAddr, vtep netip.Addr) error {
if eid == "" {
return errors.New("invalid endpoint id")
}
@@ -130,7 +130,7 @@ func (n *network) peerAdd(eid string, peerIP netip.Prefix, peerMac net.HardwareA
}
// addNeighbor programs the kernel so the given peer is reachable through the VXLAN tunnel.
func (n *network) addNeighbor(peerIP netip.Prefix, peerMac net.HardwareAddr, vtep netip.Addr) error {
func (n *network) addNeighbor(peerIP netip.Prefix, peerMac hashable.MACAddr, vtep netip.Addr) error {
if n.sbox == nil {
// We are hitting this case for all the events that are arriving before that the sandbox
// is being created. The peer got already added into the database and the sandbox init will
@@ -154,13 +154,13 @@ func (n *network) addNeighbor(peerIP netip.Prefix, peerMac net.HardwareAddr, vte
}
// Add neighbor entry for the peer IP
if err := n.sbox.AddNeighbor(peerIP.Addr().AsSlice(), peerMac, osl.WithLinkName(s.vxlanName)); err != nil {
if err := n.sbox.AddNeighbor(peerIP.Addr().AsSlice(), peerMac.AsSlice(), osl.WithLinkName(s.vxlanName)); err != nil {
return fmt.Errorf("could not add neighbor entry into the sandbox: %w", err)
}
// Add fdb entry to the bridge for the peer mac
if n.fdbCnt.Add(ipmacOf(vtep, peerMac), 1) == 1 {
if err := n.sbox.AddNeighbor(vtep.AsSlice(), peerMac, osl.WithLinkName(s.vxlanName), osl.WithFamily(syscall.AF_BRIDGE)); err != nil {
if n.fdbCnt.Add(hashable.IPMACFrom(vtep, peerMac), 1) == 1 {
if err := n.sbox.AddNeighbor(vtep.AsSlice(), peerMac.AsSlice(), osl.WithLinkName(s.vxlanName), osl.WithFamily(syscall.AF_BRIDGE)); err != nil {
return fmt.Errorf("could not add fdb entry into the sandbox: %w", err)
}
}
@@ -171,7 +171,7 @@ func (n *network) addNeighbor(peerIP netip.Prefix, peerMac net.HardwareAddr, vte
// peerDelete removes an entry from the peer database.
//
// Local peers are signified by an invalid vtep (i.e. netip.Addr{}).
func (n *network) peerDelete(eid string, peerIP netip.Prefix, peerMac net.HardwareAddr, vtep netip.Addr) error {
func (n *network) peerDelete(eid string, peerIP netip.Prefix, peerMac hashable.MACAddr, vtep netip.Addr) error {
if eid == "" {
return errors.New("invalid endpoint id")
}
@@ -207,7 +207,7 @@ func (n *network) peerDelete(eid string, peerIP netip.Prefix, peerMac net.Hardwa
if !ok {
return fmt.Errorf("peerDelete: unable to restore a configuration: no entry for %v found in the database", peerIP)
}
err := n.addNeighbor(peerIP, peerEntry.mac.HardwareAddr(), peerEntry.vtep)
err := n.addNeighbor(peerIP, peerEntry.mac, peerEntry.vtep)
if err != nil {
return fmt.Errorf("peer delete operation failed: %w", err)
}
@@ -217,7 +217,7 @@ func (n *network) peerDelete(eid string, peerIP netip.Prefix, peerMac net.Hardwa
// deleteNeighbor removes programming from the kernel for the given peer to be
// reachable through the VXLAN tunnel. It is the inverse of [driver.addNeighbor].
func (n *network) deleteNeighbor(peerIP netip.Prefix, peerMac net.HardwareAddr, vtep netip.Addr) error {
func (n *network) deleteNeighbor(peerIP netip.Prefix, peerMac hashable.MACAddr, vtep netip.Addr) error {
if n.sbox == nil {
return nil
}
@@ -233,14 +233,14 @@ func (n *network) deleteNeighbor(peerIP netip.Prefix, peerMac net.HardwareAddr,
return fmt.Errorf("could not find the subnet %q in network %q", peerIP.String(), n.id)
}
// Remove fdb entry to the bridge for the peer mac
if n.fdbCnt.Add(ipmacOf(vtep, peerMac), -1) == 0 {
if err := n.sbox.DeleteNeighbor(vtep.AsSlice(), peerMac, osl.WithLinkName(s.vxlanName), osl.WithFamily(syscall.AF_BRIDGE)); err != nil {
if n.fdbCnt.Add(hashable.IPMACFrom(vtep, peerMac), -1) == 0 {
if err := n.sbox.DeleteNeighbor(vtep.AsSlice(), peerMac.AsSlice(), osl.WithLinkName(s.vxlanName), osl.WithFamily(syscall.AF_BRIDGE)); err != nil {
return fmt.Errorf("could not delete fdb entry in the sandbox: %w", err)
}
}
// Delete neighbor entry for the peer IP
if err := n.sbox.DeleteNeighbor(peerIP.Addr().AsSlice(), peerMac, osl.WithLinkName(s.vxlanName)); err != nil {
if err := n.sbox.DeleteNeighbor(peerIP.Addr().AsSlice(), peerMac.AsSlice(), osl.WithLinkName(s.vxlanName)); err != nil {
return fmt.Errorf("could not delete neighbor entry in the sandbox:%v", err)
}

View File

@@ -1,52 +0,0 @@
package overlay
// Handy utility types for making unhashable values hashable.
import (
"net"
"net/netip"
)
// macAddr is a hashable encoding of a MAC address.
type macAddr uint64
// macAddrOf converts a net.HardwareAddr to a macAddr.
func macAddrOf(mac net.HardwareAddr) macAddr {
if len(mac) != 6 {
return 0
}
return macAddr(mac[0])<<40 | macAddr(mac[1])<<32 | macAddr(mac[2])<<24 |
macAddr(mac[3])<<16 | macAddr(mac[4])<<8 | macAddr(mac[5])
}
// HardwareAddr converts a macAddr back to a net.HardwareAddr.
func (p macAddr) HardwareAddr() net.HardwareAddr {
mac := [6]byte{
byte(p >> 40), byte(p >> 32), byte(p >> 24),
byte(p >> 16), byte(p >> 8), byte(p),
}
return mac[:]
}
// String returns p.HardwareAddr().String().
func (p macAddr) String() string {
return p.HardwareAddr().String()
}
// ipmac is a hashable tuple of an IP address and a MAC address suitable for use as a map key.
type ipmac struct {
ip netip.Addr
mac macAddr
}
// ipmacOf is a convenience constructor for creating an ipmac from a [net.HardwareAddr].
func ipmacOf(ip netip.Addr, mac net.HardwareAddr) ipmac {
return ipmac{
ip: ip,
mac: macAddrOf(mac),
}
}
func (i ipmac) String() string {
return i.ip.String() + " " + i.mac.String()
}

View File

@@ -1,29 +0,0 @@
package overlay
import (
"net"
"net/netip"
"testing"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestMACAddrOf(t *testing.T) {
want := net.HardwareAddr{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}
assert.DeepEqual(t, macAddrOf(want).HardwareAddr(), want)
}
func TestIPMACOf(t *testing.T) {
assert.Check(t, is.Equal(ipmacOf(netip.Addr{}, nil), ipmac{}))
assert.Check(t, is.Equal(
ipmacOf(
netip.MustParseAddr("11.22.33.44"),
net.HardwareAddr{0x01, 0x02, 0x03, 0x04, 0x05, 0x06},
),
ipmac{
ip: netip.MustParseAddr("11.22.33.44"),
mac: macAddrOf(net.HardwareAddr{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}),
},
))
}

View File

@@ -0,0 +1,82 @@
// 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 hashable provides handy utility types for making unhashable values
// hashable.
package hashable
import (
"net"
"net/netip"
)
// MACAddr is a hashable encoding of a MAC address.
type MACAddr uint64
// MACAddrFromSlice parses the 6-byte slice as a MAC-48 address.
// Note that a [net.HardwareAddr] can be passed directly as the []byte argument.
// If slice's length is not 6, MACAddrFromSlice returns 0, false.
func MACAddrFromSlice(slice net.HardwareAddr) (MACAddr, bool) {
if len(slice) != 6 {
return 0, false
}
return MACAddrFrom6([6]byte(slice)), true
}
// MACAddrFrom6 returns the address of the MAC-48 address
// given by the bytes in addr.
func MACAddrFrom6(addr [6]byte) MACAddr {
return MACAddr(addr[0])<<40 | MACAddr(addr[1])<<32 | MACAddr(addr[2])<<24 |
MACAddr(addr[3])<<16 | MACAddr(addr[4])<<8 | MACAddr(addr[5])
}
// ParseMAC parses s as an IEEE 802 MAC-48 address using one of the formats
// accepted by [net.ParseMAC].
func ParseMAC(s string) (MACAddr, error) {
hw, err := net.ParseMAC(s)
if err != nil {
return 0, err
}
mac, ok := MACAddrFromSlice(hw)
if !ok {
return 0, &net.AddrError{Err: "not a MAC-48 address", Addr: s}
}
return mac, nil
}
// AsSlice returns a MAC address in its 6-byte representation.
func (p MACAddr) AsSlice() []byte {
mac := [6]byte{
byte(p >> 40), byte(p >> 32), byte(p >> 24),
byte(p >> 16), byte(p >> 8), byte(p),
}
return mac[:]
}
// String returns net.HardwareAddr(p.AsSlice()).String().
func (p MACAddr) String() string {
return net.HardwareAddr(p.AsSlice()).String()
}
// IPMAC is a hashable tuple of an IP address and a MAC address suitable for use as a map key.
type IPMAC struct {
ip netip.Addr
mac MACAddr
}
// IPMACFrom returns an [IPMAC] with the provided IP and MAC addresses.
func IPMACFrom(ip netip.Addr, mac MACAddr) IPMAC {
return IPMAC{ip: ip, mac: mac}
}
func (i IPMAC) String() string {
return i.ip.String() + " " + i.mac.String()
}
func (i IPMAC) IP() netip.Addr {
return i.ip
}
func (i IPMAC) MAC() MACAddr {
return i.mac
}

View File

@@ -0,0 +1,66 @@
package hashable
import (
"net"
"net/netip"
"testing"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
// Assert that the types are hashable.
var (
_ map[MACAddr]bool
_ map[IPMAC]bool
)
func TestMACAddrFrom6(t *testing.T) {
want := [6]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}
assert.DeepEqual(t, MACAddrFrom6(want).AsSlice(), want[:])
}
func TestMACAddrFromSlice(t *testing.T) {
mac, ok := MACAddrFromSlice(net.HardwareAddr{0x01, 0x02, 0x03, 0x04, 0x05, 0x06})
assert.Check(t, ok)
assert.Check(t, is.DeepEqual(mac.AsSlice(), []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}))
// Invalid length
for _, tc := range [][]byte{
{0x01, 0x02, 0x03, 0x04, 0x05},
{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07},
{},
nil,
} {
mac, ok = MACAddrFromSlice(net.HardwareAddr(tc))
assert.Check(t, !ok, "want MACAddrFromSlice(%#v) ok=false, got true", tc)
assert.Check(t, is.DeepEqual(mac.AsSlice(), []byte{0, 0, 0, 0, 0, 0}), "want MACAddrFromSlice(%#v) = %#v, got %#v", tc, []byte{0, 0, 0, 0, 0, 0}, mac.AsSlice())
}
}
func TestParseMAC(t *testing.T) {
mac, err := ParseMAC("01:02:03:04:05:06")
assert.Check(t, err)
assert.Check(t, is.DeepEqual(mac.AsSlice(), []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}))
// Invalid MAC address
_, err = ParseMAC("01:02:03:04:05:06:07:08")
assert.Check(t, is.ErrorContains(err, "not a MAC-48 address"))
}
func TestMACAddr_String(t *testing.T) {
mac := MACAddrFrom6([6]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06})
assert.Check(t, is.Equal(mac.String(), "01:02:03:04:05:06"))
assert.Check(t, is.Equal(MACAddr(0).String(), "00:00:00:00:00:00"))
}
func TestIPMACFrom(t *testing.T) {
assert.Check(t, is.Equal(IPMACFrom(netip.Addr{}, 0), IPMAC{}))
ipm := IPMACFrom(
netip.MustParseAddr("11.22.33.44"),
MACAddrFrom6([6]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}),
)
assert.Check(t, is.Equal(ipm.IP(), netip.MustParseAddr("11.22.33.44")))
assert.Check(t, is.Equal(ipm.MAC(), MACAddrFrom6([6]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06})))
}