Description: changes needed for github.com/vishvananda/netlink v1.2.1+ (retry on EINTR)
Applied-Upstream: https://github.com/moby/moby/pull/48407, v28+, v27.5+
Origin: https://github.com/moby/moby/pull/48407

diff --git a/engine/daemon/cluster/listen_addr_linux.go b/engine/daemon/cluster/listen_addr_linux.go
index 62e4f61a65..e8e2bddadc 100644
--- a/engine/daemon/cluster/listen_addr_linux.go
+++ b/engine/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/engine/daemon/daemon_unix.go b/engine/daemon/daemon_unix.go
index cda787b570..27366ee68a 100644
--- a/engine/daemon/daemon_unix.go
+++ b/engine/daemon/daemon_unix.go
@@ -28,6 +28,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"
@@ -1062,7 +1063,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/engine/integration-cli/docker_cli_network_unix_test.go b/engine/integration-cli/docker_cli_network_unix_test.go
index 24a241ec3a..84e77f263b 100644
--- a/engine/integration-cli/docker_cli_network_unix_test.go
+++ b/engine/integration-cli/docker_cli_network_unix_test.go
@@ -17,6 +17,7 @@ import (
 	"github.com/docker/docker/api/types"
 	"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"
@@ -111,7 +112,7 @@ func setupRemoteNetworkDrivers(c *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 {
 			netlink.LinkDel(link)
 		}
 		fmt.Fprintf(w, "null")
@@ -1753,7 +1754,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 {
@@ -1771,7 +1772,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/engine/integration-cli/docker_cli_swarm_test.go b/engine/integration-cli/docker_cli_swarm_test.go
index a5d0c3b0d2..65b26e6c36 100644
--- a/engine/integration-cli/docker_cli_swarm_test.go
+++ b/engine/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"
@@ -705,7 +706,7 @@ func setupRemoteGlobalNetworkPlugin(c *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 {
 			netlink.LinkDel(link)
 		}
 		fmt.Fprintf(w, "null")
diff --git a/engine/internal/nlwrap/nlwrap_linux.go b/engine/internal/nlwrap/nlwrap_linux.go
new file mode 100644
index 0000000000..8f874de7b0
--- /dev/null
+++ b/engine/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) //nolint:forbidigo
+		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) //nolint:forbidigo
+		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...) //nolint:forbidigo
+		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) //nolint:forbidigo
+		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) //nolint:forbidigo
+		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) //nolint:forbidigo
+		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() //nolint:forbidigo
+		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() //nolint:forbidigo
+		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) //nolint:forbidigo
+		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) //nolint:forbidigo
+		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) //nolint:forbidigo
+		return err
+	})
+	return states, err
+}
diff --git a/engine/libnetwork/drivers/bridge/bridge_linux.go b/engine/libnetwork/drivers/bridge/bridge_linux.go
index 005c726f2d..623565eebd 100644
--- a/engine/libnetwork/drivers/bridge/bridge_linux.go
+++ b/engine/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/iptables"
@@ -147,7 +148,7 @@ type driver struct {
 	isolationChain2V6 *iptables.ChainInfo
 	networks          map[string]*bridgeNetwork
 	store             *datastore.Store
-	nlh               *netlink.Handle
+	nlh               nlwrap.Handle
 	configNetwork     sync.Mutex
 	portAllocator     *portallocator.PortAllocator // Overridable for tests.
 	sync.Mutex
@@ -694,7 +695,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()
@@ -905,7 +906,7 @@ func (d *driver) deleteNetwork(nid string) error {
 	return d.storeDelete(config)
 }
 
-func addToBridge(nlh *netlink.Handle, ifaceName, bridgeName string) error {
+func addToBridge(nlh nlwrap.Handle, ifaceName, bridgeName string) error {
 	lnk, err := nlh.LinkByName(ifaceName)
 	if err != nil {
 		return fmt.Errorf("could not find interface %s: %v", ifaceName, err)
@@ -917,7 +918,7 @@ func addToBridge(nlh *netlink.Handle, ifaceName, bridgeName string) error {
 	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/engine/libnetwork/drivers/bridge/bridge_linux_test.go b/engine/libnetwork/drivers/bridge/bridge_linux_test.go
index 5b3c111868..e7b0c8d93d 100644
--- a/engine/libnetwork/drivers/bridge/bridge_linux_test.go
+++ b/engine/libnetwork/drivers/bridge/bridge_linux_test.go
@@ -10,6 +10,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/ipamutils"
@@ -1207,7 +1208,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/engine/libnetwork/drivers/bridge/interface_linux.go b/engine/libnetwork/drivers/bridge/interface_linux.go
index a78c02cadd..23310d42f8 100644
--- a/engine/libnetwork/drivers/bridge/interface_linux.go
+++ b/engine/libnetwork/drivers/bridge/interface_linux.go
@@ -9,6 +9,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"
 )
@@ -26,14 +27,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/engine/libnetwork/drivers/bridge/interface_linux_test.go b/engine/libnetwork/drivers/bridge/interface_linux_test.go
index 557185eca8..24970b9c9c 100644
--- a/engine/libnetwork/drivers/bridge/interface_linux_test.go
+++ b/engine/libnetwork/drivers/bridge/interface_linux_test.go
@@ -6,6 +6,7 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/docker/docker/internal/nlwrap"
 	"github.com/docker/docker/internal/testutils/netnsutils"
 	"github.com/google/go-cmp/cmp"
 	"github.com/vishvananda/netlink"
@@ -29,7 +30,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)
@@ -41,7 +42,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)
 	}
@@ -61,7 +62,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/engine/libnetwork/drivers/bridge/network_linux_test.go b/engine/libnetwork/drivers/bridge/network_linux_test.go
index fc7bd21b32..5d6121edb4 100644
--- a/engine/libnetwork/drivers/bridge/network_linux_test.go
+++ b/engine/libnetwork/drivers/bridge/network_linux_test.go
@@ -3,10 +3,10 @@ package bridge
 import (
 	"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) {
@@ -54,7 +54,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)
 	}
