netlink/rule_test.go
Nikolay Aleksandrov ced5aaba43 rule: add support for FRA_PROTOCOL
Add support for ip rules' FRA_PROTOCOL attribute and also check for it
when testing rules. The default ip rule protocol is RTPROT_UNSPEC (0) so
we set the attribute only when it is >0.

Signed-off-by: Nikolay Aleksandrov <razor@blackwall.org>
2023-03-16 09:30:32 -07:00

427 lines
9.8 KiB
Go

//go:build linux
// +build linux
package netlink
import (
"net"
"testing"
"golang.org/x/sys/unix"
)
func TestRuleAddDel(t *testing.T) {
skipUnlessRoot(t)
defer setUpNetlinkTest(t)()
srcNet := &net.IPNet{IP: net.IPv4(172, 16, 0, 1), Mask: net.CIDRMask(16, 32)}
dstNet := &net.IPNet{IP: net.IPv4(172, 16, 1, 1), Mask: net.CIDRMask(24, 32)}
rulesBegin, err := RuleList(FAMILY_V4)
if err != nil {
t.Fatal(err)
}
rule := NewRule()
rule.Table = unix.RT_TABLE_MAIN
rule.Src = srcNet
rule.Dst = dstNet
rule.Priority = 5
rule.OifName = "lo"
rule.IifName = "lo"
rule.Invert = true
rule.Tos = 0x10
rule.Dport = NewRulePortRange(80, 80)
rule.Sport = NewRulePortRange(1000, 1024)
rule.IPProto = unix.IPPROTO_UDP
rule.UIDRange = NewRuleUIDRange(100, 100)
rule.Protocol = unix.RTPROT_KERNEL
if err := RuleAdd(rule); err != nil {
t.Fatal(err)
}
rules, err := RuleList(FAMILY_V4)
if err != nil {
t.Fatal(err)
}
if len(rules) != len(rulesBegin)+1 {
t.Fatal("Rule not added properly")
}
// find this rule
found := ruleExists(rules, *rule)
if !found {
t.Fatal("Rule has diffrent options than one added")
}
if err := RuleDel(rule); err != nil {
t.Fatal(err)
}
rulesEnd, err := RuleList(FAMILY_V4)
if err != nil {
t.Fatal(err)
}
if len(rulesEnd) != len(rulesBegin) {
t.Fatal("Rule not removed properly")
}
}
func TestRuleListFiltered(t *testing.T) {
skipUnlessRoot(t)
defer setUpNetlinkTest(t)()
t.Run("IPv4", testRuleListFilteredIPv4)
t.Run("IPv6", testRuleListFilteredIPv6)
}
func testRuleListFilteredIPv4(t *testing.T) {
srcNet := &net.IPNet{IP: net.IPv4(172, 16, 0, 1), Mask: net.CIDRMask(16, 32)}
dstNet := &net.IPNet{IP: net.IPv4(172, 16, 1, 1), Mask: net.CIDRMask(24, 32)}
runRuleListFiltered(t, FAMILY_V4, srcNet, dstNet)
}
func testRuleListFilteredIPv6(t *testing.T) {
ip1 := net.ParseIP("fd56:6b58:db28:2913::")
ip2 := net.ParseIP("fde9:379f:3b35:6635::")
srcNet := &net.IPNet{IP: ip1, Mask: net.CIDRMask(64, 128)}
dstNet := &net.IPNet{IP: ip2, Mask: net.CIDRMask(96, 128)}
runRuleListFiltered(t, FAMILY_V6, srcNet, dstNet)
}
func runRuleListFiltered(t *testing.T, family int, srcNet, dstNet *net.IPNet) {
defaultRules, _ := RuleList(family)
tests := []struct {
name string
ruleFilter *Rule
filterMask uint64
preRun func() *Rule // Creates sample rule harness
postRun func(*Rule) // Deletes sample rule harness
setupWant func(*Rule) ([]Rule, bool)
}{
{
name: "returns all rules",
ruleFilter: nil,
filterMask: 0,
preRun: func() *Rule { return nil },
postRun: func(r *Rule) {},
setupWant: func(_ *Rule) ([]Rule, bool) {
return defaultRules, false
},
},
{
name: "returns one rule filtered by Src",
ruleFilter: &Rule{Src: srcNet},
filterMask: RT_FILTER_SRC,
preRun: func() *Rule {
r := NewRule()
r.Src = srcNet
r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
r.Table = 1
RuleAdd(r)
return r
},
postRun: func(r *Rule) { RuleDel(r) },
setupWant: func(r *Rule) ([]Rule, bool) {
return []Rule{*r}, false
},
},
{
name: "returns one rule filtered by Dst",
ruleFilter: &Rule{Dst: dstNet},
filterMask: RT_FILTER_DST,
preRun: func() *Rule {
r := NewRule()
r.Dst = dstNet
r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
r.Table = 1
RuleAdd(r)
return r
},
postRun: func(r *Rule) { RuleDel(r) },
setupWant: func(r *Rule) ([]Rule, bool) {
return []Rule{*r}, false
},
},
{
name: "returns two rules filtered by Dst",
ruleFilter: &Rule{Dst: dstNet},
filterMask: RT_FILTER_DST,
preRun: func() *Rule {
r := NewRule()
r.Dst = dstNet
r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
r.Table = 1
RuleAdd(r)
rc := *r // Create almost identical copy
rc.Src = srcNet
RuleAdd(&rc)
return r
},
postRun: func(r *Rule) {
RuleDel(r)
rc := *r // Delete the almost identical copy
rc.Src = srcNet
RuleDel(&rc)
},
setupWant: func(r *Rule) ([]Rule, bool) {
rs := []Rule{}
rs = append(rs, *r)
rc := *r // Append the almost identical copy
rc.Src = srcNet
rs = append(rs, rc)
return rs, false
},
},
{
name: "returns one rule filtered by Src when two rules exist",
ruleFilter: &Rule{Src: srcNet},
filterMask: RT_FILTER_SRC,
preRun: func() *Rule {
r := NewRule()
r.Dst = dstNet
r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
r.Table = 1
RuleAdd(r)
rc := *r // Create almost identical copy
rc.Src = srcNet
RuleAdd(&rc)
return r
},
postRun: func(r *Rule) {
RuleDel(r)
rc := *r // Delete the almost identical copy
rc.Src = srcNet
RuleDel(&rc)
},
setupWant: func(r *Rule) ([]Rule, bool) {
rs := []Rule{}
// Do not append `r`
rc := *r // Append the almost identical copy
rc.Src = srcNet
rs = append(rs, rc)
return rs, false
},
},
{
name: "returns rules with specific priority",
ruleFilter: &Rule{Priority: 5},
filterMask: RT_FILTER_PRIORITY,
preRun: func() *Rule {
r := NewRule()
r.Src = srcNet
r.Priority = 5
r.Table = 1
RuleAdd(r)
for i := 2; i < 5; i++ {
rc := *r // Create almost identical copy
rc.Table = i
RuleAdd(&rc)
}
return r
},
postRun: func(r *Rule) {
RuleDel(r)
for i := 2; i < 5; i++ {
rc := *r // Delete the almost identical copy
rc.Table = -1
RuleDel(&rc)
}
},
setupWant: func(r *Rule) ([]Rule, bool) {
rs := []Rule{}
rs = append(rs, *r)
for i := 2; i < 5; i++ {
rc := *r // Append the almost identical copy
rc.Table = i
rs = append(rs, rc)
}
return rs, false
},
},
{
name: "returns rules filtered by Table",
ruleFilter: &Rule{Table: 199},
filterMask: RT_FILTER_TABLE,
preRun: func() *Rule {
r := NewRule()
r.Src = srcNet
r.Priority = 1 // Must add priority otherwise it's auto-assigned
r.Table = 199
RuleAdd(r)
return r
},
postRun: func(r *Rule) { RuleDel(r) },
setupWant: func(r *Rule) ([]Rule, bool) {
return []Rule{*r}, false
},
},
{
name: "returns rules filtered by Mask",
ruleFilter: &Rule{Mask: 0x5},
filterMask: RT_FILTER_MASK,
preRun: func() *Rule {
r := NewRule()
r.Src = srcNet
r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
r.Table = 1
r.Mask = 0x5
RuleAdd(r)
return r
},
postRun: func(r *Rule) { RuleDel(r) },
setupWant: func(r *Rule) ([]Rule, bool) {
return []Rule{*r}, false
},
},
{
name: "returns rules filtered by Mark",
ruleFilter: &Rule{Mark: 0xbb},
filterMask: RT_FILTER_MARK,
preRun: func() *Rule {
r := NewRule()
r.Src = srcNet
r.Priority = 1 // Must add priority, table, mask otherwise it's auto-assigned
r.Table = 1
r.Mask = 0xff
r.Mark = 0xbb
RuleAdd(r)
return r
},
postRun: func(r *Rule) { RuleDel(r) },
setupWant: func(r *Rule) ([]Rule, bool) {
return []Rule{*r}, false
},
},
{
name: "returns rules filtered by Tos",
ruleFilter: &Rule{Tos: 12},
filterMask: RT_FILTER_TOS,
preRun: func() *Rule {
r := NewRule()
r.Src = srcNet
r.Priority = 1 // Must add priority, table, mask otherwise it's auto-assigned
r.Table = 12
r.Tos = 12 // Tos must equal table
RuleAdd(r)
return r
},
postRun: func(r *Rule) { RuleDel(r) },
setupWant: func(r *Rule) ([]Rule, bool) {
return []Rule{*r}, false
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rule := tt.preRun()
rules, err := RuleListFiltered(family, tt.ruleFilter, tt.filterMask)
tt.postRun(rule)
wantRules, wantErr := tt.setupWant(rule)
if len(wantRules) != len(rules) {
t.Errorf("Expected len: %d, got: %d", len(wantRules), len(rules))
} else {
for i := range wantRules {
if !ruleEquals(wantRules[i], rules[i]) {
t.Errorf("Rules mismatch, want %v, got %v", wantRules[i], rules[i])
}
}
}
if (err != nil) != wantErr {
t.Errorf("Error expectation not met, want %v, got %v", (err != nil), wantErr)
}
})
}
}
func TestRuleString(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
r Rule
s string
}{
"empty rule": {
s: "ip rule 0: from all to all table 0",
},
"rule with src and dst equivalent to <nil>": {
r: Rule{
Priority: 100,
Src: &net.IPNet{IP: net.IPv4(10, 0, 0, 0)},
Dst: &net.IPNet{IP: net.IPv4(20, 0, 0, 0)},
Table: 99,
},
s: "ip rule 100: from all to all table 99",
},
"rule with src and dst": {
r: Rule{
Priority: 100,
Src: &net.IPNet{IP: net.IPv4(10, 0, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)},
Dst: &net.IPNet{IP: net.IPv4(20, 0, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)},
Table: 99,
},
s: "ip rule 100: from 10.0.0.0/24 to 20.0.0.0/24 table 99",
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
s := testCase.r.String()
if s != testCase.s {
t.Errorf("expected %q but got %q", testCase.s, s)
}
})
}
}
func ruleExists(rules []Rule, rule Rule) bool {
for i := range rules {
if ruleEquals(rules[i], rule) {
return true
}
}
return false
}
func ruleEquals(a, b Rule) bool {
return a.Table == b.Table &&
((a.Src == nil && b.Src == nil) ||
(a.Src != nil && b.Src != nil && a.Src.String() == b.Src.String())) &&
((a.Dst == nil && b.Dst == nil) ||
(a.Dst != nil && b.Dst != nil && a.Dst.String() == b.Dst.String())) &&
a.OifName == b.OifName &&
a.Priority == b.Priority &&
a.IifName == b.IifName &&
a.Invert == b.Invert &&
a.Tos == b.Tos &&
a.IPProto == b.IPProto &&
a.Protocol == b.Protocol
}