Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 72 additions & 18 deletions handle_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package netlink

import (
"fmt"
"sync"
"sync/atomic"
"time"

"github.com/vishvananda/netlink/nl"
Expand All @@ -12,10 +14,53 @@ import (
// Empty handle used by the netlink package methods
var pkgHandle = &Handle{}

var configMu sync.Mutex
var configDone bool

// ConfigureHandle configures the default, package-wide netlink handle used by
// the netlink package's global functions like [LinkList] and [AddrList] with
// the given opts. It does not affect any existing or future [Handle] returned
// by the library.
//
// This function is not safe to call concurrently with any netlink operations
// using the global package handle. Invoke it from init() functions only.
//
// Returns an error if called more than once per process.
func ConfigureHandle(opts HandleOptions) error {
configMu.Lock()
defer configMu.Unlock()

if configDone {
return fmt.Errorf("netlink package handle already configured")
}

h, err := NewHandleWithOptions(opts)
if err != nil {
return fmt.Errorf("creating handle: %w", err)
}

configDone = true
Comment thread
ti-mo marked this conversation as resolved.
pkgHandle = h

return nil
Comment thread
ti-mo marked this conversation as resolved.
}

// HandleOptions defines the options for creating a netlink Handle, allowing the
// caller to customize its behaviour.
type HandleOptions struct {
lookupByDump bool
collectVFInfo bool
retryInterrupted bool
// DisableVFInfoCollection controls whether to fetch VF information for each
// link. This is an expensive operation and should be disabled if the caller
// does not need the VF information.
DisableVFInfoCollection bool

// RetryInterrupted controls whether to automatically retry dump operations a
// number of times if they fail with EINTR before finally returning
// [ErrDumpInterrupted].
RetryInterrupted bool

// NetNS specifies the network namespace to operate on. If not set, the
// current network namespace will be used.
NetNS *netns.NsHandle
}

// Handle is a handle for the netlink requests on a
Expand All @@ -25,19 +70,16 @@ type HandleOptions struct {
type Handle struct {
sockets map[int]*nl.SocketHandle
options HandleOptions

lookupByDump atomic.Bool
}

// DisableVFInfoCollection configures the handle to skip VF information fetching
//
// Deprecated: Use [NewHandleWithOptions] and set
// [HandleOptions.DisableVFInfoCollection] instead.
func (h *Handle) DisableVFInfoCollection() *Handle {
h.options.collectVFInfo = false
return h
}

// RetryInterrupted configures the Handle to automatically retry dump operations
// a number of times if they fail with EINTR before finally returning
// [ErrDumpInterrupted].
func (h *Handle) RetryInterrupted() *Handle {
h.options.retryInterrupted = true
h.options.DisableVFInfoCollection = true
return h
}

Expand Down Expand Up @@ -68,7 +110,8 @@ func (h *Handle) SupportsNetlinkFamily(nlFamily int) bool {
// If no families are specified, all the families the netlink package
// supports will be automatically added.
func NewHandle(nlFamilies ...int) (*Handle, error) {
return newHandle(netns.None(), netns.None(), nlFamilies...)
none := netns.None()
return newHandle(none, HandleOptions{NetNS: &none}, nlFamilies...)
}

// SetSocketTimeout sets the send and receive timeout for each socket in the
Expand Down Expand Up @@ -146,24 +189,35 @@ func (h *Handle) SetStrictCheck(state bool) error {
// specified by ns. If ns=netns.None(), current network namespace
// will be assumed
func NewHandleAt(ns netns.NsHandle, nlFamilies ...int) (*Handle, error) {
return newHandle(ns, netns.None(), nlFamilies...)
return newHandle(netns.None(), HandleOptions{NetNS: &ns}, nlFamilies...)
}

// NewHandleAtFrom works as NewHandle but allows client to specify the
// new and the origin netns Handle.
func NewHandleAtFrom(newNs, curNs netns.NsHandle) (*Handle, error) {
return newHandle(newNs, curNs)
return newHandle(curNs, HandleOptions{NetNS: &newNs})
}

// NewHandleWithOptions returns a Handle created using the specified options.
func NewHandleWithOptions(opts HandleOptions, nlFamilies ...int) (*Handle, error) {
return newHandle(netns.None(), opts, nlFamilies...)
}

func newHandle(newNs, curNs netns.NsHandle, nlFamilies ...int) (*Handle, error) {
func newHandle(curNs netns.NsHandle, opts HandleOptions, nlFamilies ...int) (*Handle, error) {
h := &Handle{
sockets: map[int]*nl.SocketHandle{},
options: HandleOptions{collectVFInfo: true},
options: opts,
}
fams := nl.SupportedNlFamilies
if len(nlFamilies) != 0 {
fams = nlFamilies
}

newNs := netns.None()
if opts.NetNS != nil {
newNs = *opts.NetNS
}

for _, f := range fams {
s, err := nl.GetNetlinkSocketAt(newNs, curNs, f)
if err != nil {
Expand Down Expand Up @@ -207,6 +261,6 @@ func (h *Handle) newNetlinkRequest(proto, flags int) *nl.NetlinkRequest {
},
Sockets: h.sockets,

RetryInterrupted: h.options.retryInterrupted,
RetryInterrupted: h.options.RetryInterrupted,
}
}
19 changes: 19 additions & 0 deletions handle_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package netlink
import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestSetGetSocketTimeout(t *testing.T) {
Expand All @@ -15,3 +17,20 @@ func TestSetGetSocketTimeout(t *testing.T) {
t.Fatalf("Unexpected socket timeout value: got=%v, expected=%v", val, timeout)
}
}

func TestConfigureHandle(t *testing.T) {
orig := pkgHandle
origDone := configDone
t.Cleanup(func() {
configMu.Lock()
defer configMu.Unlock()

pkgHandle = orig
configDone = origDone
})

assert.NoError(t, ConfigureHandle(HandleOptions{DisableVFInfoCollection: true}))
assert.NotEqual(t, orig, pkgHandle)
Comment thread
ti-mo marked this conversation as resolved.
assert.NoError(t, pkgHandle.Close())
assert.Error(t, ConfigureHandle(HandleOptions{}))
Comment thread
ti-mo marked this conversation as resolved.
}
Comment thread
ti-mo marked this conversation as resolved.
30 changes: 15 additions & 15 deletions link_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -2020,7 +2020,7 @@ func LinkByName(name string) (Link, error) {
// filtering a dump of all link names. In this case, if the returned error is
// [ErrDumpInterrupted] the result may be missing or outdated.
func (h *Handle) LinkByName(name string) (Link, error) {
if h.options.lookupByDump {
if h.lookupByDump.Load() {
return h.linkByNameDump(name)
}

Expand All @@ -2029,9 +2029,8 @@ func (h *Handle) LinkByName(name string) (Link, error) {
msg := nl.NewIfInfomsg(unix.AF_UNSPEC)
req.AddData(msg)

if h.options.collectVFInfo {
attr := nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF))
req.AddData(attr)
if !h.options.DisableVFInfoCollection {
req.AddData(nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF)))
}

nameData := nl.NewRtAttr(unix.IFLA_IFNAME, nl.ZeroTerminated(name))
Expand All @@ -2044,7 +2043,7 @@ func (h *Handle) LinkByName(name string) (Link, error) {
if err == unix.EINVAL {
// older kernels don't support looking up via IFLA_IFNAME
// so fall back to dumping all links
h.options.lookupByDump = true
h.lookupByDump.Store(true)
return h.linkByNameDump(name)
}

Expand All @@ -2068,7 +2067,7 @@ func LinkByAlias(alias string) (Link, error) {
// filtering a dump of all link names. In this case, if the returned error is
// [ErrDumpInterrupted] the result may be missing or outdated.
func (h *Handle) LinkByAlias(alias string) (Link, error) {
if h.options.lookupByDump {
if h.lookupByDump.Load() {
return h.linkByAliasDump(alias)
}

Expand All @@ -2077,9 +2076,8 @@ func (h *Handle) LinkByAlias(alias string) (Link, error) {
msg := nl.NewIfInfomsg(unix.AF_UNSPEC)
req.AddData(msg)

if h.options.collectVFInfo {
attr := nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF))
req.AddData(attr)
if !h.options.DisableVFInfoCollection {
req.AddData(nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF)))
}

nameData := nl.NewRtAttr(unix.IFLA_IFALIAS, nl.ZeroTerminated(alias))
Expand All @@ -2089,7 +2087,7 @@ func (h *Handle) LinkByAlias(alias string) (Link, error) {
if err == unix.EINVAL {
// older kernels don't support looking up via IFLA_IFALIAS
// so fall back to dumping all links
h.options.lookupByDump = true
h.lookupByDump.Store(true)
return h.linkByAliasDump(alias)
}

Expand All @@ -2108,9 +2106,9 @@ func (h *Handle) LinkByIndex(index int) (Link, error) {
msg := nl.NewIfInfomsg(unix.AF_UNSPEC)
msg.Index = int32(index)
req.AddData(msg)
if h.options.collectVFInfo {
attr := nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF))
req.AddData(attr)

if !h.options.DisableVFInfoCollection {
req.AddData(nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF)))
}

return execGetLink(req)
Expand Down Expand Up @@ -2529,8 +2527,10 @@ func (h *Handle) LinkList() ([]Link, error) {

msg := nl.NewIfInfomsg(unix.AF_UNSPEC)
req.AddData(msg)
attr := nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF))
req.AddData(attr)

if !h.options.DisableVFInfoCollection {
req.AddData(nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF)))
}

msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWLINK)
if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) {
Expand Down
Loading