Skip to content

Commit a1e571e

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

3 files changed

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

0 commit comments

Comments
 (0)