Skip to content

Commit 5decd08

Browse files
committed
Add netlinksafe
A wrapper to add retry on for netlink when it receives a ErrDumpInterrupted
1 parent 85f4e45 commit 5decd08

File tree

1 file changed

+321
-0
lines changed

1 file changed

+321
-0
lines changed

pkg/netlinksafe/netlink.go

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
// Package netlinksafe wraps vishvandanda/netlink functions that may return EINTR.
2+
//
3+
// A Handle instantiated using [NewHandle] or [NewHandleAt] can be used in place
4+
// of a netlink.Handle, it's a wrapper that replaces methods that need to be
5+
// wrapped. Functions that use the package handle need to be called as "netlinksafe.X"
6+
// instead of "netlink.X".
7+
//
8+
// The wrapped functions currently return EINTR when NLM_F_DUMP_INTR flagged
9+
// in a netlink response, meaning something changed during the dump so results
10+
// may be incomplete or inconsistent.
11+
//
12+
// At present, the possibly incomplete/inconsistent results are not returned
13+
// by netlink functions along with the EINTR. So, it's not possible to do
14+
// anything but retry. After maxAttempts the EINTR will be returned to the
15+
// caller.
16+
package netlinksafe
17+
18+
import (
19+
"log"
20+
21+
"github.com/pkg/errors"
22+
"github.com/vishvananda/netlink"
23+
"github.com/vishvananda/netlink/nl"
24+
"github.com/vishvananda/netns"
25+
)
26+
27+
// Arbitrary limit on max attempts at netlink calls if they are repeatedly interrupted.
28+
const maxAttempts = 5
29+
30+
type Handle struct {
31+
*netlink.Handle
32+
}
33+
34+
func NewHandle(nlFamilies ...int) (Handle, error) {
35+
nlh, err := netlink.NewHandle(nlFamilies...)
36+
if err != nil {
37+
return Handle{}, err
38+
}
39+
return Handle{nlh}, nil
40+
}
41+
42+
func NewHandleAt(ns netns.NsHandle, nlFamilies ...int) (Handle, error) {
43+
nlh, err := netlink.NewHandleAt(ns, nlFamilies...)
44+
if err != nil {
45+
return Handle{}, err
46+
}
47+
return Handle{nlh}, nil
48+
}
49+
50+
func (h Handle) Close() {
51+
if h.Handle != nil {
52+
h.Handle.Close()
53+
}
54+
}
55+
56+
func retryOnIntr(f func() error) {
57+
for attempt := 0; attempt < maxAttempts; attempt++ {
58+
if err := f(); !errors.Is(err, netlink.ErrDumpInterrupted) {
59+
return
60+
}
61+
}
62+
log.Printf("netlink call interrupted after %d attempts", maxAttempts)
63+
}
64+
65+
func discardErrDumpInterrupted(err error) error {
66+
if errors.Is(err, netlink.ErrDumpInterrupted) {
67+
// The netlink function has returned possibly-inconsistent data along with the
68+
// error. Discard the error and return the data. This restores the behaviour of
69+
// the netlink package prior to v1.2.1, in which NLM_F_DUMP_INTR was ignored in
70+
// the netlink response.
71+
log.Printf("discarding ErrDumpInterrupted: %+v", errors.WithStack(err))
72+
return nil
73+
}
74+
return err
75+
}
76+
77+
// AddrList calls netlink.AddrList, retrying if necessary.
78+
func AddrList(link netlink.Link, family int) ([]netlink.Addr, error) {
79+
var addrs []netlink.Addr
80+
var err error
81+
retryOnIntr(func() error {
82+
addrs, err = netlink.AddrList(link, family) //nolint:forbidigo
83+
return err
84+
})
85+
return addrs, discardErrDumpInterrupted(err)
86+
}
87+
88+
// LinkByName calls h.Handle.LinkByName, retrying if necessary. The netlink function
89+
// doesn't normally ask the kernel for a dump of links. But, on an old kernel, it
90+
// will do as a fallback and that dump may get inconsistent results.
91+
func (h Handle) LinkByName(name string) (netlink.Link, error) {
92+
var link netlink.Link
93+
var err error
94+
retryOnIntr(func() error {
95+
link, err = h.Handle.LinkByName(name) //nolint:forbidigo
96+
return err
97+
})
98+
return link, discardErrDumpInterrupted(err)
99+
}
100+
101+
// LinkByName calls netlink.LinkByName, retrying if necessary. The netlink
102+
// function doesn't normally ask the kernel for a dump of links. But, on an old
103+
// kernel, it will do as a fallback and that dump may get inconsistent results.
104+
func LinkByName(name string) (netlink.Link, error) {
105+
var link netlink.Link
106+
var err error
107+
retryOnIntr(func() error {
108+
link, err = netlink.LinkByName(name) //nolint:forbidigo
109+
return err
110+
})
111+
return link, discardErrDumpInterrupted(err)
112+
}
113+
114+
// LinkList calls h.Handle.LinkList, retrying if necessary.
115+
func (h Handle) LinkList() ([]netlink.Link, error) {
116+
var links []netlink.Link
117+
var err error
118+
retryOnIntr(func() error {
119+
links, err = h.Handle.LinkList() //nolint:forbidigo
120+
return err
121+
})
122+
return links, discardErrDumpInterrupted(err)
123+
}
124+
125+
// LinkList calls netlink.Handle.LinkList, retrying if necessary.
126+
func LinkList() ([]netlink.Link, error) {
127+
var links []netlink.Link
128+
var err error
129+
retryOnIntr(func() error {
130+
links, err = netlink.LinkList() //nolint:forbidigo
131+
return err
132+
})
133+
return links, discardErrDumpInterrupted(err)
134+
}
135+
136+
// RouteList calls h.Handle.RouteList, retrying if necessary.
137+
func (h Handle) RouteList(link netlink.Link, family int) ([]netlink.Route, error) {
138+
var routes []netlink.Route
139+
var err error
140+
retryOnIntr(func() error {
141+
routes, err = h.Handle.RouteList(link, family) //nolint:forbidigo
142+
return err
143+
})
144+
return routes, err
145+
}
146+
147+
// RouteList calls netlink.RouteList, retrying if necessary.
148+
func RouteList(link netlink.Link, family int) ([]netlink.Route, error) {
149+
var route []netlink.Route
150+
var err error
151+
retryOnIntr(func() error {
152+
route, err = netlink.RouteList(link, family) //nolint:forbidigo
153+
return err
154+
})
155+
return route, discardErrDumpInterrupted(err)
156+
}
157+
158+
// BridgeVlanList calls netlink.BridgeVlanList, retrying if necessary.
159+
func BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) {
160+
var err error
161+
var info map[int32][]*nl.BridgeVlanInfo
162+
retryOnIntr(func() error {
163+
info, err = netlink.BridgeVlanList() //nolint:forbidigo
164+
return err
165+
})
166+
return info, discardErrDumpInterrupted(err)
167+
}
168+
169+
// RouteListFiltered calls h.Handle.RouteListFiltered, retrying if necessary.
170+
func (h Handle) RouteListFiltered(family int, filter *netlink.Route, filterMask uint64) ([]netlink.Route, error) {
171+
var routes []netlink.Route
172+
var err error
173+
retryOnIntr(func() error {
174+
routes, err = h.Handle.RouteListFiltered(family, filter, filterMask) //nolint:forbidigo
175+
return err
176+
})
177+
return routes, err
178+
}
179+
180+
// RouteListFiltered calls netlink.RouteListFiltered, retrying if necessary.
181+
func RouteListFiltered(family int, filter *netlink.Route, filterMask uint64) ([]netlink.Route, error) {
182+
var route []netlink.Route
183+
var err error
184+
retryOnIntr(func() error {
185+
route, err = netlink.RouteListFiltered(family, filter, filterMask) //nolint:forbidigo
186+
return err
187+
})
188+
return route, discardErrDumpInterrupted(err)
189+
}
190+
191+
// QdiscList calls netlink.QdiscList, retrying if necessary.
192+
func QdiscList(link netlink.Link) ([]netlink.Qdisc, error) {
193+
var qdisc []netlink.Qdisc
194+
var err error
195+
retryOnIntr(func() error {
196+
qdisc, err = netlink.QdiscList(link) //nolint:forbidigo
197+
return err
198+
})
199+
return qdisc, discardErrDumpInterrupted(err)
200+
}
201+
202+
// QdiscList calls h.Handle.QdiscList, retrying if necessary.
203+
func (h *Handle) QdiscList(link netlink.Link) ([]netlink.Qdisc, error) {
204+
var qdisc []netlink.Qdisc
205+
var err error
206+
retryOnIntr(func() error {
207+
qdisc, err = h.Handle.QdiscList(link) //nolint:forbidigo
208+
return err
209+
})
210+
return qdisc, err
211+
}
212+
213+
// LinkGetProtinfo calls netlink.LinkGetProtinfo, retrying if necessary.
214+
func LinkGetProtinfo(link netlink.Link) (netlink.Protinfo, error) {
215+
var protinfo netlink.Protinfo
216+
var err error
217+
retryOnIntr(func() error {
218+
protinfo, err = netlink.LinkGetProtinfo(link) //nolint:forbidigo
219+
return err
220+
})
221+
return protinfo, discardErrDumpInterrupted(err)
222+
}
223+
224+
// LinkGetProtinfo calls h.Handle.LinkGetProtinfo, retrying if necessary.
225+
func (h *Handle) LinkGetProtinfo(link netlink.Link) (netlink.Protinfo, error) {
226+
var protinfo netlink.Protinfo
227+
var err error
228+
retryOnIntr(func() error {
229+
protinfo, err = h.Handle.LinkGetProtinfo(link) //nolint:forbidigo
230+
return err
231+
})
232+
return protinfo, err
233+
}
234+
235+
// RuleListFiltered calls netlink.RuleListFiltered, retrying if necessary.
236+
func RuleListFiltered(family int, filter *netlink.Rule, filterMask uint64) ([]netlink.Rule, error) {
237+
var rules []netlink.Rule
238+
var err error
239+
retryOnIntr(func() error {
240+
rules, err = netlink.RuleListFiltered(family, filter, filterMask) //nolint:forbidigo
241+
return err
242+
})
243+
return rules, discardErrDumpInterrupted(err)
244+
}
245+
246+
// RuleListFiltered calls h.Handle.RuleListFiltered, retrying if necessary.
247+
func (h *Handle) RuleListFiltered(family int, filter *netlink.Rule, filterMask uint64) ([]netlink.Rule, error) {
248+
var rules []netlink.Rule
249+
var err error
250+
retryOnIntr(func() error {
251+
rules, err = h.Handle.RuleListFiltered(family, filter, filterMask) //nolint:forbidigo
252+
return err
253+
})
254+
return rules, err
255+
}
256+
257+
// FilterList calls netlink.FilterList, retrying if necessary.
258+
func FilterList(link netlink.Link, parent uint32) ([]netlink.Filter, error) {
259+
var filters []netlink.Filter
260+
var err error
261+
retryOnIntr(func() error {
262+
filters, err = netlink.FilterList(link, parent) //nolint:forbidigo
263+
return err
264+
})
265+
return filters, discardErrDumpInterrupted(err)
266+
}
267+
268+
// FilterList calls h.Handle.FilterList, retrying if necessary.
269+
func (h *Handle) FilterList(link netlink.Link, parent uint32) ([]netlink.Filter, error) {
270+
var filters []netlink.Filter
271+
var err error
272+
retryOnIntr(func() error {
273+
filters, err = h.Handle.FilterList(link, parent) //nolint:forbidigo
274+
return err
275+
})
276+
return filters, err
277+
}
278+
279+
// RuleList calls netlink.RuleList, retrying if necessary.
280+
func RuleList(family int) ([]netlink.Rule, error) {
281+
var rules []netlink.Rule
282+
var err error
283+
retryOnIntr(func() error {
284+
rules, err = netlink.RuleList(family) //nolint:forbidigo
285+
return err
286+
})
287+
return rules, discardErrDumpInterrupted(err)
288+
}
289+
290+
// RuleList calls h.Handle.RuleList, retrying if necessary.
291+
func (h *Handle) RuleList(family int) ([]netlink.Rule, error) {
292+
var rules []netlink.Rule
293+
var err error
294+
retryOnIntr(func() error {
295+
rules, err = h.Handle.RuleList(family) //nolint:forbidigo
296+
return err
297+
})
298+
return rules, err
299+
}
300+
301+
// ConntrackDeleteFilters calls netlink.ConntrackDeleteFilters, retrying if necessary.
302+
func ConntrackDeleteFilters(table netlink.ConntrackTableType, family netlink.InetFamily, filters ...netlink.CustomConntrackFilter) (uint, error) {
303+
var deleted uint
304+
var err error
305+
retryOnIntr(func() error {
306+
deleted, err = netlink.ConntrackDeleteFilters(table, family, filters...) //nolint:forbidigo
307+
return err
308+
})
309+
return deleted, discardErrDumpInterrupted(err)
310+
}
311+
312+
// ConntrackDeleteFilters calls h.Handle.ConntrackDeleteFilters, retrying if necessary.
313+
func (h *Handle) ConntrackDeleteFilters(table netlink.ConntrackTableType, family netlink.InetFamily, filters ...netlink.CustomConntrackFilter) (uint, error) {
314+
var deleted uint
315+
var err error
316+
retryOnIntr(func() error {
317+
deleted, err = h.Handle.ConntrackDeleteFilters(table, family, filters...) //nolint:forbidigo
318+
return err
319+
})
320+
return deleted, err
321+
}

0 commit comments

Comments
 (0)