// 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 }