Files
moby/daemon/libnetwork/nlwrap/nlwrap_linux.go
Paweł Gronowski 3df05205f4 modernize: Use range int
Added in Go 1.22

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-15 18:56:34 +01:00

200 lines
6.8 KiB
Go

// Package nlwrap wraps vishvandanda/netlink functions that may return EINTR.
//
// A Handle instantiated using [NewHandle] or [NewHandleAt] can be used in place
// of a netlink.Handle, it's a wrapper that replaces methods that need to be
// wrapped. Functions that use the package handle need to be called as "nlwrap.X"
// instead of "netlink.X".
//
// When netlink.ErrDumpInterrupted is returned, the wrapped functions retry up to
// maxAttempts times. This error means NLM_F_DUMP_INTR was flagged in a netlink
// response, meaning something changed during the dump so results may be
// incomplete or inconsistent.
//
// To avoid retrying indefinitely, if netlink.ErrDumpInterrupted is still
// returned after maxAttempts, the wrapped functions will discard the error, log
// a stack trace to make the issue visible and aid in debugging, and return the
// possibly inconsistent results. Returning possibly inconsistent results matches
// the behaviour of vishvananda/netlink versions prior to 1.2.1, in which the
// NLM_F_DUMP_INTR flag was ignored.
package nlwrap
import (
"context"
"github.com/containerd/log"
"github.com/pkg/errors"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
)
// Arbitrary limit on max attempts at netlink calls if they are repeatedly interrupted.
const maxAttempts = 5
type Handle struct {
*netlink.Handle
}
func NewHandle(nlFamilies ...int) (Handle, error) {
nlh, err := netlink.NewHandle(nlFamilies...)
if err != nil {
return Handle{}, err
}
return Handle{nlh}, nil
}
func NewHandleAt(ns netns.NsHandle, nlFamilies ...int) (Handle, error) {
nlh, err := netlink.NewHandleAt(ns, nlFamilies...)
if err != nil {
return Handle{}, err
}
return Handle{nlh}, nil
}
func (nlh Handle) Close() {
if nlh.Handle != nil {
nlh.Handle.Close()
}
}
func retryOnIntr(f func() error) {
for range maxAttempts {
if err := f(); !errors.Is(err, netlink.ErrDumpInterrupted) {
return
}
}
log.G(context.TODO()).Infof("netlink call interrupted after %d attempts", maxAttempts)
}
func discardErrDumpInterrupted(err error) error {
if errors.Is(err, netlink.ErrDumpInterrupted) {
// The netlink function has returned possibly-inconsistent data along with the
// error. Discard the error and return the data. This restores the behaviour of
// the netlink package prior to v1.2.1, in which NLM_F_DUMP_INTR was ignored in
// the netlink response.
log.G(context.TODO()).Warnf("discarding ErrDumpInterrupted: %+v", errors.WithStack(err))
return nil
}
return err
}
// AddrList calls nlh.Handle.AddrList, retrying if necessary.
func (nlh Handle) AddrList(link netlink.Link, family int) (addrs []netlink.Addr, err error) {
retryOnIntr(func() error {
addrs, err = nlh.Handle.AddrList(link, family) //nolint:forbidigo
return err
})
return addrs, discardErrDumpInterrupted(err)
}
// AddrList calls netlink.AddrList, retrying if necessary.
func AddrList(link netlink.Link, family int) (addrs []netlink.Addr, err error) {
retryOnIntr(func() error {
addrs, err = netlink.AddrList(link, family) //nolint:forbidigo
return err
})
return addrs, discardErrDumpInterrupted(err)
}
// ConntrackDeleteFilters calls nlh.Handle.ConntrackDeleteFilters, retrying if necessary.
func (nlh Handle) ConntrackDeleteFilters(
table netlink.ConntrackTableType,
family netlink.InetFamily,
filters ...netlink.CustomConntrackFilter,
) (matched uint, err error) {
retryOnIntr(func() error {
matched, err = nlh.Handle.ConntrackDeleteFilters(table, family, filters...) //nolint:forbidigo
return err
})
return matched, discardErrDumpInterrupted(err)
}
// ConntrackTableList calls netlink.ConntrackTableList, retrying if necessary.
func ConntrackTableList(
table netlink.ConntrackTableType,
family netlink.InetFamily,
) (flows []*netlink.ConntrackFlow, err error) {
retryOnIntr(func() error {
flows, err = netlink.ConntrackTableList(table, family) //nolint:forbidigo
return err
})
return flows, discardErrDumpInterrupted(err)
}
// LinkByName calls nlh.Handle.LinkByName, retrying if necessary. The netlink function
// doesn't normally ask the kernel for a dump of links. But, on an old kernel, it
// will do as a fallback and that dump may get inconsistent results.
func (nlh Handle) LinkByName(name string) (link netlink.Link, err error) {
retryOnIntr(func() error {
link, err = nlh.Handle.LinkByName(name) //nolint:forbidigo
return err
})
return link, discardErrDumpInterrupted(err)
}
// LinkByName calls netlink.LinkByName, retrying if necessary. The netlink
// function doesn't normally ask the kernel for a dump of links. But, on an old
// kernel, it will do as a fallback and that dump may get inconsistent results.
func LinkByName(name string) (link netlink.Link, err error) {
retryOnIntr(func() error {
link, err = netlink.LinkByName(name) //nolint:forbidigo
return err
})
return link, discardErrDumpInterrupted(err)
}
// LinkList calls nlh.Handle.LinkList, retrying if necessary.
func (nlh Handle) LinkList() (links []netlink.Link, err error) {
retryOnIntr(func() error {
links, err = nlh.Handle.LinkList() //nolint:forbidigo
return err
})
return links, discardErrDumpInterrupted(err)
}
// LinkList calls netlink.Handle.LinkList, retrying if necessary.
func LinkList() (links []netlink.Link, err error) {
retryOnIntr(func() error {
links, err = netlink.LinkList() //nolint:forbidigo
return err
})
return links, discardErrDumpInterrupted(err)
}
// LinkSubscribeWithOptions calls netlink.LinkSubscribeWithOptions, retrying if necessary.
// Close the done channel when done (rather than just sending on it), so that goroutines
// started by the netlink package are all stopped.
func LinkSubscribeWithOptions(ch chan<- netlink.LinkUpdate, done <-chan struct{}, options netlink.LinkSubscribeOptions) (err error) {
retryOnIntr(func() error {
err = netlink.LinkSubscribeWithOptions(ch, done, options) //nolint:forbidigo
return err
})
return err
}
// RouteList calls nlh.Handle.RouteList, retrying if necessary.
func (nlh Handle) RouteList(link netlink.Link, family int) (routes []netlink.Route, err error) {
retryOnIntr(func() error {
routes, err = nlh.Handle.RouteList(link, family) //nolint:forbidigo
return err
})
return routes, discardErrDumpInterrupted(err)
}
// XfrmPolicyList calls nlh.Handle.XfrmPolicyList, retrying if necessary.
func (nlh Handle) XfrmPolicyList(family int) (policies []netlink.XfrmPolicy, err error) {
retryOnIntr(func() error {
policies, err = nlh.Handle.XfrmPolicyList(family) //nolint:forbidigo
return err
})
return policies, discardErrDumpInterrupted(err)
}
// XfrmStateList calls nlh.Handle.XfrmStateList, retrying if necessary.
func (nlh Handle) XfrmStateList(family int) (states []netlink.XfrmState, err error) {
retryOnIntr(func() error {
states, err = nlh.Handle.XfrmStateList(family) //nolint:forbidigo
return err
})
return states, discardErrDumpInterrupted(err)
}