@@ -74,7 +74,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/engine/libnetwork/drivers/bridge/setup_device_linux_test.go b/engine/libnetwork/drivers/bridge/setup_device_linux_test.go
index 05f3513314..41edf97c5d 100644
--- a/engine/libnetwork/drivers/bridge/setup_device_linux_test.go
+++ b/engine/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/engine/libnetwork/drivers/bridge/setup_ip_tables_linux.go b/engine/libnetwork/drivers/bridge/setup_ip_tables_linux.go
index 328c58bced..7b98258fdb 100644
--- a/engine/libnetwork/drivers/bridge/setup_ip_tables_linux.go
+++ b/engine/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
@@ -473,7 +473,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/engine/libnetwork/drivers/bridge/setup_ip_tables_linux_test.go b/engine/libnetwork/drivers/bridge/setup_ip_tables_linux_test.go
index dd5fd678b6..1b6ce4b661 100644
--- a/engine/libnetwork/drivers/bridge/setup_ip_tables_linux_test.go
+++ b/engine/libnetwork/drivers/bridge/setup_ip_tables_linux_test.go
@@ -4,12 +4,12 @@ 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/docker/docker/libnetwork/portmapper"
-	"github.com/vishvananda/netlink"
 	"gotest.tools/v3/assert"
 )
 
@@ -39,7 +39,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)
 	}
@@ -69,7 +69,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)
 	}
