Add netlinksafe

A wrapper to add retry on for netlink when it receives a ErrDumpInterrupted

Signed-off-by: Adrian Moisey <adrian@changeover.za.net>
This commit is contained in:
Adrian Moisey
2025-03-27 08:24:26 +02:00
committed by Casey Callendrello
parent 0464017aff
commit 5f88af10a0

321
pkg/netlinksafe/netlink.go Normal file
View File

@@ -0,0 +1,321 @@
// Package netlinksafe 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 "netlinksafe.X"
// instead of "netlink.X".
//
// The wrapped functions currently return EINTR when NLM_F_DUMP_INTR flagged
// in a netlink response, meaning something changed during the dump so results
// may be incomplete or inconsistent.
//
// At present, the possibly incomplete/inconsistent results are not returned
// by netlink functions along with the EINTR. So, it's not possible to do
// anything but retry. After maxAttempts the EINTR will be returned to the
// caller.
package netlinksafe
import (
"log"
"github.com/pkg/errors"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
"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 (h Handle) Close() {
if h.Handle != nil {
h.Handle.Close()
}
}
func retryOnIntr(f func() error) {
for attempt := 0; attempt < maxAttempts; attempt++ {
if err := f(); !errors.Is(err, netlink.ErrDumpInterrupted) {
return
}
}
log.Printf("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.Printf("discarding ErrDumpInterrupted: %+v", errors.WithStack(err))
return nil
}
return err
}
// AddrList calls netlink.AddrList, retrying if necessary.
func AddrList(link netlink.Link, family int) ([]netlink.Addr, error) {
var addrs []netlink.Addr
var err error
retryOnIntr(func() error {
addrs, err = netlink.AddrList(link, family) //nolint:forbidigo
return err
})
return addrs, discardErrDumpInterrupted(err)
}
// LinkByName calls h.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 (h Handle) LinkByName(name string) (netlink.Link, error) {
var link netlink.Link
var err error
retryOnIntr(func() error {
link, err = h.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) (netlink.Link, error) {
var link netlink.Link
var err error
retryOnIntr(func() error {
link, err = netlink.LinkByName(name) //nolint:forbidigo
return err
})
return link, discardErrDumpInterrupted(err)
}
// LinkList calls h.Handle.LinkList, retrying if necessary.
func (h Handle) LinkList() ([]netlink.Link, error) {
var links []netlink.Link
var err error
retryOnIntr(func() error {
links, err = h.Handle.LinkList() //nolint:forbidigo
return err
})
return links, discardErrDumpInterrupted(err)
}
// LinkList calls netlink.Handle.LinkList, retrying if necessary.
func LinkList() ([]netlink.Link, error) {
var links []netlink.Link
var err error
retryOnIntr(func() error {
links, err = netlink.LinkList() //nolint:forbidigo
return err
})
return links, discardErrDumpInterrupted(err)
}
// RouteList calls h.Handle.RouteList, retrying if necessary.
func (h Handle) RouteList(link netlink.Link, family int) ([]netlink.Route, error) {
var routes []netlink.Route
var err error
retryOnIntr(func() error {
routes, err = h.Handle.RouteList(link, family) //nolint:forbidigo
return err
})
return routes, err
}
// RouteList calls netlink.RouteList, retrying if necessary.
func RouteList(link netlink.Link, family int) ([]netlink.Route, error) {
var route []netlink.Route
var err error
retryOnIntr(func() error {
route, err = netlink.RouteList(link, family) //nolint:forbidigo
return err
})
return route, discardErrDumpInterrupted(err)
}
// BridgeVlanList calls netlink.BridgeVlanList, retrying if necessary.
func BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) {
var err error
var info map[int32][]*nl.BridgeVlanInfo
retryOnIntr(func() error {
info, err = netlink.BridgeVlanList() //nolint:forbidigo
return err
})
return info, discardErrDumpInterrupted(err)
}
// RouteListFiltered calls h.Handle.RouteListFiltered, retrying if necessary.
func (h Handle) RouteListFiltered(family int, filter *netlink.Route, filterMask uint64) ([]netlink.Route, error) {
var routes []netlink.Route
var err error
retryOnIntr(func() error {
routes, err = h.Handle.RouteListFiltered(family, filter, filterMask) //nolint:forbidigo
return err
})
return routes, err
}
// RouteListFiltered calls netlink.RouteListFiltered, retrying if necessary.
func RouteListFiltered(family int, filter *netlink.Route, filterMask uint64) ([]netlink.Route, error) {
var route []netlink.Route
var err error
retryOnIntr(func() error {
route, err = netlink.RouteListFiltered(family, filter, filterMask) //nolint:forbidigo
return err
})
return route, discardErrDumpInterrupted(err)
}
// QdiscList calls netlink.QdiscList, retrying if necessary.
func QdiscList(link netlink.Link) ([]netlink.Qdisc, error) {
var qdisc []netlink.Qdisc
var err error
retryOnIntr(func() error {
qdisc, err = netlink.QdiscList(link) //nolint:forbidigo
return err
})
return qdisc, discardErrDumpInterrupted(err)
}
// QdiscList calls h.Handle.QdiscList, retrying if necessary.
func (h *Handle) QdiscList(link netlink.Link) ([]netlink.Qdisc, error) {
var qdisc []netlink.Qdisc
var err error
retryOnIntr(func() error {
qdisc, err = h.Handle.QdiscList(link) //nolint:forbidigo
return err
})
return qdisc, err
}
// LinkGetProtinfo calls netlink.LinkGetProtinfo, retrying if necessary.
func LinkGetProtinfo(link netlink.Link) (netlink.Protinfo, error) {
var protinfo netlink.Protinfo
var err error
retryOnIntr(func() error {
protinfo, err = netlink.LinkGetProtinfo(link) //nolint:forbidigo
return err
})
return protinfo, discardErrDumpInterrupted(err)
}
// LinkGetProtinfo calls h.Handle.LinkGetProtinfo, retrying if necessary.
func (h *Handle) LinkGetProtinfo(link netlink.Link) (netlink.Protinfo, error) {
var protinfo netlink.Protinfo
var err error
retryOnIntr(func() error {
protinfo, err = h.Handle.LinkGetProtinfo(link) //nolint:forbidigo
return err
})
return protinfo, err
}
// RuleListFiltered calls netlink.RuleListFiltered, retrying if necessary.
func RuleListFiltered(family int, filter *netlink.Rule, filterMask uint64) ([]netlink.Rule, error) {
var rules []netlink.Rule
var err error
retryOnIntr(func() error {
rules, err = netlink.RuleListFiltered(family, filter, filterMask) //nolint:forbidigo
return err
})
return rules, discardErrDumpInterrupted(err)
}
// RuleListFiltered calls h.Handle.RuleListFiltered, retrying if necessary.
func (h *Handle) RuleListFiltered(family int, filter *netlink.Rule, filterMask uint64) ([]netlink.Rule, error) {
var rules []netlink.Rule
var err error
retryOnIntr(func() error {
rules, err = h.Handle.RuleListFiltered(family, filter, filterMask) //nolint:forbidigo
return err
})
return rules, err
}
// FilterList calls netlink.FilterList, retrying if necessary.
func FilterList(link netlink.Link, parent uint32) ([]netlink.Filter, error) {
var filters []netlink.Filter
var err error
retryOnIntr(func() error {
filters, err = netlink.FilterList(link, parent) //nolint:forbidigo
return err
})
return filters, discardErrDumpInterrupted(err)
}
// FilterList calls h.Handle.FilterList, retrying if necessary.
func (h *Handle) FilterList(link netlink.Link, parent uint32) ([]netlink.Filter, error) {
var filters []netlink.Filter
var err error
retryOnIntr(func() error {
filters, err = h.Handle.FilterList(link, parent) //nolint:forbidigo
return err
})
return filters, err
}
// RuleList calls netlink.RuleList, retrying if necessary.
func RuleList(family int) ([]netlink.Rule, error) {
var rules []netlink.Rule
var err error
retryOnIntr(func() error {
rules, err = netlink.RuleList(family) //nolint:forbidigo
return err
})
return rules, discardErrDumpInterrupted(err)
}
// RuleList calls h.Handle.RuleList, retrying if necessary.
func (h *Handle) RuleList(family int) ([]netlink.Rule, error) {
var rules []netlink.Rule
var err error
retryOnIntr(func() error {
rules, err = h.Handle.RuleList(family) //nolint:forbidigo
return err
})
return rules, err
}
// ConntrackDeleteFilters calls netlink.ConntrackDeleteFilters, retrying if necessary.
func ConntrackDeleteFilters(table netlink.ConntrackTableType, family netlink.InetFamily, filters ...netlink.CustomConntrackFilter) (uint, error) {
var deleted uint
var err error
retryOnIntr(func() error {
deleted, err = netlink.ConntrackDeleteFilters(table, family, filters...) //nolint:forbidigo
return err
})
return deleted, discardErrDumpInterrupted(err)
}
// ConntrackDeleteFilters calls h.Handle.ConntrackDeleteFilters, retrying if necessary.
func (h *Handle) ConntrackDeleteFilters(table netlink.ConntrackTableType, family netlink.InetFamily, filters ...netlink.CustomConntrackFilter) (uint, error) {
var deleted uint
var err error
retryOnIntr(func() error {
deleted, err = h.Handle.ConntrackDeleteFilters(table, family, filters...) //nolint:forbidigo
return err
})
return deleted, err
}