mirror of
https://github.com/rancher/plugins.git
synced 2025-09-12 18:36:40 +00:00
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:
committed by
Casey Callendrello
parent
0464017aff
commit
5f88af10a0
321
pkg/netlinksafe/netlink.go
Normal file
321
pkg/netlinksafe/netlink.go
Normal 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
|
||||||
|
}
|
Reference in New Issue
Block a user