Skip to content

Commit d049a96

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

3 files changed

Lines changed: 366 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: 160 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,168 @@ 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+
277+
for j := range attrs {
278+
switch attrs[j].Attr.Type {
279+
case unix.RTA_TABLE:
280+
rule.Table = int(native.Uint32(attrs[j].Value[0:4]))
281+
case nl.FRA_SRC:
282+
rule.Src = &net.IPNet{
283+
IP: attrs[j].Value,
284+
Mask: net.CIDRMask(int(msg.Src_len), 8*len(attrs[j].Value)),
285+
}
286+
case nl.FRA_DST:
287+
rule.Dst = &net.IPNet{
288+
IP: attrs[j].Value,
289+
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attrs[j].Value)),
290+
}
291+
case nl.FRA_FWMARK:
292+
rule.Mark = int(native.Uint32(attrs[j].Value[0:4]))
293+
case nl.FRA_FWMASK:
294+
rule.Mask = int(native.Uint32(attrs[j].Value[0:4]))
295+
case nl.FRA_TUN_ID:
296+
rule.TunID = uint(native.Uint64(attrs[j].Value[0:8]))
297+
case nl.FRA_IIFNAME:
298+
rule.IifName = string(attrs[j].Value[:len(attrs[j].Value)-1])
299+
case nl.FRA_OIFNAME:
300+
rule.OifName = string(attrs[j].Value[:len(attrs[j].Value)-1])
301+
case nl.FRA_SUPPRESS_PREFIXLEN:
302+
i := native.Uint32(attrs[j].Value[0:4])
303+
if i != 0xffffffff {
304+
rule.SuppressPrefixlen = int(i)
305+
}
306+
case nl.FRA_SUPPRESS_IFGROUP:
307+
i := native.Uint32(attrs[j].Value[0:4])
308+
if i != 0xffffffff {
309+
rule.SuppressIfgroup = int(i)
310+
}
311+
case nl.FRA_FLOW:
312+
rule.Flow = int(native.Uint32(attrs[j].Value[0:4]))
313+
case nl.FRA_GOTO:
314+
rule.Goto = int(native.Uint32(attrs[j].Value[0:4]))
315+
case nl.FRA_PRIORITY:
316+
rule.Priority = int(native.Uint32(attrs[j].Value[0:4]))
317+
case nl.FRA_DPORT_RANGE:
318+
rule.Dport = NewRulePortRange(native.Uint16(attrs[j].Value[0:2]), native.Uint16(attrs[j].Value[2:4]))
319+
case nl.FRA_SPORT_RANGE:
320+
rule.Sport = NewRulePortRange(native.Uint16(attrs[j].Value[0:2]), native.Uint16(attrs[j].Value[2:4]))
321+
}
322+
}
323+
324+
return *rule, nil
325+
}
326+
327+
// RuleSubscribe takes a chan down which notifications will be sent
328+
// when rules are added or deleted. Close the 'done' chan to stop subscription.
329+
func RuleSubscribe(ch chan<- RuleUpdate, done <-chan struct{}) error {
330+
return ruleSubscribeAt(netns.None(), netns.None(), ch, done, nil, false)
331+
}
332+
333+
// RuleSubscribeAt works like RuleSubscribe plus it allows the caller
334+
// to choose the network namespace in which to subscribe (ns).
335+
func RuleSubscribeAt(ns netns.NsHandle, ch chan<- RuleUpdate, done <-chan struct{}) error {
336+
return ruleSubscribeAt(ns, netns.None(), ch, done, nil, false)
337+
}
338+
339+
// RuleSubscribeOptions contains a set of options to use with
340+
// RuleSubscribeWithOptions.
341+
type RuleSubscribeOptions struct {
342+
Namespace *netns.NsHandle
343+
ErrorCallback func(error)
344+
ListExisting bool
345+
}
346+
347+
// RuleSubscribeWithOptions works like RuleSubscribe but enables
348+
// additional options to modify the behavior.
349+
func RuleSubscribeWithOptions(ch chan<- RuleUpdate, done <-chan struct{}, options RuleSubscribeOptions) error {
350+
if options.Namespace == nil {
351+
none := netns.None()
352+
options.Namespace = &none
353+
}
354+
return ruleSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback, options.ListExisting)
355+
}
356+
357+
func ruleSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- RuleUpdate, done <-chan struct{}, cberr func(error), listExisting bool) error {
358+
s, err := nl.SubscribeAt(newNs, curNs, unix.NETLINK_ROUTE, unix.RTNLGRP_IPV4_RULE, unix.RTNLGRP_IPV6_RULE)
359+
if err != nil {
360+
return err
361+
}
362+
if done != nil {
363+
go func() {
364+
<-done
365+
s.Close()
366+
}()
367+
}
368+
if listExisting {
369+
req := pkgHandle.newNetlinkRequest(unix.RTM_GETRULE, unix.NLM_F_DUMP)
370+
infmsg := nl.NewIfInfomsg(unix.AF_UNSPEC)
371+
req.AddData(infmsg)
372+
if err := s.Send(req); err != nil {
373+
return err
374+
}
375+
}
376+
go func() {
377+
defer close(ch)
378+
for {
379+
msgs, from, err := s.Receive()
380+
if err != nil {
381+
if cberr != nil {
382+
cberr(fmt.Errorf("Receive failed: %v", err))
383+
}
384+
return
385+
}
386+
if from.Pid != nl.PidKernel {
387+
if cberr != nil {
388+
cberr(fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel))
389+
}
390+
continue
391+
}
392+
for _, m := range msgs {
393+
if m.Header.Type == unix.NLMSG_DONE {
394+
continue
395+
}
396+
if m.Header.Type == unix.NLMSG_ERROR {
397+
error := int32(native.Uint32(m.Data[0:4]))
398+
if error == 0 {
399+
continue
400+
}
401+
if cberr != nil {
402+
cberr(fmt.Errorf("error message: %v", syscall.Errno(-error)))
403+
}
404+
continue
405+
}
406+
rule, err := deserializeRule(m.Data)
407+
if err != nil {
408+
if cberr != nil {
409+
cberr(err)
410+
}
411+
continue
412+
}
413+
ch <- RuleUpdate{Type: m.Header.Type, Rule: rule}
414+
}
415+
}
416+
}()
417+
418+
return nil
419+
}
420+
325421
func (pr *RulePortRange) toRtAttrData() []byte {
326422
b := [][]byte{make([]byte, 2), make([]byte, 2)}
327423
native.PutUint16(b[0], pr.Start)

0 commit comments

Comments
 (0)