Skip to content

Commit bcbb72a

Browse files
committed
feat: ip rule subscribe
1 parent dcee557 commit bcbb72a

3 files changed

Lines changed: 434 additions & 69 deletions

File tree

rule.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,9 @@ type RuleUIDRange struct {
8080
Start uint32
8181
End uint32
8282
}
83+
84+
// RuleUpdate is sent when a rule changes - type is RTM_NEWRULE or RTM_DELRULE.
85+
type RuleUpdate struct {
86+
Type uint16
87+
Rule
88+
}

rule_linux.go

Lines changed: 205 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import (
55
"errors"
66
"fmt"
77
"net"
8+
"syscall"
89

910
"github.com/vishvananda/netlink/nl"
11+
"github.com/vishvananda/netns"
1012
"golang.org/x/sys/unix"
1113
)
1214

@@ -119,8 +121,8 @@ func ruleHandle(rule *Rule, req *nl.NetlinkRequest) error {
119121
req.AddData(nl.NewRtAttr(nl.FRA_FLOW, b))
120122
}
121123
if rule.TunID > 0 {
122-
b := make([]byte, 4)
123-
native.PutUint32(b, uint32(rule.TunID))
124+
b := make([]byte, 8)
125+
networkOrder.PutUint64(b, uint64(rule.TunID))
124126
req.AddData(nl.NewRtAttr(nl.FRA_TUN_ID, b))
125127
}
126128
if rule.Table >= 256 {
@@ -154,9 +156,7 @@ func ruleHandle(rule *Rule, req *nl.NetlinkRequest) error {
154156
}
155157

156158
if rule.IPProto > 0 {
157-
b := make([]byte, 4)
158-
native.PutUint32(b, uint32(rule.IPProto))
159-
req.AddData(nl.NewRtAttr(nl.FRA_IP_PROTO, b))
159+
req.AddData(nl.NewRtAttr(nl.FRA_IP_PROTO, nl.Uint8Attr(uint8(rule.IPProto))))
160160
}
161161

162162
if rule.Dport != nil {
@@ -227,73 +227,11 @@ func (h *Handle) RuleListFiltered(family int, filter *Rule, filterMask uint64) (
227227

228228
var res = make([]Rule, 0)
229229
for i := range msgs {
230-
msg := nl.DeserializeRtMsg(msgs[i])
231-
attrs, err := nl.ParseRouteAttr(msgs[i][msg.Len():])
230+
rule, err := deserializeRule(msgs[i])
232231
if err != nil {
233232
return nil, err
234233
}
235234

236-
rule := NewRule()
237-
rule.Priority = 0 // The default priority from kernel
238-
239-
rule.Invert = msg.Flags&FibRuleInvert > 0
240-
rule.Family = int(msg.Family)
241-
rule.Tos = uint(msg.Tos)
242-
243-
for j := range attrs {
244-
switch attrs[j].Attr.Type {
245-
case unix.RTA_TABLE:
246-
rule.Table = int(native.Uint32(attrs[j].Value[0:4]))
247-
case nl.FRA_SRC:
248-
rule.Src = &net.IPNet{
249-
IP: attrs[j].Value,
250-
Mask: net.CIDRMask(int(msg.Src_len), 8*len(attrs[j].Value)),
251-
}
252-
case nl.FRA_DST:
253-
rule.Dst = &net.IPNet{
254-
IP: attrs[j].Value,
255-
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attrs[j].Value)),
256-
}
257-
case nl.FRA_FWMARK:
258-
rule.Mark = native.Uint32(attrs[j].Value[0:4])
259-
case nl.FRA_FWMASK:
260-
mask := native.Uint32(attrs[j].Value[0:4])
261-
rule.Mask = &mask
262-
case nl.FRA_TUN_ID:
263-
rule.TunID = uint(native.Uint64(attrs[j].Value[0:8]))
264-
case nl.FRA_IIFNAME:
265-
rule.IifName = string(attrs[j].Value[:len(attrs[j].Value)-1])
266-
case nl.FRA_OIFNAME:
267-
rule.OifName = string(attrs[j].Value[:len(attrs[j].Value)-1])
268-
case nl.FRA_SUPPRESS_PREFIXLEN:
269-
i := native.Uint32(attrs[j].Value[0:4])
270-
if i != 0xffffffff {
271-
rule.SuppressPrefixlen = int(i)
272-
}
273-
case nl.FRA_SUPPRESS_IFGROUP:
274-
i := native.Uint32(attrs[j].Value[0:4])
275-
if i != 0xffffffff {
276-
rule.SuppressIfgroup = int(i)
277-
}
278-
case nl.FRA_FLOW:
279-
rule.Flow = int(native.Uint32(attrs[j].Value[0:4]))
280-
case nl.FRA_GOTO:
281-
rule.Goto = int(native.Uint32(attrs[j].Value[0:4]))
282-
case nl.FRA_PRIORITY:
283-
rule.Priority = int(native.Uint32(attrs[j].Value[0:4]))
284-
case nl.FRA_IP_PROTO:
285-
rule.IPProto = int(native.Uint32(attrs[j].Value[0:4]))
286-
case nl.FRA_DPORT_RANGE:
287-
rule.Dport = NewRulePortRange(native.Uint16(attrs[j].Value[0:2]), native.Uint16(attrs[j].Value[2:4]))
288-
case nl.FRA_SPORT_RANGE:
289-
rule.Sport = NewRulePortRange(native.Uint16(attrs[j].Value[0:2]), native.Uint16(attrs[j].Value[2:4]))
290-
case nl.FRA_UID_RANGE:
291-
rule.UIDRange = NewRuleUIDRange(native.Uint32(attrs[j].Value[0:4]), native.Uint32(attrs[j].Value[4:8]))
292-
case nl.FRA_PROTOCOL:
293-
rule.Protocol = uint8(attrs[j].Value[0])
294-
}
295-
}
296-
297235
if filter != nil {
298236
switch {
299237
case filterMask&RT_FILTER_SRC != 0 &&
@@ -316,12 +254,210 @@ func (h *Handle) RuleListFiltered(family int, filter *Rule, filterMask uint64) (
316254
}
317255
}
318256

319-
res = append(res, *rule)
257+
res = append(res, rule)
320258
}
321259

322260
return res, executeErr
323261
}
324262

263+
func deserializeRule(m []byte) (Rule, error) {
264+
msg := nl.DeserializeRtMsg(m)
265+
attrs, err := nl.ParseRouteAttr(m[msg.Len():])
266+
if err != nil {
267+
return Rule{}, err
268+
}
269+
270+
rule := NewRule()
271+
272+
rule.Invert = msg.Flags&FibRuleInvert > 0
273+
rule.Tos = uint(msg.Tos)
274+
// The kernel returns the table id in rtmsg.table for values < 256 and as
275+
// the RTA_TABLE attribute for larger values. Start with the header value.
276+
rule.Table = int(msg.Table)
277+
278+
for j := range attrs {
279+
switch attrs[j].Attr.Type {
280+
case unix.RTA_TABLE:
281+
rule.Table = int(native.Uint32(attrs[j].Value[0:4]))
282+
case nl.FRA_SRC:
283+
rule.Src = &net.IPNet{
284+
IP: attrs[j].Value,
285+
Mask: net.CIDRMask(int(msg.Src_len), 8*len(attrs[j].Value)),
286+
}
287+
case nl.FRA_DST:
288+
rule.Dst = &net.IPNet{
289+
IP: attrs[j].Value,
290+
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attrs[j].Value)),
291+
}
292+
case nl.FRA_FWMARK:
293+
rule.Mark = native.Uint32(attrs[j].Value[0:4])
294+
case nl.FRA_FWMASK:
295+
mask := native.Uint32(attrs[j].Value[0:4])
296+
rule.Mask = &mask
297+
case nl.FRA_TUN_ID:
298+
rule.TunID = uint(networkOrder.Uint64(attrs[j].Value[0:8]))
299+
case nl.FRA_IIFNAME:
300+
rule.IifName = string(attrs[j].Value[:len(attrs[j].Value)-1])
301+
case nl.FRA_OIFNAME:
302+
rule.OifName = string(attrs[j].Value[:len(attrs[j].Value)-1])
303+
case nl.FRA_SUPPRESS_PREFIXLEN:
304+
i := native.Uint32(attrs[j].Value[0:4])
305+
if i != 0xffffffff {
306+
rule.SuppressPrefixlen = int(i)
307+
}
308+
case nl.FRA_SUPPRESS_IFGROUP:
309+
i := native.Uint32(attrs[j].Value[0:4])
310+
if i != 0xffffffff {
311+
rule.SuppressIfgroup = int(i)
312+
}
313+
case nl.FRA_FLOW:
314+
rule.Flow = int(native.Uint32(attrs[j].Value[0:4]))
315+
case nl.FRA_GOTO:
316+
rule.Goto = int(native.Uint32(attrs[j].Value[0:4]))
317+
case nl.FRA_PRIORITY:
318+
rule.Priority = int(native.Uint32(attrs[j].Value[0:4]))
319+
case nl.FRA_IP_PROTO:
320+
rule.IPProto = int(attrs[j].Value[0])
321+
case nl.FRA_DPORT_RANGE:
322+
rule.Dport = NewRulePortRange(native.Uint16(attrs[j].Value[0:2]), native.Uint16(attrs[j].Value[2:4]))
323+
case nl.FRA_SPORT_RANGE:
324+
rule.Sport = NewRulePortRange(native.Uint16(attrs[j].Value[0:2]), native.Uint16(attrs[j].Value[2:4]))
325+
case nl.FRA_UID_RANGE:
326+
rule.UIDRange = NewRuleUIDRange(native.Uint32(attrs[j].Value[0:4]), native.Uint32(attrs[j].Value[4:8]))
327+
case nl.FRA_PROTOCOL:
328+
rule.Protocol = uint8(attrs[j].Value[0])
329+
}
330+
}
331+
332+
// Some kernels omit FRA_PRIORITY when the rule priority is 0. Since 0 is a
333+
// valid priority (e.g. the local-table rule), default to 0 if no attribute
334+
// was present.
335+
if rule.Priority < 0 {
336+
rule.Priority = 0
337+
}
338+
339+
rule.Family = int(msg.Family)
340+
rule.Type = msg.Type
341+
342+
return *rule, nil
343+
}
344+
345+
// RuleSubscribe takes a chan down which notifications will be sent
346+
// when rules are added or deleted. The 'done' chan must be closed to stop the
347+
// subscription and release the underlying socket/goroutine.
348+
func RuleSubscribe(ch chan<- RuleUpdate, done <-chan struct{}) error {
349+
return ruleSubscribeAt(netns.None(), netns.None(), ch, done, nil, false, 0, nil, false)
350+
}
351+
352+
// RuleSubscribeAt works like RuleSubscribe plus it allows the caller
353+
// to choose the network namespace in which to subscribe (ns).
354+
// The 'done' chan must be closed to stop the subscription and release resources.
355+
func RuleSubscribeAt(ns netns.NsHandle, ch chan<- RuleUpdate, done <-chan struct{}) error {
356+
return ruleSubscribeAt(ns, netns.None(), ch, done, nil, false, 0, nil, false)
357+
}
358+
359+
// RuleSubscribeOptions contains a set of options to use with
360+
// RuleSubscribeWithOptions.
361+
type RuleSubscribeOptions struct {
362+
Namespace *netns.NsHandle
363+
ErrorCallback func(error)
364+
ListExisting bool
365+
ReceiveBufferSize int
366+
ReceiveBufferForceSize bool
367+
ReceiveTimeout *unix.Timeval
368+
}
369+
370+
// RuleSubscribeWithOptions works like RuleSubscribe but enables
371+
// additional options to modify the behavior.
372+
func RuleSubscribeWithOptions(ch chan<- RuleUpdate, done <-chan struct{}, options RuleSubscribeOptions) error {
373+
if options.Namespace == nil {
374+
none := netns.None()
375+
options.Namespace = &none
376+
}
377+
return ruleSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback, options.ListExisting,
378+
options.ReceiveBufferSize, options.ReceiveTimeout, options.ReceiveBufferForceSize)
379+
}
380+
381+
func ruleSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- RuleUpdate, done <-chan struct{}, cberr func(error), listExisting bool,
382+
rcvbuf int, rcvTimeout *unix.Timeval, rcvBufForce bool) error {
383+
s, err := nl.SubscribeAt(newNs, curNs, unix.NETLINK_ROUTE, unix.RTNLGRP_IPV4_RULE, unix.RTNLGRP_IPV6_RULE)
384+
if err != nil {
385+
return err
386+
}
387+
if rcvTimeout != nil {
388+
if err := s.SetReceiveTimeout(rcvTimeout); err != nil {
389+
return err
390+
}
391+
}
392+
if rcvbuf != 0 {
393+
err = s.SetReceiveBufferSize(rcvbuf, rcvBufForce)
394+
if err != nil {
395+
return err
396+
}
397+
}
398+
if done != nil {
399+
go func() {
400+
<-done
401+
s.Close()
402+
}()
403+
}
404+
if listExisting {
405+
req := pkgHandle().newNetlinkRequest(unix.RTM_GETRULE, unix.NLM_F_DUMP)
406+
infmsg := nl.NewIfInfomsg(unix.AF_UNSPEC)
407+
req.AddData(infmsg)
408+
if err := s.Send(req); err != nil {
409+
s.Close()
410+
return err
411+
}
412+
}
413+
go func() {
414+
defer close(ch)
415+
for {
416+
msgs, from, err := s.Receive()
417+
if err != nil {
418+
if cberr != nil {
419+
cberr(fmt.Errorf("Receive failed: %v", err))
420+
}
421+
return
422+
}
423+
if from.Pid != nl.PidKernel {
424+
if cberr != nil {
425+
cberr(fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel))
426+
}
427+
continue
428+
}
429+
for _, m := range msgs {
430+
if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 && cberr != nil {
431+
cberr(ErrDumpInterrupted)
432+
}
433+
if m.Header.Type == unix.NLMSG_DONE {
434+
continue
435+
}
436+
if m.Header.Type == unix.NLMSG_ERROR {
437+
error := int32(native.Uint32(m.Data[0:4]))
438+
if error == 0 {
439+
continue
440+
}
441+
if cberr != nil {
442+
cberr(fmt.Errorf("error message: %v", syscall.Errno(-error)))
443+
}
444+
continue
445+
}
446+
rule, err := deserializeRule(m.Data)
447+
if err != nil {
448+
if cberr != nil {
449+
cberr(err)
450+
}
451+
continue
452+
}
453+
ch <- RuleUpdate{Type: m.Header.Type, Rule: rule}
454+
}
455+
}
456+
}()
457+
458+
return nil
459+
}
460+
325461
func (pr *RulePortRange) toRtAttrData() []byte {
326462
b := [][]byte{make([]byte, 2), make([]byte, 2)}
327463
native.PutUint16(b[0], pr.Start)

0 commit comments

Comments
 (0)