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) + } + }) + } +}