From 37dcc4e1037f3e7df08abba80397dd6db9eb04a2 Mon Sep 17 00:00:00 2001 From: Lei Huang Date: Thu, 6 Aug 2026 00:57:49 -0700 Subject: [PATCH] handle: use per-request sockets for VF-only configuration PR #1174 changed ConfigureHandle() to configure the package-wide pkgHandle through NewHandleWithOptions(), which creates persistent netlink sockets in the pkgHandle. Because pkgHandle.sockets remains bound to the network namespace where it was created, the following operations could target the wrong namespace after the calling OS thread changed namespaces. This PR returns a handle without nil sockets when the caller only disables VF information collection: NewHandleWithOptions(HandleOptions{ DisableVFInfoCollection: true, }) Signed-off-by: Lei Huang --- handle_linux.go | 17 ++++++++++-- handle_linux_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/handle_linux.go b/handle_linux.go index a6e8346d2..a55f71390 100644 --- a/handle_linux.go +++ b/handle_linux.go @@ -58,8 +58,15 @@ type HandleOptions struct { // [ErrDumpInterrupted]. RetryInterrupted bool - // NetNS specifies the network namespace to operate on. If not set, the - // current network namespace will be used. + // NetNS specifies the network namespace in which the Handle's persistent + // netlink sockets are created. A persistent socket remains bound to its + // creation namespace even if the calling OS thread later changes namespaces. + // + // When NetNS is non-nil, persistent sockets are created in that namespace. + // When NetNS is nil, persistent sockets are created in the calling OS thread's + // current namespace when the Handle is created. A socketless Handle instead + // creates a temporary socket for each operation in the calling OS thread's + // current namespace when the operation is executed. NetNS *netns.NsHandle } @@ -199,7 +206,13 @@ func NewHandleAtFrom(newNs, curNs netns.NsHandle) (*Handle, error) { } // NewHandleWithOptions returns a Handle created using the specified options. +// If DisableVFInfoCollection is the only option set and no netlink families are +// specified, the returned Handle creates a socket for each request in the +// caller's current network namespace instead of keeping persistent sockets. func NewHandleWithOptions(opts HandleOptions, nlFamilies ...int) (*Handle, error) { + if opts.DisableVFInfoCollection && !opts.RetryInterrupted && opts.NetNS == nil && len(nlFamilies) == 0 { + return &Handle{options: opts}, nil + } return newHandle(netns.None(), opts, nlFamilies...) } diff --git a/handle_linux_test.go b/handle_linux_test.go index 29e728d03..53ae0f0e9 100644 --- a/handle_linux_test.go +++ b/handle_linux_test.go @@ -5,6 +5,8 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/vishvananda/netns" + "golang.org/x/sys/unix" ) func TestSetGetSocketTimeout(t *testing.T) { @@ -34,3 +36,62 @@ func TestConfigureHandle(t *testing.T) { assert.NoError(t, pkgHandle.Close()) assert.Error(t, ConfigureHandle(HandleOptions{})) } + +func TestNewHandleWithOptions(t *testing.T) { + none := netns.None() + tests := []struct { + name string + opts HandleOptions + nlFamilies []int + wantSocketless bool + }{ + { + name: "disable VF only", + opts: HandleOptions{DisableVFInfoCollection: true}, + wantSocketless: true, + }, + { + name: "retry interrupted", + opts: HandleOptions{ + DisableVFInfoCollection: true, + RetryInterrupted: true, + }, + }, + { + name: "explicit namespace", + opts: HandleOptions{ + DisableVFInfoCollection: true, + NetNS: &none, + }, + }, + { + name: "explicit netlink family", + opts: HandleOptions{DisableVFInfoCollection: true}, + nlFamilies: []int{unix.NETLINK_ROUTE}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h, err := NewHandleWithOptions(tt.opts, tt.nlFamilies...) + if !assert.NoError(t, err) { + return + } + t.Cleanup(func() { assert.NoError(t, h.Close()) }) + + assert.True(t, h.options.DisableVFInfoCollection) + if tt.wantSocketless { + assert.Nil(t, h.sockets) + } else { + assert.NotNil(t, h.sockets) + } + + req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_DUMP) + if tt.wantSocketless { + assert.Nil(t, req.Sockets) + } else { + assert.NotNil(t, req.Sockets) + } + }) + } +}