@@ -196,7 +196,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/engine/libnetwork/drivers/bridge/setup_ipv4_linux_test.go b/engine/libnetwork/drivers/bridge/setup_ipv4_linux_test.go
index 6b191b3745..0243066870 100644
--- a/engine/libnetwork/drivers/bridge/setup_ipv4_linux_test.go
+++ b/engine/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/engine/libnetwork/drivers/bridge/setup_ipv6_linux_test.go b/engine/libnetwork/drivers/bridge/setup_ipv6_linux_test.go
index 278da7745c..19423be521 100644
--- a/engine/libnetwork/drivers/bridge/setup_ipv6_linux_test.go
+++ b/engine/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/engine/libnetwork/drivers/bridge/setup_verify_linux_test.go b/engine/libnetwork/drivers/bridge/setup_verify_linux_test.go
index 045f3a1681..08013d8ba6 100644
--- a/engine/libnetwork/drivers/bridge/setup_verify_linux_test.go
+++ b/engine/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/engine/libnetwork/drivers/overlay/ov_network.go b/engine/libnetwork/drivers/overlay/ov_network.go
index 6dcf3ec37c..1f4d4b04fb 100644
--- a/engine/libnetwork/drivers/overlay/ov_network.go
+++ b/engine/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/engine/libnetwork/drivers/overlay/ov_utils.go b/engine/libnetwork/drivers/overlay/ov_utils.go
index 1ef14ecfc2..76a5bcef01 100644
--- a/engine/libnetwork/drivers/overlay/ov_utils.go
+++ b/engine/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/engine/libnetwork/iptables/conntrack.go b/engine/libnetwork/iptables/conntrack.go
index c77993e105..c311cf064a 100644
--- a/engine/libnetwork/iptables/conntrack.go
+++ b/engine/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/engine/libnetwork/libnetwork_linux_test.go b/engine/libnetwork/libnetwork_linux_test.go
index 7128e0fccc..a2a4634a6d 100644
--- a/engine/libnetwork/libnetwork_linux_test.go
+++ b/engine/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"
@@ -30,7 +31,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"
@@ -1463,7 +1463,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/engine/libnetwork/netutils/utils_linux.go b/engine/libnetwork/netutils/utils_linux.go
index b5488aa6bb..2b323beece 100644
--- a/engine/libnetwork/netutils/utils_linux.go
+++ b/engine/libnetwork/netutils/utils_linux.go
@@ -8,6 +8,7 @@ import (
 	"net"
 	"os"
 
+	"github.com/docker/docker/internal/nlwrap"
 	"github.com/docker/docker/libnetwork/ns"
 	"github.com/docker/docker/libnetwork/resolvconf"
 	"github.com/docker/docker/libnetwork/types"
@@ -38,17 +39,17 @@ func CheckRouteOverlaps(toCheck *net.IPNet) error {
 // 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/engine/libnetwork/ns/init_linux.go b/engine/libnetwork/ns/init_linux.go
index 66bc67c603..b2d945e885 100644
--- a/engine/libnetwork/ns/init_linux.go
+++ b/engine/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/engine/libnetwork/osl/interface_linux.go b/engine/libnetwork/osl/interface_linux.go
index 3491aac70c..b07fd1eddc 100644
--- a/engine/libnetwork/osl/interface_linux.go
+++ b/engine/libnetwork/osl/interface_linux.go
@@ -8,6 +8,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/vishvananda/netlink"
@@ -312,10 +313,10 @@ func (n *Namespace) RemoveInterface(i *Interface) error {
 	return nil
 }
 
-func configureInterface(nlh *netlink.Handle, iface netlink.Link, i *Interface) error {
+func configureInterface(nlh nlwrap.Handle, iface netlink.Link, i *Interface) error {
 	ifaceName := iface.Attrs().Name
 	ifaceConfigurators := []struct {
-		Fn         func(*netlink.Handle, netlink.Link, *Interface) error
+		Fn         func(nlwrap.Handle, netlink.Link, *Interface) error
 		ErrMessage string
 	}{
 		{setInterfaceName, fmt.Sprintf("error renaming interface %q to %q", ifaceName, i.DstName())},
@@ -334,7 +335,7 @@ func configureInterface(nlh *netlink.Handle, iface netlink.Link, i *Interface) e
 	return nil
 }
 
-func setInterfaceMaster(nlh *netlink.Handle, iface netlink.Link, i *Interface) error {
+func setInterfaceMaster(nlh nlwrap.Handle, iface netlink.Link, i *Interface) error {
 	if i.DstMaster() == "" {
 		return nil
 	}
@@ -344,14 +345,14 @@ func setInterfaceMaster(nlh *netlink.Handle, iface netlink.Link, i *Interface) e
 	})
 }
 
-func setInterfaceMAC(nlh *netlink.Handle, iface netlink.Link, i *Interface) error {
+func setInterfaceMAC(nlh nlwrap.Handle, iface netlink.Link, i *Interface) error {
 	if i.MacAddress() == nil {
 		return nil
 	}
 	return nlh.LinkSetHardwareAddr(iface, i.MacAddress())
 }
 
-func setInterfaceIP(nlh *netlink.Handle, iface netlink.Link, i *Interface) error {
+func setInterfaceIP(nlh nlwrap.Handle, iface netlink.Link, i *Interface) error {
 	if i.Address() == nil {
 		return nil
 	}
@@ -362,7 +363,7 @@ func setInterfaceIP(nlh *netlink.Handle, iface netlink.Link, i *Interface) error
 	return nlh.AddrAdd(iface, ipAddr)
 }
 
-func setInterfaceIPv6(nlh *netlink.Handle, iface netlink.Link, i *Interface) error {
+func setInterfaceIPv6(nlh nlwrap.Handle, iface netlink.Link, i *Interface) error {
 	addr := i.AddressIPv6()
 	// IPv6 must be enabled on the interface if and only if the network is
 	// IPv6-enabled. For an interface on an IPv4-only network, if IPv6 isn't
@@ -383,7 +384,7 @@ func setInterfaceIPv6(nlh *netlink.Handle, iface netlink.Link, i *Interface) err
 	return nlh.AddrAdd(iface, nlAddr)
 }
 
-func setInterfaceLinkLocalIPs(nlh *netlink.Handle, iface netlink.Link, i *Interface) error {
+func setInterfaceLinkLocalIPs(nlh nlwrap.Handle, iface netlink.Link, i *Interface) error {
 	for _, llIP := range i.LinkLocalAddresses() {
 		ipAddr := &netlink.Addr{IPNet: llIP}
 		if err := nlh.AddrAdd(iface, ipAddr); err != nil {
@@ -393,11 +394,11 @@ func setInterfaceLinkLocalIPs(nlh *netlink.Handle, iface netlink.Link, i *Interf
 	return nil
 }
 
-func setInterfaceName(nlh *netlink.Handle, iface netlink.Link, i *Interface) error {
+func setInterfaceName(nlh nlwrap.Handle, iface netlink.Link, i *Interface) error {
 	return nlh.LinkSetName(iface, i.DstName())
 }
 
-func setInterfaceRoutes(nlh *netlink.Handle, iface netlink.Link, i *Interface) error {
+func setInterfaceRoutes(nlh nlwrap.Handle, iface netlink.Link, i *Interface) error {
 	for _, route := range i.Routes() {
 		err := nlh.RouteAdd(&netlink.Route{
 			Scope:     netlink.SCOPE_LINK,
@@ -411,7 +412,7 @@ func setInterfaceRoutes(nlh *netlink.Handle, iface netlink.Link, i *Interface) e
 	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/engine/libnetwork/osl/namespace_linux.go b/engine/libnetwork/osl/namespace_linux.go
index 70dbc9d3f8..dc982245e1 100644
--- a/engine/libnetwork/osl/namespace_linux.go
+++ b/engine/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/engine/libnetwork/osl/sandbox_linux_test.go b/engine/libnetwork/osl/sandbox_linux_test.go
index 171f6b5295..005597fd90 100644
--- a/engine/libnetwork/osl/sandbox_linux_test.go
+++ b/engine/libnetwork/osl/sandbox_linux_test.go
@@ -12,6 +12,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"
@@ -58,7 +59,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},
@@ -129,7 +130,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)
 	}
