Skip to content

Commit cc2233b

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

3 files changed

Lines changed: 428 additions & 64 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: 202 additions & 64 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

@@ -227,73 +229,11 @@ func (h *Handle) RuleListFiltered(family int, filter *Rule, filterMask uint64) (
227229

228230
var res = make([]Rule, 0)
229231
for i := range msgs {
230-
msg := nl.DeserializeRtMsg(msgs[i])
231-
attrs, err := nl.ParseRouteAttr(msgs[i][msg.Len():])
232+
rule, err := deserializeRule(msgs[i])
232233
if err != nil {
233234
return nil, err
234235
}
235236

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-
297237
if filter != nil {
298238
switch {
299239
case filterMask&RT_FILTER_SRC != 0 &&
@@ -316,12 +256,210 @@ func (h *Handle) RuleListFiltered(family int, filter *Rule, filterMask uint64) (
316256
}
317257
}
318258

319-
res = append(res, *rule)
259+
res = append(res, rule)
320260
}
321261

322262
return res, executeErr
323263
}
324264

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

0 commit comments

Comments
 (0)