Files
moby/daemon/libnetwork/ns/init_linux.go
Rob Murray 362d4d9538 testutil:SetupTestOSContext - don't leak namespaces, netlink handles
SetupTestOSContextEx calls 'ns.Init' (which, outside tests, is protected
by a sync.Once), and it's called again by the returned OSContext.Cleanup
method. That overwrites the ns package's namespace and netlink handles
(initNs and initNl) without closing them.

Because SetupTestOSContextEx changes that shared state, it should not
be used in parallel tests. So, rather than trying to close the handles
in ns.Init if already open - un-export Init so it's always called via
its sync.Once, and add a reset function for tests to use. Have
SetupTestOSContextEx claim a mutex to avoid crashy surprises or
hard to catch issues where the ns package isn't using the expected
namespace if it is used in parallel tests.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-09-23 16:16:00 +01:00

113 lines
2.9 KiB
Go

package ns
import (
"context"
"sync"
"syscall"
"testing"
"time"
"github.com/containerd/log"
"github.com/moby/moby/v2/daemon/libnetwork/internal/modprobe"
"github.com/moby/moby/v2/daemon/libnetwork/nlwrap"
"github.com/vishvananda/netns"
)
var (
initNs = netns.None()
initNl nlwrap.Handle
initOnce sync.Once
// NetlinkSocketsTimeout represents the default timeout duration for the sockets
NetlinkSocketsTimeout = 3 * time.Second
)
// initHandles initializes a new network namespace
func initHandles() {
var err error
initNs, err = netns.Get()
if err != nil {
log.G(context.TODO()).Errorf("could not get initial namespace: %v", err)
}
initNl, err = nlwrap.NewHandle(getSupportedNlFamilies()...)
if err != nil {
log.G(context.TODO()).Errorf("could not create netlink handle on initial namespace: %v", err)
}
err = initNl.SetSocketTimeout(NetlinkSocketsTimeout)
if err != nil {
log.G(context.TODO()).Warnf("Failed to set the timeout on the default netlink handle sockets: %v", err)
}
}
// ResetHandles resets the initial namespace and netlink handles.
// This is useful for testing to ensure a clean state. It will
// panic if called outside a test.
func ResetHandles() {
if !testing.Testing() {
panic("ResetHandles should only be called from tests")
}
if initNs.IsOpen() {
initNs.Close()
initNs = netns.None()
}
if initNl.Handle != nil {
initNl.Close()
initNl = nlwrap.Handle{}
}
initOnce = sync.Once{}
}
// ParseHandlerInt transforms the namespace handler into an integer
func ParseHandlerInt() int {
return int(getHandler())
}
// GetHandler returns the namespace handler
func getHandler() netns.NsHandle {
initOnce.Do(initHandles)
return initNs
}
// NlHandle returns the netlink handler
func NlHandle() nlwrap.Handle {
initOnce.Do(initHandles)
return initNl
}
func getSupportedNlFamilies() []int {
fams := []int{syscall.NETLINK_ROUTE}
// NETLINK_XFRM test
if err := checkXfrmSocket(); err != nil {
log.G(context.TODO()).Warnf("Could not load necessary modules for IPSEC rules: %v", err)
} else {
fams = append(fams, syscall.NETLINK_XFRM)
}
// NETLINK_NETFILTER test
if err := modprobe.LoadModules(context.TODO(), checkNfSocket, "nf_conntrack", "nf_conntrack_netlink"); err != nil {
log.G(context.TODO()).Warnf("Could not load necessary modules for Conntrack: %v", err)
} else {
fams = append(fams, syscall.NETLINK_NETFILTER)
}
return fams
}
// API check on required xfrm modules (xfrm_user, xfrm_algo)
func checkXfrmSocket() error {
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_XFRM)
if err != nil {
return err
}
syscall.Close(fd)
return nil
}
// API check on required nf_conntrack* modules (nf_conntrack, nf_conntrack_netlink)
func checkNfSocket() error {
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_NETFILTER)
if err != nil {
return err
}
syscall.Close(fd)
return nil
}