From af73684bc3055b7eccbd5c13abb6d5d1e670fd46 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 29 Mar 2026 16:14:38 +0200 Subject: [PATCH] daemon/libnetwork/ns: use sync.OnceValues This avoids depending on global state in the package, and "forces" consumers to go through the initHandles() func to get the handles. A slight change in behavior is that `ResetHandles()` may now initialize a new namespace only to reset it, but this is likely an "OK" trade-off. Signed-off-by: Sebastiaan van Stijn --- daemon/libnetwork/ns/init_linux.go | 47 +++++++++++++++++------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/daemon/libnetwork/ns/init_linux.go b/daemon/libnetwork/ns/init_linux.go index 1c3d83d0d9..e714a55dde 100644 --- a/daemon/libnetwork/ns/init_linux.go +++ b/daemon/libnetwork/ns/init_linux.go @@ -14,48 +14,55 @@ import ( "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 -) +// NetlinkSocketsTimeout represents the default timeout duration for the sockets. +var NetlinkSocketsTimeout = 3 * time.Second + +// initNamespace initializes a new network namespace. +var initNamespace = sync.OnceValues(initHandles) // initHandles initializes a new network namespace -func initHandles() { - var err error - initNs, err = netns.Get() +func initHandles() (netns.NsHandle, nlwrap.Handle) { + initNs, err := netns.Get() if err != nil { - log.G(context.TODO()).Errorf("could not get initial namespace: %v", err) + log.G(context.Background()).WithError(err).Error("could not get initial namespace: falling back to using netns.None") + initNs = netns.None() } - initNl, err = nlwrap.NewHandle(getSupportedNlFamilies()...) + initNl, err := nlwrap.NewHandle(getSupportedNlFamilies()...) if err != nil { // Fail fast to keep the invariant: NlHandle must be a valid handle panic(fmt.Errorf("could not create netlink handle on initial (host) namespace: %w", 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) + log.G(context.Background()).WithError(err).Warn("failed to set the timeout on the default netlink handle sockets") } + + return initNs, initNl } // 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. +// +// Note: This function is not safe for concurrent use with callers +// that are using handles obtained from this package. It may close +// handles while they are still in use. func ResetHandles() { if !testing.Testing() { panic("ResetHandles should only be called from tests") } + initNs, initNl := initNamespace() + // Reset the initNamespace sync.OnceValues. This may race with + // concurrent callers still calling the old initNamespace (and + // values), but adding a [sync.RWMutex] only for the test-case + // is probably too much (unless things are racy). + initNamespace = sync.OnceValues(initHandles) 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 @@ -65,14 +72,14 @@ func ParseHandlerInt() int { // GetHandler returns the namespace handler func getHandler() netns.NsHandle { - initOnce.Do(initHandles) - return initNs + ns, _ := initNamespace() + return ns } // NlHandle returns the netlink handler func NlHandle() nlwrap.Handle { - initOnce.Do(initHandles) - return initNl + _, nl := initNamespace() + return nl } func getSupportedNlFamilies() []int {