diff --git a/daemon/cluster/listen_addr_linux.go b/daemon/cluster/listen_addr_linux.go index 62e4f61a6534f..e8e2bddadcd84 100644 --- a/daemon/cluster/listen_addr_linux.go +++ b/daemon/cluster/listen_addr_linux.go @@ -3,13 +3,14 @@ package cluster // import "github.com/docker/docker/daemon/cluster" import ( "net" + "github.com/docker/docker/internal/nlwrap" "github.com/vishvananda/netlink" ) func (c *Cluster) resolveSystemAddr() (net.IP, error) { // Use the system's only device IP address, or fail if there are // multiple addresses to choose from. - interfaces, err := netlink.LinkList() + interfaces, err := nlwrap.LinkList() if err != nil { return nil, err } @@ -26,7 +27,7 @@ func (c *Cluster) resolveSystemAddr() (net.IP, error) { continue } - addrs, err := netlink.AddrList(intf, netlink.FAMILY_ALL) + addrs, err := nlwrap.AddrList(intf, netlink.FAMILY_ALL) if err != nil { continue } diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 10ab139245a53..408dd53cb5b00 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -27,6 +27,7 @@ import ( "github.com/docker/docker/daemon/config" "github.com/docker/docker/daemon/initlayer" "github.com/docker/docker/errdefs" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/libcontainerd/remote" "github.com/docker/docker/libnetwork" nwconfig "github.com/docker/docker/libnetwork/config" @@ -1065,7 +1066,7 @@ func initBridgeDriver(controller *libnetwork.Controller, cfg config.BridgeConfig // Remove default bridge interface if present (--bridge=none use case) func removeDefaultBridgeInterface() { - if lnk, err := netlink.LinkByName(bridge.DefaultBridgeName); err == nil { + if lnk, err := nlwrap.LinkByName(bridge.DefaultBridgeName); err == nil { if err := netlink.LinkDel(lnk); err != nil { log.G(context.TODO()).Warnf("Failed to remove bridge interface (%s): %v", bridge.DefaultBridgeName, err) } diff --git a/integration-cli/docker_cli_network_unix_test.go b/integration-cli/docker_cli_network_unix_test.go index a81da8a6de5ec..825caffb76d3b 100644 --- a/integration-cli/docker_cli_network_unix_test.go +++ b/integration-cli/docker_cli_network_unix_test.go @@ -18,6 +18,7 @@ import ( "github.com/docker/docker/api/types/network" "github.com/docker/docker/integration-cli/cli" "github.com/docker/docker/integration-cli/daemon" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/libnetwork/driverapi" remoteapi "github.com/docker/docker/libnetwork/drivers/remote/api" "github.com/docker/docker/libnetwork/ipamapi" @@ -120,7 +121,7 @@ func setupRemoteNetworkDrivers(t *testing.T, mux *http.ServeMux, url, netDrv, ip mux.HandleFunc(fmt.Sprintf("/%s.DeleteEndpoint", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", plugins.VersionMimetype) - if link, err := netlink.LinkByName("cnt0"); err == nil { + if link, err := nlwrap.LinkByName("cnt0"); err == nil { err = netlink.LinkDel(link) assert.NilError(t, err) } @@ -1778,7 +1779,7 @@ func (s *DockerNetworkSuite) TestConntrackFlowsLeak(c *testing.T) { cli.DockerCmd(c, "run", "-d", "--name", "client", "--net=host", "busybox", "sh", "-c", cmd) // Get all the flows using netlink - flows, err := netlink.ConntrackTableList(netlink.ConntrackTable, unix.AF_INET) + flows, err := nlwrap.ConntrackTableList(netlink.ConntrackTable, unix.AF_INET) assert.NilError(c, err) var flowMatch int for _, flow := range flows { @@ -1796,7 +1797,7 @@ func (s *DockerNetworkSuite) TestConntrackFlowsLeak(c *testing.T) { cli.DockerCmd(c, "rm", "-fv", "server") // Fetch again all the flows and validate that there is no server flow in the conntrack laying around - flows, err = netlink.ConntrackTableList(netlink.ConntrackTable, unix.AF_INET) + flows, err = nlwrap.ConntrackTableList(netlink.ConntrackTable, unix.AF_INET) assert.NilError(c, err) flowMatch = 0 for _, flow := range flows { diff --git a/integration-cli/docker_cli_swarm_test.go b/integration-cli/docker_cli_swarm_test.go index 1420f1a8ed72c..a32ddc035dd3b 100644 --- a/integration-cli/docker_cli_swarm_test.go +++ b/integration-cli/docker_cli_swarm_test.go @@ -22,6 +22,7 @@ import ( "github.com/docker/docker/integration-cli/checker" "github.com/docker/docker/integration-cli/cli" "github.com/docker/docker/integration-cli/daemon" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/libnetwork/driverapi" "github.com/docker/docker/libnetwork/ipamapi" remoteipam "github.com/docker/docker/libnetwork/ipams/remote/api" @@ -718,7 +719,7 @@ func setupRemoteGlobalNetworkPlugin(t *testing.T, mux *http.ServeMux, url, netDr mux.HandleFunc(fmt.Sprintf("/%s.DeleteEndpoint", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", plugins.VersionMimetype) - if link, err := netlink.LinkByName("cnt0"); err == nil { + if link, err := nlwrap.LinkByName("cnt0"); err == nil { err := netlink.LinkDel(link) assert.NilError(t, err) } diff --git a/internal/nlwrap/nlwrap_linux.go b/internal/nlwrap/nlwrap_linux.go new file mode 100644 index 0000000000000..cda5e25a7af80 --- /dev/null +++ b/internal/nlwrap/nlwrap_linux.go @@ -0,0 +1,172 @@ +// Package nlwrap wraps vishvandanda/netlink functions that may return EINTR. +// +// A Handle instantiated using [NewHandle] or [NewHandleAt] can be used in place +// of a netlink.Handle, it's a wrapper that replaces methods that need to be +// wrapped. Functions that use the package handle need to be called as "nlwrap.X" +// instead of "netlink.X". +// +// The wrapped functions currently return EINTR when NLM_F_DUMP_INTR flagged +// in a netlink response, meaning something changed during the dump so results +// may be incomplete or inconsistent. +// +// At present, the possibly incomplete/inconsistent results are not returned +// by netlink functions along with the EINTR. So, it's not possible to do +// anything but retry. After maxAttempts the EINTR will be returned to the +// caller. +package nlwrap + +import ( + "context" + "errors" + + "github.com/containerd/log" + "github.com/vishvananda/netlink" + "github.com/vishvananda/netns" + "golang.org/x/sys/unix" +) + +// Arbitrary limit on max attempts at netlink calls if they are repeatedly interrupted. +const maxAttempts = 5 + +type Handle struct { + *netlink.Handle +} + +func NewHandle(nlFamilies ...int) (Handle, error) { + nlh, err := netlink.NewHandle(nlFamilies...) + if err != nil { + return Handle{}, err + } + return Handle{nlh}, nil +} + +func NewHandleAt(ns netns.NsHandle, nlFamilies ...int) (Handle, error) { + nlh, err := netlink.NewHandleAt(ns, nlFamilies...) + if err != nil { + return Handle{}, err + } + return Handle{nlh}, nil +} + +func (h Handle) Close() { + if h.Handle != nil { + h.Handle.Close() + } +} + +func retryOnIntr(f func() error) { + for attempt := 0; attempt < maxAttempts; attempt += 1 { + if err := f(); !errors.Is(err, unix.EINTR) { + return + } + } + log.G(context.TODO()).Infof("netlink call interrupted after %d attempts", maxAttempts) +} + +// AddrList calls nlh.LinkList, retrying if necessary. +func (nlh Handle) AddrList(link netlink.Link, family int) (addrs []netlink.Addr, err error) { + retryOnIntr(func() error { + addrs, err = nlh.Handle.AddrList(link, family) + return err + }) + return addrs, err +} + +// AddrList calls netlink.LinkList, retrying if necessary. +func AddrList(link netlink.Link, family int) (addrs []netlink.Addr, err error) { + retryOnIntr(func() error { + addrs, err = netlink.AddrList(link, family) + return err + }) + return addrs, err +} + +// ConntrackDeleteFilters calls nlh.ConntrackDeleteFilters, retrying if necessary. +func (nlh Handle) ConntrackDeleteFilters( + table netlink.ConntrackTableType, + family netlink.InetFamily, + filters ...netlink.CustomConntrackFilter, +) (matched uint, err error) { + retryOnIntr(func() error { + matched, err = nlh.Handle.ConntrackDeleteFilters(table, family, filters...) + return err + }) + return matched, err +} + +// ConntrackTableList calls netlink.ConntrackTableList, retrying if necessary. +func ConntrackTableList( + table netlink.ConntrackTableType, + family netlink.InetFamily, +) (flows []*netlink.ConntrackFlow, err error) { + retryOnIntr(func() error { + flows, err = netlink.ConntrackTableList(table, family) + return err + }) + return flows, err +} + +// LinkByName calls nlh.LinkByName, retrying if necessary. The netlink function +// doesn't normally ask the kernel for a dump of links. But, on an old kernel, it +// will do as a fallback and that dump may get inconsistent results. +func (nlh Handle) LinkByName(name string) (link netlink.Link, err error) { + retryOnIntr(func() error { + link, err = nlh.Handle.LinkByName(name) + return err + }) + return link, err +} + +// LinkByName calls netlink.LinkByName, retrying if necessary. The netlink +// function doesn't normally ask the kernel for a dump of links. But, on an old +// kernel, it will do as a fallback and that dump may get inconsistent results. +func LinkByName(name string) (link netlink.Link, err error) { + retryOnIntr(func() error { + link, err = netlink.LinkByName(name) + return err + }) + return link, err +} + +// LinkList calls nlh.LinkList, retrying if necessary. +func (nlh Handle) LinkList() (links []netlink.Link, err error) { + retryOnIntr(func() error { + links, err = nlh.Handle.LinkList() + return err + }) + return links, err +} + +// LinkList calls netlink.LinkList, retrying if necessary. +func LinkList() (links []netlink.Link, err error) { + retryOnIntr(func() error { + links, err = netlink.LinkList() + return err + }) + return links, err +} + +// RouteList calls nlh.RouteList, retrying if necessary. +func (nlh Handle) RouteList(link netlink.Link, family int) (routes []netlink.Route, err error) { + retryOnIntr(func() error { + routes, err = nlh.Handle.RouteList(link, family) + return err + }) + return routes, err +} + +func (nlh Handle) XfrmPolicyList(family int) (policies []netlink.XfrmPolicy, err error) { + retryOnIntr(func() error { + policies, err = nlh.Handle.XfrmPolicyList(family) + return err + }) + return policies, err +} + +func (nlh Handle) XfrmStateList(family int) (states []netlink.XfrmState, err error) { + retryOnIntr(func() error { + states, err = nlh.Handle.XfrmStateList(family) + return err + }) + return states, err +} diff --git a/libnetwork/drivers/bridge/bridge_linux.go b/libnetwork/drivers/bridge/bridge_linux.go index 0057344e56ea2..06d96638bd4c9 100644 --- a/libnetwork/drivers/bridge/bridge_linux.go +++ b/libnetwork/drivers/bridge/bridge_linux.go @@ -12,6 +12,7 @@ import ( "github.com/containerd/log" "github.com/docker/docker/errdefs" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/libnetwork/datastore" "github.com/docker/docker/libnetwork/driverapi" "github.com/docker/docker/libnetwork/drivers/bridge/internal/rlkclient" @@ -155,7 +156,7 @@ type driver struct { isolationChain2V6 *iptables.ChainInfo networks map[string]*bridgeNetwork store *datastore.Store - nlh *netlink.Handle + nlh nlwrap.Handle portDriverClient portDriverClient configNetwork sync.Mutex sync.Mutex @@ -808,7 +809,7 @@ func (d *driver) checkConflict(config *networkConfiguration) error { func (d *driver) createNetwork(config *networkConfiguration) (err error) { // Initialize handle when needed d.Lock() - if d.nlh == nil { + if d.nlh.Handle == nil { d.nlh = ns.NlHandle() } d.Unlock() @@ -1018,7 +1019,7 @@ func (d *driver) deleteNetwork(nid string) error { return d.storeDelete(config) } -func addToBridge(ctx context.Context, nlh *netlink.Handle, ifaceName, bridgeName string) error { +func addToBridge(ctx context.Context, nlh nlwrap.Handle, ifaceName, bridgeName string) error { ctx, span := otel.Tracer("").Start(ctx, "libnetwork.drivers.bridge.addToBridge", trace.WithAttributes( attribute.String("ifaceName", ifaceName), attribute.String("bridgeName", bridgeName))) @@ -1035,7 +1036,7 @@ func addToBridge(ctx context.Context, nlh *netlink.Handle, ifaceName, bridgeName return nil } -func setHairpinMode(nlh *netlink.Handle, link netlink.Link, enable bool) error { +func setHairpinMode(nlh nlwrap.Handle, link netlink.Link, enable bool) error { err := nlh.LinkSetHairpin(link, enable) if err != nil { return fmt.Errorf("unable to set hairpin mode on %s via netlink: %v", diff --git a/libnetwork/drivers/bridge/bridge_linux_test.go b/libnetwork/drivers/bridge/bridge_linux_test.go index ba1671b8f4bb8..208bcdaf7aa02 100644 --- a/libnetwork/drivers/bridge/bridge_linux_test.go +++ b/libnetwork/drivers/bridge/bridge_linux_test.go @@ -11,6 +11,7 @@ import ( "strconv" "testing" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/internal/testutils/netnsutils" "github.com/docker/docker/libnetwork/driverapi" "github.com/docker/docker/libnetwork/internal/netiputil" @@ -1226,7 +1227,7 @@ func TestCreateWithExistingBridge(t *testing.T) { t.Fatalf("Failed to delete network %s: %v", brName, err) } - if _, err := netlink.LinkByName(brName); err != nil { + if _, err := nlwrap.LinkByName(brName); err != nil { t.Fatal("Deleting bridge network that using existing bridge interface unexpectedly deleted the bridge interface") } } diff --git a/libnetwork/drivers/bridge/interface_linux.go b/libnetwork/drivers/bridge/interface_linux.go index 4ed940e646a95..48ef59bb79e32 100644 --- a/libnetwork/drivers/bridge/interface_linux.go +++ b/libnetwork/drivers/bridge/interface_linux.go @@ -8,6 +8,7 @@ import ( "github.com/containerd/log" "github.com/docker/docker/errdefs" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/libnetwork/internal/netiputil" "github.com/vishvananda/netlink" ) @@ -25,14 +26,14 @@ type bridgeInterface struct { bridgeIPv6 *net.IPNet gatewayIPv4 net.IP gatewayIPv6 net.IP - nlh *netlink.Handle + nlh nlwrap.Handle } // newInterface creates a new bridge interface structure. It attempts to find // an already existing device identified by the configuration BridgeName field, // or the default bridge name when unspecified, but doesn't attempt to create // one when missing -func newInterface(nlh *netlink.Handle, config *networkConfiguration) (*bridgeInterface, error) { +func newInterface(nlh nlwrap.Handle, config *networkConfiguration) (*bridgeInterface, error) { var err error i := &bridgeInterface{nlh: nlh} diff --git a/libnetwork/drivers/bridge/interface_linux_test.go b/libnetwork/drivers/bridge/interface_linux_test.go index a661d09379ef6..832554be34a69 100644 --- a/libnetwork/drivers/bridge/interface_linux_test.go +++ b/libnetwork/drivers/bridge/interface_linux_test.go @@ -5,6 +5,7 @@ import ( "sort" "testing" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/internal/testutils/netnsutils" "github.com/vishvananda/netlink" "golang.org/x/sys/unix" @@ -28,7 +29,7 @@ func addAddr(t *testing.T, link netlink.Link, addr string) { func prepTestBridge(t *testing.T, nc *networkConfiguration) *bridgeInterface { t.Helper() - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() assert.Assert(t, err) i, err := newInterface(nh, nc) assert.Assert(t, err) @@ -40,7 +41,7 @@ func prepTestBridge(t *testing.T, nc *networkConfiguration) *bridgeInterface { func TestInterfaceDefaultName(t *testing.T) { defer netnsutils.SetupTestOSContext(t)() - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() if err != nil { t.Fatal(err) } @@ -60,7 +61,7 @@ func TestAddressesNoInterface(t *testing.T) { func TestAddressesEmptyInterface(t *testing.T) { defer netnsutils.SetupTestOSContext(t)() - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() assert.NilError(t, err) inf, err := newInterface(nh, &networkConfiguration{}) diff --git a/libnetwork/drivers/bridge/network_linux_test.go b/libnetwork/drivers/bridge/network_linux_test.go index e5827ebb81367..f1a664a941af4 100644 --- a/libnetwork/drivers/bridge/network_linux_test.go +++ b/libnetwork/drivers/bridge/network_linux_test.go @@ -4,10 +4,10 @@ import ( "context" "testing" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/internal/testutils/netnsutils" "github.com/docker/docker/libnetwork/driverapi" "github.com/docker/docker/libnetwork/netlabel" - "github.com/vishvananda/netlink" ) func TestLinkCreate(t *testing.T) { @@ -55,7 +55,7 @@ func TestLinkCreate(t *testing.T) { } // Verify sbox endpoint interface inherited MTU value from bridge config - sboxLnk, err := netlink.LinkByName(te.iface.srcName) + sboxLnk, err := nlwrap.LinkByName(te.iface.srcName) if err != nil { t.Fatal(err) } @@ -75,7 +75,7 @@ func TestLinkCreate(t *testing.T) { t.Fatal("Invalid Dstname returned") } - _, err = netlink.LinkByName(te.iface.srcName) + _, err = nlwrap.LinkByName(te.iface.srcName) if err != nil { t.Fatalf("Could not find source link %s: %v", te.iface.srcName, err) } diff --git a/libnetwork/drivers/bridge/setup_device_linux_test.go b/libnetwork/drivers/bridge/setup_device_linux_test.go index 05f3513314885..41edf97c5d82e 100644 --- a/libnetwork/drivers/bridge/setup_device_linux_test.go +++ b/libnetwork/drivers/bridge/setup_device_linux_test.go @@ -6,16 +6,16 @@ import ( "syscall" "testing" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/internal/testutils/netnsutils" "github.com/docker/docker/libnetwork/netutils" - "github.com/vishvananda/netlink" "gotest.tools/v3/assert" ) func TestSetupNewBridge(t *testing.T) { defer netnsutils.SetupTestOSContext(t)() - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() if err != nil { t.Fatal(err) } @@ -41,7 +41,7 @@ func TestSetupNewBridge(t *testing.T) { func TestSetupNewNonDefaultBridge(t *testing.T) { defer netnsutils.SetupTestOSContext(t)() - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() if err != nil { t.Fatal(err) } @@ -63,7 +63,7 @@ func TestSetupNewNonDefaultBridge(t *testing.T) { func TestSetupDeviceUp(t *testing.T) { defer netnsutils.SetupTestOSContext(t)() - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() if err != nil { t.Fatal(err) } @@ -98,7 +98,7 @@ func TestGenerateRandomMAC(t *testing.T) { func TestMTUBiggerThan1500(t *testing.T) { defer netnsutils.SetupTestOSContext(t)() - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() if err != nil { t.Fatal(err) } @@ -114,7 +114,7 @@ func TestMTUBiggerThan1500(t *testing.T) { func TestMTUBiggerThan64K(t *testing.T) { defer netnsutils.SetupTestOSContext(t)() - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() if err != nil { t.Fatal(err) } diff --git a/libnetwork/drivers/bridge/setup_ip_tables_linux.go b/libnetwork/drivers/bridge/setup_ip_tables_linux.go index dd8810fbbd8a0..ad7be89416460 100644 --- a/libnetwork/drivers/bridge/setup_ip_tables_linux.go +++ b/libnetwork/drivers/bridge/setup_ip_tables_linux.go @@ -9,9 +9,9 @@ import ( "github.com/containerd/log" "github.com/docker/docker/errdefs" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/libnetwork/iptables" "github.com/docker/docker/libnetwork/types" - "github.com/vishvananda/netlink" ) // DockerChain: DOCKER iptable chain name @@ -482,7 +482,7 @@ func setupInternalNetworkRules(bridgeIface string, addr *net.IPNet, icc, insert // As such, we need to flush all those conntrack entries to make sure NAT rules // are correctly applied to all packets. // See: #8795, #44688 & #44742. -func clearConntrackEntries(nlh *netlink.Handle, ep *bridgeEndpoint) { +func clearConntrackEntries(nlh nlwrap.Handle, ep *bridgeEndpoint) { var ipv4List []net.IP var ipv6List []net.IP var udpPorts []uint16 diff --git a/libnetwork/drivers/bridge/setup_ip_tables_linux_test.go b/libnetwork/drivers/bridge/setup_ip_tables_linux_test.go index 440286cd73476..cff66608c44ec 100644 --- a/libnetwork/drivers/bridge/setup_ip_tables_linux_test.go +++ b/libnetwork/drivers/bridge/setup_ip_tables_linux_test.go @@ -4,11 +4,11 @@ import ( "net" "testing" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/internal/testutils/netnsutils" "github.com/docker/docker/libnetwork/driverapi" "github.com/docker/docker/libnetwork/iptables" "github.com/docker/docker/libnetwork/netlabel" - "github.com/vishvananda/netlink" "gotest.tools/v3/assert" ) @@ -38,7 +38,7 @@ func TestProgramIPTable(t *testing.T) { // Create a test bridge with a basic bridge configuration (name + IPv4). defer netnsutils.SetupTestOSContext(t)() - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() if err != nil { t.Fatal(err) } @@ -68,7 +68,7 @@ func TestSetupIPChains(t *testing.T) { // Create a test bridge with a basic bridge configuration (name + IPv4). defer netnsutils.SetupTestOSContext(t)() - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() if err != nil { t.Fatal(err) } @@ -193,7 +193,7 @@ func TestSetupIP6TablesWithHostIPv4(t *testing.T) { AddressIPv6: &net.IPNet{IP: net.ParseIP("2001:db8::1"), Mask: net.CIDRMask(64, 128)}, HostIPv4: net.ParseIP("192.0.2.2"), } - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() if err != nil { t.Fatal(err) } diff --git a/libnetwork/drivers/bridge/setup_ipv4_linux_test.go b/libnetwork/drivers/bridge/setup_ipv4_linux_test.go index 6b191b3745b9a..024306687027a 100644 --- a/libnetwork/drivers/bridge/setup_ipv4_linux_test.go +++ b/libnetwork/drivers/bridge/setup_ipv4_linux_test.go @@ -4,11 +4,12 @@ import ( "net" "testing" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/internal/testutils/netnsutils" "github.com/vishvananda/netlink" ) -func setupTestInterface(t *testing.T, nh *netlink.Handle) (*networkConfiguration, *bridgeInterface) { +func setupTestInterface(t *testing.T, nh nlwrap.Handle) (*networkConfiguration, *bridgeInterface) { config := &networkConfiguration{ BridgeName: DefaultBridgeName, } @@ -28,7 +29,7 @@ func TestSetupBridgeIPv4Fixed(t *testing.T) { t.Fatalf("Failed to parse bridge IPv4: %v", err) } - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() if err != nil { t.Fatal(err) } @@ -61,7 +62,7 @@ func TestSetupBridgeIPv4Fixed(t *testing.T) { func TestSetupGatewayIPv4(t *testing.T) { defer netnsutils.SetupTestOSContext(t)() - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() if err != nil { t.Fatal(err) } diff --git a/libnetwork/drivers/bridge/setup_ipv6_linux_test.go b/libnetwork/drivers/bridge/setup_ipv6_linux_test.go index 8c9f8c7645afc..cfd97f50d3479 100644 --- a/libnetwork/drivers/bridge/setup_ipv6_linux_test.go +++ b/libnetwork/drivers/bridge/setup_ipv6_linux_test.go @@ -7,6 +7,7 @@ import ( "os" "testing" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/internal/testutils/netnsutils" "github.com/vishvananda/netlink" ) @@ -14,7 +15,7 @@ import ( func TestSetupIPv6(t *testing.T) { defer netnsutils.SetupTestOSContext(t)() - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() if err != nil { t.Fatal(err) } @@ -66,7 +67,7 @@ func TestSetupGatewayIPv6(t *testing.T) { DefaultGatewayIPv6: gw, } - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() if err != nil { t.Fatal(err) } diff --git a/libnetwork/drivers/bridge/setup_verify_linux_test.go b/libnetwork/drivers/bridge/setup_verify_linux_test.go index 045f3a16812e4..08013d8ba6749 100644 --- a/libnetwork/drivers/bridge/setup_verify_linux_test.go +++ b/libnetwork/drivers/bridge/setup_verify_linux_test.go @@ -4,12 +4,13 @@ import ( "net" "testing" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/internal/testutils/netnsutils" "github.com/vishvananda/netlink" ) func setupVerifyTest(t *testing.T) *bridgeInterface { - nh, err := netlink.NewHandle() + nh, err := nlwrap.NewHandle() if err != nil { t.Fatal(err) } diff --git a/libnetwork/drivers/overlay/ov_network.go b/libnetwork/drivers/overlay/ov_network.go index 3d170d74899d3..8a50b8e5ad508 100644 --- a/libnetwork/drivers/overlay/ov_network.go +++ b/libnetwork/drivers/overlay/ov_network.go @@ -15,6 +15,7 @@ import ( "sync" "github.com/containerd/log" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/libnetwork/driverapi" "github.com/docker/docker/libnetwork/drivers/overlay/overlayutils" "github.com/docker/docker/libnetwork/netlabel" @@ -351,7 +352,7 @@ func populateVNITbl() { } defer n.Close() - nlh, err := netlink.NewHandleAt(n, unix.NETLINK_ROUTE) + nlh, err := nlwrap.NewHandleAt(n, unix.NETLINK_ROUTE) if err != nil { log.G(context.TODO()).Errorf("Could not open netlink handle during vni population for ns %s: %v", path, err) return nil diff --git a/libnetwork/drivers/overlay/ov_utils.go b/libnetwork/drivers/overlay/ov_utils.go index 1ef14ecfc2222..76a5bcef01731 100644 --- a/libnetwork/drivers/overlay/ov_utils.go +++ b/libnetwork/drivers/overlay/ov_utils.go @@ -9,6 +9,7 @@ import ( "syscall" "github.com/containerd/log" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/libnetwork/drivers/overlay/overlayutils" "github.com/docker/docker/libnetwork/netutils" "github.com/docker/docker/libnetwork/ns" @@ -110,7 +111,7 @@ func deleteVxlanByVNI(path string, vni uint32) error { } defer ns.Close() - nlh, err = netlink.NewHandleAt(ns, syscall.NETLINK_ROUTE) + nlh, err = nlwrap.NewHandleAt(ns, syscall.NETLINK_ROUTE) if err != nil { return fmt.Errorf("failed to get netlink handle for ns %s: %v", path, err) } diff --git a/libnetwork/iptables/conntrack.go b/libnetwork/iptables/conntrack.go index 37fd91528a4e0..b3e91a3065844 100644 --- a/libnetwork/iptables/conntrack.go +++ b/libnetwork/iptables/conntrack.go @@ -9,13 +9,14 @@ import ( "syscall" "github.com/containerd/log" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/libnetwork/types" "github.com/vishvananda/netlink" ) // checkConntrackProgrammable checks if the handle supports the // NETLINK_NETFILTER and the base modules are loaded. -func checkConntrackProgrammable(nlh *netlink.Handle) error { +func checkConntrackProgrammable(nlh nlwrap.Handle) error { if !nlh.SupportsNetlinkFamily(syscall.NETLINK_NETFILTER) { return errors.New("conntrack is not available") } @@ -24,7 +25,7 @@ func checkConntrackProgrammable(nlh *netlink.Handle) error { // DeleteConntrackEntries deletes all the conntrack connections on the host for the specified IP // Returns the number of flows deleted for IPv4, IPv6 else error -func DeleteConntrackEntries(nlh *netlink.Handle, ipv4List []net.IP, ipv6List []net.IP) error { +func DeleteConntrackEntries(nlh nlwrap.Handle, ipv4List []net.IP, ipv6List []net.IP) error { if err := checkConntrackProgrammable(nlh); err != nil { return err } @@ -56,7 +57,7 @@ func DeleteConntrackEntries(nlh *netlink.Handle, ipv4List []net.IP, ipv6List []n return nil } -func DeleteConntrackEntriesByPort(nlh *netlink.Handle, proto types.Protocol, ports []uint16) error { +func DeleteConntrackEntriesByPort(nlh nlwrap.Handle, proto types.Protocol, ports []uint16) error { if err := checkConntrackProgrammable(nlh); err != nil { return err } @@ -95,7 +96,7 @@ func DeleteConntrackEntriesByPort(nlh *netlink.Handle, proto types.Protocol, por return nil } -func purgeConntrackState(nlh *netlink.Handle, family netlink.InetFamily, ipAddress net.IP) (uint, error) { +func purgeConntrackState(nlh nlwrap.Handle, family netlink.InetFamily, ipAddress net.IP) (uint, error) { filter := &netlink.ConntrackFilter{} // NOTE: doing the flush using the ipAddress is safe because today there cannot be multiple networks with the same subnet // so it will not be possible to flush flows that are of other containers diff --git a/libnetwork/libnetwork_linux_test.go b/libnetwork/libnetwork_linux_test.go index d6401bef16302..ed1cdee08dfc1 100644 --- a/libnetwork/libnetwork_linux_test.go +++ b/libnetwork/libnetwork_linux_test.go @@ -17,6 +17,7 @@ import ( "testing" "github.com/containerd/log" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/internal/testutils/netnsutils" "github.com/docker/docker/libnetwork" "github.com/docker/docker/libnetwork/config" @@ -32,7 +33,6 @@ import ( "github.com/docker/docker/pkg/plugins" "github.com/docker/docker/pkg/reexec" "github.com/pkg/errors" - "github.com/vishvananda/netlink" "github.com/vishvananda/netns" "golang.org/x/sync/errgroup" "gotest.tools/v3/assert" @@ -1490,7 +1490,7 @@ func checkSandbox(t *testing.T, info libnetwork.EndpointInfo) { } defer sbNs.Close() - nh, err := netlink.NewHandleAt(sbNs) + nh, err := nlwrap.NewHandleAt(sbNs) if err != nil { t.Fatal(err) } diff --git a/libnetwork/netutils/utils_linux.go b/libnetwork/netutils/utils_linux.go index 51630e1e95286..50fdec43e2de5 100644 --- a/libnetwork/netutils/utils_linux.go +++ b/libnetwork/netutils/utils_linux.go @@ -8,6 +8,7 @@ import ( "os" "slices" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/libnetwork/internal/netiputil" "github.com/docker/docker/libnetwork/ns" "github.com/docker/docker/libnetwork/resolvconf" @@ -99,17 +100,17 @@ func queryOnLinkRoutes() []netip.Prefix { // GenerateIfaceName returns an interface name using the passed in // prefix and the length of random bytes. The api ensures that the // there are is no interface which exists with that name. -func GenerateIfaceName(nlh *netlink.Handle, prefix string, len int) (string, error) { - linkByName := netlink.LinkByName - if nlh != nil { - linkByName = nlh.LinkByName - } +func GenerateIfaceName(nlh nlwrap.Handle, prefix string, len int) (string, error) { for i := 0; i < 3; i++ { name, err := GenerateRandomName(prefix, len) if err != nil { return "", err } - _, err = linkByName(name) + if nlh.Handle == nil { + _, err = nlwrap.LinkByName(name) + } else { + _, err = nlh.LinkByName(name) + } if err != nil { if errors.As(err, &netlink.LinkNotFoundError{}) { return name, nil diff --git a/libnetwork/ns/init_linux.go b/libnetwork/ns/init_linux.go index 66bc67c603beb..b2d945e885448 100644 --- a/libnetwork/ns/init_linux.go +++ b/libnetwork/ns/init_linux.go @@ -10,13 +10,13 @@ import ( "time" "github.com/containerd/log" - "github.com/vishvananda/netlink" + "github.com/docker/docker/internal/nlwrap" "github.com/vishvananda/netns" ) var ( initNs netns.NsHandle - initNl *netlink.Handle + initNl nlwrap.Handle initOnce sync.Once // NetlinkSocketsTimeout represents the default timeout duration for the sockets NetlinkSocketsTimeout = 3 * time.Second @@ -29,7 +29,7 @@ func Init() { if err != nil { log.G(context.TODO()).Errorf("could not get initial namespace: %v", err) } - initNl, err = netlink.NewHandle(getSupportedNlFamilies()...) + initNl, err = nlwrap.NewHandle(getSupportedNlFamilies()...) if err != nil { log.G(context.TODO()).Errorf("could not create netlink handle on initial namespace: %v", err) } @@ -51,7 +51,7 @@ func getHandler() netns.NsHandle { } // NlHandle returns the netlink handler -func NlHandle() *netlink.Handle { +func NlHandle() nlwrap.Handle { initOnce.Do(Init) return initNl } diff --git a/libnetwork/osl/interface_linux.go b/libnetwork/osl/interface_linux.go index 28e1ac32fa113..0370d4f0bd787 100644 --- a/libnetwork/osl/interface_linux.go +++ b/libnetwork/osl/interface_linux.go @@ -11,6 +11,7 @@ import ( "time" "github.com/containerd/log" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/libnetwork/ns" "github.com/docker/docker/libnetwork/types" "github.com/pkg/errors" @@ -162,7 +163,7 @@ func (n *Namespace) findDst(srcName string, isBridge bool) string { return "" } -func moveLink(ctx context.Context, nlhHost *netlink.Handle, iface netlink.Link, i *Interface, path string) error { +func moveLink(ctx context.Context, nlhHost nlwrap.Handle, iface netlink.Link, i *Interface, path string) error { ctx, span := otel.Tracer("").Start(ctx, "libnetwork.osl.moveLink", trace.WithAttributes( attribute.String("ifaceName", i.DstName()))) defer span.End() @@ -340,14 +341,14 @@ func (n *Namespace) RemoveInterface(i *Interface) error { return nil } -func (n *Namespace) configureInterface(ctx context.Context, nlh *netlink.Handle, iface netlink.Link, i *Interface) error { +func (n *Namespace) configureInterface(ctx context.Context, nlh nlwrap.Handle, iface netlink.Link, i *Interface) error { ctx, span := otel.Tracer("").Start(ctx, "libnetwork.osl.configureInterface", trace.WithAttributes( attribute.String("ifaceName", iface.Attrs().Name))) defer span.End() ifaceName := iface.Attrs().Name ifaceConfigurators := []struct { - Fn func(context.Context, *netlink.Handle, netlink.Link, *Interface) error + Fn func(context.Context, nlwrap.Handle, netlink.Link, *Interface) error ErrMessage string }{ {setInterfaceName, fmt.Sprintf("error renaming interface %q to %q", ifaceName, i.DstName())}, @@ -371,7 +372,7 @@ func (n *Namespace) configureInterface(ctx context.Context, nlh *netlink.Handle, return nil } -func setInterfaceMaster(ctx context.Context, nlh *netlink.Handle, iface netlink.Link, i *Interface) error { +func setInterfaceMaster(ctx context.Context, nlh nlwrap.Handle, iface netlink.Link, i *Interface) error { if i.DstMaster() == "" { return nil } @@ -386,7 +387,7 @@ func setInterfaceMaster(ctx context.Context, nlh *netlink.Handle, iface netlink. }) } -func setInterfaceMAC(ctx context.Context, nlh *netlink.Handle, iface netlink.Link, i *Interface) error { +func setInterfaceMAC(ctx context.Context, nlh nlwrap.Handle, iface netlink.Link, i *Interface) error { if i.MacAddress() == nil { return nil } @@ -399,7 +400,7 @@ func setInterfaceMAC(ctx context.Context, nlh *netlink.Handle, iface netlink.Lin return nlh.LinkSetHardwareAddr(iface, i.MacAddress()) } -func setInterfaceIP(ctx context.Context, nlh *netlink.Handle, iface netlink.Link, i *Interface) error { +func setInterfaceIP(ctx context.Context, nlh nlwrap.Handle, iface netlink.Link, i *Interface) error { if i.Address() == nil { return nil } @@ -416,7 +417,7 @@ func setInterfaceIP(ctx context.Context, nlh *netlink.Handle, iface netlink.Link return nlh.AddrAdd(iface, ipAddr) } -func setInterfaceIPv6(ctx context.Context, nlh *netlink.Handle, iface netlink.Link, i *Interface) error { +func setInterfaceIPv6(ctx context.Context, nlh nlwrap.Handle, iface netlink.Link, i *Interface) error { addr := i.AddressIPv6() ctx, span := otel.Tracer("").Start(ctx, "libnetwork.osl.setInterfaceIPv6", trace.WithAttributes( attribute.String("i.SrcName", i.SrcName()), @@ -443,7 +444,7 @@ func setInterfaceIPv6(ctx context.Context, nlh *netlink.Handle, iface netlink.Li return nlh.AddrAdd(iface, nlAddr) } -func setInterfaceLinkLocalIPs(ctx context.Context, nlh *netlink.Handle, iface netlink.Link, i *Interface) error { +func setInterfaceLinkLocalIPs(ctx context.Context, nlh nlwrap.Handle, iface netlink.Link, i *Interface) error { ctx, span := otel.Tracer("").Start(ctx, "libnetwork.osl.setInterfaceLinkLocalIPs", trace.WithAttributes( attribute.String("i.SrcName", i.SrcName()), attribute.String("i.DstName", i.DstName()))) @@ -498,7 +499,7 @@ func (n *Namespace) setSysctls(ctx context.Context, ifName string, sysctls []str return nil } -func setInterfaceName(ctx context.Context, nlh *netlink.Handle, iface netlink.Link, i *Interface) error { +func setInterfaceName(ctx context.Context, nlh nlwrap.Handle, iface netlink.Link, i *Interface) error { ctx, span := otel.Tracer("").Start(ctx, "libnetwork.osl.setInterfaceName", trace.WithAttributes( attribute.String("ifaceName", iface.Attrs().Name))) defer span.End() @@ -506,7 +507,7 @@ func setInterfaceName(ctx context.Context, nlh *netlink.Handle, iface netlink.Li return nlh.LinkSetName(iface, i.DstName()) } -func setInterfaceRoutes(ctx context.Context, nlh *netlink.Handle, iface netlink.Link, i *Interface) error { +func setInterfaceRoutes(ctx context.Context, nlh nlwrap.Handle, iface netlink.Link, i *Interface) error { ctx, span := otel.Tracer("").Start(ctx, "libnetwork.osl.setInterfaceRoutes", trace.WithAttributes( attribute.String("i.SrcName", i.SrcName()), attribute.String("i.DstName", i.DstName()))) @@ -525,7 +526,7 @@ func setInterfaceRoutes(ctx context.Context, nlh *netlink.Handle, iface netlink. return nil } -func checkRouteConflict(nlh *netlink.Handle, address *net.IPNet, family int) error { +func checkRouteConflict(nlh nlwrap.Handle, address *net.IPNet, family int) error { routes, err := nlh.RouteList(nil, family) if err != nil { return err diff --git a/libnetwork/osl/namespace_linux.go b/libnetwork/osl/namespace_linux.go index 2f3995ff0cc7a..efbed92af1f87 100644 --- a/libnetwork/osl/namespace_linux.go +++ b/libnetwork/osl/namespace_linux.go @@ -15,6 +15,7 @@ import ( "time" "github.com/containerd/log" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/internal/unshare" "github.com/docker/docker/libnetwork/ns" "github.com/docker/docker/libnetwork/osl/kernel" @@ -198,7 +199,7 @@ func NewSandbox(key string, osCreate, isRestore bool) (*Namespace, error) { } defer sboxNs.Close() - n.nlHandle, err = netlink.NewHandleAt(sboxNs, syscall.NETLINK_ROUTE) + n.nlHandle, err = nlwrap.NewHandleAt(sboxNs, syscall.NETLINK_ROUTE) if err != nil { return nil, fmt.Errorf("failed to create a netlink handle: %v", err) } @@ -241,7 +242,7 @@ func GetSandboxForExternalKey(basePath string, key string) (*Namespace, error) { } defer sboxNs.Close() - n.nlHandle, err = netlink.NewHandleAt(sboxNs, syscall.NETLINK_ROUTE) + n.nlHandle, err = nlwrap.NewHandleAt(sboxNs, syscall.NETLINK_ROUTE) if err != nil { return nil, fmt.Errorf("failed to create a netlink handle: %v", err) } @@ -320,7 +321,7 @@ type Namespace struct { isDefault bool ipv6LoEnabledOnce sync.Once ipv6LoEnabledCached bool - nlHandle *netlink.Handle + nlHandle nlwrap.Handle mu sync.Mutex } @@ -456,9 +457,7 @@ func (n *Namespace) Key() string { // Destroy destroys the sandbox. func (n *Namespace) Destroy() error { - if n.nlHandle != nil { - n.nlHandle.Close() - } + n.nlHandle.Handle.Close() // Assuming no running process is executing in this network namespace, // unmounting is sufficient to destroy it. if err := syscall.Unmount(n.path, syscall.MNT_DETACH); err != nil { diff --git a/libnetwork/osl/sandbox_linux_test.go b/libnetwork/osl/sandbox_linux_test.go index 6b599736d2ac6..b998512a41198 100644 --- a/libnetwork/osl/sandbox_linux_test.go +++ b/libnetwork/osl/sandbox_linux_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "github.com/docker/docker/internal/nlwrap" "github.com/docker/docker/internal/testutils/netnsutils" "github.com/docker/docker/libnetwork/ns" "github.com/docker/docker/libnetwork/types" @@ -59,7 +60,7 @@ func newKey(t *testing.T) (string, error) { return name, nil } -func newInfo(t *testing.T, hnd *netlink.Handle) (*Namespace, error) { +func newInfo(t *testing.T, hnd nlwrap.Handle) (*Namespace, error) { t.Helper() err := hnd.LinkAdd(&netlink.Veth{ LinkAttrs: netlink.LinkAttrs{Name: vethName1, TxQLen: 0}, @@ -130,7 +131,7 @@ func verifySandbox(t *testing.T, ns *Namespace, ifaceSuffixes []string) { } defer sbNs.Close() - nh, err := netlink.NewHandleAt(sbNs) + nh, err := nlwrap.NewHandleAt(sbNs) if err != nil { t.Fatal(err) }