|
|
@@ -1,166 +1,198 @@
|
|
|
package firewall
|
|
|
|
|
|
import (
|
|
|
+ "fmt"
|
|
|
"math"
|
|
|
"net/netip"
|
|
|
"testing"
|
|
|
+ "time"
|
|
|
|
|
|
+ "github.com/slackhq/nebula/cert"
|
|
|
+ "github.com/slackhq/nebula/cert_test"
|
|
|
"github.com/slackhq/nebula/iputil"
|
|
|
"github.com/slackhq/nebula/test"
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
"github.com/stretchr/testify/require"
|
|
|
)
|
|
|
|
|
|
+// rulesAt returns the rules that a packet on proto with the given port is checked against, in id order.
|
|
|
+// port may also be PortAny or PortFragment.
|
|
|
+func (t *Table) rulesAt(proto uint8, port int32) []*rule {
|
|
|
+ pi := t.protos[proto]
|
|
|
+ if pi == nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ p := &Packet{Protocol: proto, Fragment: port == PortFragment}
|
|
|
+ if port > 0 {
|
|
|
+ p.LocalPort = uint16(port)
|
|
|
+ }
|
|
|
+
|
|
|
+ var rules []*rule
|
|
|
+ for id := range pi.whichMatch(p, true).all() {
|
|
|
+ rules = append(rules, &t.rules[id])
|
|
|
+ }
|
|
|
+ return rules
|
|
|
+}
|
|
|
+
|
|
|
+// oneRuleAt returns the single rule that rulesAt finds, and fails the test when there is not exactly one.
|
|
|
+func oneRuleAt(t *testing.T, table *Table, proto uint8, key int32) *rule {
|
|
|
+ t.Helper()
|
|
|
+ rules := table.rulesAt(proto, key)
|
|
|
+ require.Len(t, rules, 1)
|
|
|
+ return rules[0]
|
|
|
+}
|
|
|
+
|
|
|
func TestRulesBuilder_AddRule(t *testing.T) {
|
|
|
l := test.NewLogger()
|
|
|
|
|
|
- ti, err := netip.ParsePrefix("1.2.3.4/32")
|
|
|
- require.NoError(t, err)
|
|
|
-
|
|
|
- ti6, err := netip.ParsePrefix("fd12::34/128")
|
|
|
- require.NoError(t, err)
|
|
|
+ ti := netip.MustParsePrefix("1.2.3.4/32")
|
|
|
+ ti6 := netip.MustParsePrefix("fd12::34/128")
|
|
|
|
|
|
rb := NewRulesBuilder(l)
|
|
|
require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1, 1, []string{}, "", "", "", "", ""))
|
|
|
rules := rb.Build(nil, nil)
|
|
|
// An empty rule is any
|
|
|
- assert.True(t, rules.In.protos[iputil.IPProtocolTCP][1].Any.Any.Any)
|
|
|
- assert.Empty(t, rules.In.protos[iputil.IPProtocolTCP][1].Any.Groups)
|
|
|
- assert.Empty(t, rules.In.protos[iputil.IPProtocolTCP][1].Any.Hosts)
|
|
|
+ r := oneRuleAt(t, rules.In, iputil.IPProtocolTCP, 1)
|
|
|
+ assert.True(t, r.remoteAny)
|
|
|
+ assert.Empty(t, r.groups)
|
|
|
+ assert.Empty(t, r.host)
|
|
|
+ // It applies only to its own port.
|
|
|
+ assert.Empty(t, rules.In.rulesAt(iputil.IPProtocolTCP, 2))
|
|
|
+ assert.Empty(t, rules.In.rulesAt(iputil.IPProtocolTCP, PortAny))
|
|
|
|
|
|
rb = NewRulesBuilder(l)
|
|
|
require.NoError(t, rb.AddRule(true, iputil.IPProtocolUDP, 1, 1, []string{"g1"}, "", "", "", "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.Nil(t, rules.In.protos[iputil.IPProtocolUDP][1].Any.Any)
|
|
|
- assert.Contains(t, rules.In.protos[iputil.IPProtocolUDP][1].Any.Groups[0].Groups, "g1")
|
|
|
- assert.Empty(t, rules.In.protos[iputil.IPProtocolUDP][1].Any.Hosts)
|
|
|
+ r = oneRuleAt(t, rules.In, iputil.IPProtocolUDP, 1)
|
|
|
+ assert.False(t, r.remoteAny)
|
|
|
+ assert.Equal(t, []string{"g1"}, r.groups)
|
|
|
+ assert.Empty(t, r.host)
|
|
|
|
|
|
rb = NewRulesBuilder(l)
|
|
|
require.NoError(t, rb.AddRule(true, iputil.IPProtocolICMP, 1, 1, []string{}, "h1", "", "", "", ""))
|
|
|
require.NoError(t, rb.AddRule(true, iputil.IPProtocolICMPv6, 1, 1, []string{}, "h2", "", "", "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- //no matter what port is given for icmp, it should end up as "any"
|
|
|
- assert.Nil(t, rules.In.protos[iputil.IPProtocolICMP][PortAny].Any.Any)
|
|
|
- assert.Empty(t, rules.In.protos[iputil.IPProtocolICMP][PortAny].Any.Groups)
|
|
|
- assert.Contains(t, rules.In.protos[iputil.IPProtocolICMP][PortAny].Any.Hosts, "h1")
|
|
|
-
|
|
|
- // ICMP and ICMPv6 share one set of rules
|
|
|
- assert.Contains(t, rules.In.protos[iputil.IPProtocolICMP][PortAny].Any.Hosts, "h2")
|
|
|
- assert.Contains(t, rules.In.protos[iputil.IPProtocolICMPv6][PortAny].Any.Hosts, "h1")
|
|
|
+ // An ICMP rule is port `any` regardless of the port given, and ICMP and ICMPv6 share one set of rules.
|
|
|
+ for _, proto := range []uint8{iputil.IPProtocolICMP, iputil.IPProtocolICMPv6} {
|
|
|
+ rs := rules.In.rulesAt(proto, PortAny)
|
|
|
+ require.Len(t, rs, 2, "proto %d", proto)
|
|
|
+ assert.False(t, rs[0].remoteAny)
|
|
|
+ assert.Equal(t, "h1", rs[0].host)
|
|
|
+ assert.Equal(t, "h2", rs[1].host)
|
|
|
+ }
|
|
|
+ assert.Same(t, rules.In.protos[iputil.IPProtocolICMP], rules.In.protos[iputil.IPProtocolICMPv6])
|
|
|
|
|
|
// Any other protocol number gets its own rules
|
|
|
rb = NewRulesBuilder(l)
|
|
|
require.NoError(t, rb.AddRule(true, 47, 0, 0, []string{"g1"}, "", "", "", "", "")) // GRE
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.Contains(t, rules.In.protos[47][PortAny].Any.Groups[0].Groups, "g1")
|
|
|
+ assert.Equal(t, []string{"g1"}, oneRuleAt(t, rules.In, 47, PortAny).groups)
|
|
|
assert.Nil(t, rules.In.protos[iputil.IPProtocolTCP])
|
|
|
|
|
|
+ // A proto `any` rule with a port applies to every protocol with ports, and ProtoAny itself has none.
|
|
|
rb = NewRulesBuilder(l)
|
|
|
require.NoError(t, rb.AddRule(false, ProtoAny, 1, 1, []string{}, "", ti.String(), "", "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.Nil(t, rules.Out.protos[ProtoAny][1].Any.Any)
|
|
|
- require.Len(t, rules.Out.protos[ProtoAny][1].Any.CIDR, 1)
|
|
|
- assert.Equal(t, ti, rules.Out.protos[ProtoAny][1].Any.CIDR[0].Prefix)
|
|
|
+ assert.Empty(t, rules.Out.rulesAt(ProtoAny, 1))
|
|
|
+ r = oneRuleAt(t, rules.Out, iputil.IPProtocolTCP, 1)
|
|
|
+ assert.False(t, r.remoteAny)
|
|
|
+ assert.Equal(t, ti, r.cidr)
|
|
|
|
|
|
rb = NewRulesBuilder(l)
|
|
|
require.NoError(t, rb.AddRule(false, ProtoAny, 1, 1, []string{}, "", ti6.String(), "", "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.Nil(t, rules.Out.protos[ProtoAny][1].Any.Any)
|
|
|
- require.Len(t, rules.Out.protos[ProtoAny][1].Any.CIDR, 1)
|
|
|
- assert.Equal(t, ti6, rules.Out.protos[ProtoAny][1].Any.CIDR[0].Prefix)
|
|
|
-
|
|
|
- // The same cidr written two ways is one entry
|
|
|
- rb = NewRulesBuilder(l)
|
|
|
- require.NoError(t, rb.AddRule(false, ProtoAny, 1, 1, []string{}, "", "1.2.3.4/24", "", "", ""))
|
|
|
- require.NoError(t, rb.AddRule(false, ProtoAny, 1, 1, []string{}, "", "1.2.3.0/24", "", "", ""))
|
|
|
- rules = rb.Build(nil, nil)
|
|
|
- require.Len(t, rules.Out.protos[ProtoAny][1].Any.CIDR, 1)
|
|
|
- assert.Equal(t, netip.MustParsePrefix("1.2.3.0/24"), rules.Out.protos[ProtoAny][1].Any.CIDR[0].Prefix)
|
|
|
+ r = oneRuleAt(t, rules.Out, iputil.IPProtocolTCP, 1)
|
|
|
+ assert.False(t, r.remoteAny)
|
|
|
+ assert.Equal(t, ti6, r.cidr)
|
|
|
|
|
|
rb = NewRulesBuilder(l)
|
|
|
require.NoError(t, rb.AddRule(false, ProtoAny, 1, 1, []string{}, "", "", ti.String(), "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.NotNil(t, rules.Out.protos[ProtoAny][1].Any.Any)
|
|
|
- ok := rules.Out.protos[ProtoAny][1].Any.Any.LocalCIDR.Get(ti)
|
|
|
- assert.True(t, ok)
|
|
|
+ r = oneRuleAt(t, rules.Out, iputil.IPProtocolTCP, 1)
|
|
|
+ assert.True(t, r.remoteAny)
|
|
|
+ assert.False(t, r.localAny)
|
|
|
+ assert.Equal(t, []netip.Prefix{ti}, r.local)
|
|
|
|
|
|
rb = NewRulesBuilder(l)
|
|
|
require.NoError(t, rb.AddRule(false, ProtoAny, 1, 1, []string{}, "", "", ti6.String(), "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.NotNil(t, rules.Out.protos[ProtoAny][1].Any.Any)
|
|
|
- ok = rules.Out.protos[ProtoAny][1].Any.Any.LocalCIDR.Get(ti6)
|
|
|
- assert.True(t, ok)
|
|
|
+ r = oneRuleAt(t, rules.Out, iputil.IPProtocolTCP, 1)
|
|
|
+ assert.True(t, r.remoteAny)
|
|
|
+ assert.False(t, r.localAny)
|
|
|
+ assert.Equal(t, []netip.Prefix{ti6}, r.local)
|
|
|
|
|
|
rb = NewRulesBuilder(l)
|
|
|
require.NoError(t, rb.AddRule(true, iputil.IPProtocolUDP, 1, 1, []string{"g1"}, "", "", "", "ca-name", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.Contains(t, rules.In.protos[iputil.IPProtocolUDP][1].CANames, "ca-name")
|
|
|
+ assert.Equal(t, "ca-name", oneRuleAt(t, rules.In, iputil.IPProtocolUDP, 1).caName)
|
|
|
|
|
|
rb = NewRulesBuilder(l)
|
|
|
require.NoError(t, rb.AddRule(true, iputil.IPProtocolUDP, 1, 1, []string{"g1"}, "", "", "", "", "ca-sha"))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.Contains(t, rules.In.protos[iputil.IPProtocolUDP][1].CAShas, "ca-sha")
|
|
|
+ assert.Equal(t, "ca-sha", oneRuleAt(t, rules.In, iputil.IPProtocolUDP, 1).caSha)
|
|
|
|
|
|
rb = NewRulesBuilder(l)
|
|
|
require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "any", "", "", "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.True(t, rules.Out.protos[ProtoAny][0].Any.Any.Any)
|
|
|
+ assert.True(t, oneRuleAt(t, rules.Out, ProtoAny, 0).remoteAny)
|
|
|
|
|
|
+ // 0.0.0.0/0 allows any IPv4 address, but no IPv6 address.
|
|
|
rb = NewRulesBuilder(l)
|
|
|
- anyIp, err := netip.ParsePrefix("0.0.0.0/0")
|
|
|
- require.NoError(t, err)
|
|
|
-
|
|
|
- // 0.0.0.0/0 is any IPv4 address, not any IPv6 address
|
|
|
- require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", anyIp.String(), "", "", ""))
|
|
|
+ require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "0.0.0.0/0", "", "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.Nil(t, rules.Out.protos[ProtoAny][0].Any.Any)
|
|
|
- assert.True(t, rules.Out.protos[ProtoAny][0].Any.match(&Packet{RemoteAddr: netip.MustParseAddr("1.1.1.1")}, nil))
|
|
|
- assert.False(t, rules.Out.protos[ProtoAny][0].Any.match(&Packet{RemoteAddr: netip.MustParseAddr("9::9")}, nil))
|
|
|
+ r = oneRuleAt(t, rules.Out, ProtoAny, 0)
|
|
|
+ assert.False(t, r.remoteAny)
|
|
|
+ assert.True(t, r.matchRemote(netip.MustParseAddr("1.1.1.1"), nil))
|
|
|
+ assert.False(t, r.matchRemote(netip.MustParseAddr("9::9"), nil))
|
|
|
|
|
|
+ // ::/0 allows any IPv6 address, but no IPv4 address.
|
|
|
rb = NewRulesBuilder(l)
|
|
|
- anyIp6, err := netip.ParsePrefix("::/0")
|
|
|
- require.NoError(t, err)
|
|
|
-
|
|
|
- // ::/0 is any IPv6 address, not any IPv4 address
|
|
|
- require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", anyIp6.String(), "", "", ""))
|
|
|
+ require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "::/0", "", "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.Nil(t, rules.Out.protos[ProtoAny][0].Any.Any)
|
|
|
- assert.True(t, rules.Out.protos[ProtoAny][0].Any.match(&Packet{RemoteAddr: netip.MustParseAddr("9::9")}, nil))
|
|
|
- assert.False(t, rules.Out.protos[ProtoAny][0].Any.match(&Packet{RemoteAddr: netip.MustParseAddr("1.1.1.1")}, nil))
|
|
|
+ r = oneRuleAt(t, rules.Out, ProtoAny, 0)
|
|
|
+ assert.False(t, r.remoteAny)
|
|
|
+ assert.True(t, r.matchRemote(netip.MustParseAddr("9::9"), nil))
|
|
|
+ assert.False(t, r.matchRemote(netip.MustParseAddr("1.1.1.1"), nil))
|
|
|
|
|
|
rb = NewRulesBuilder(l)
|
|
|
require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "any", "", "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.True(t, rules.Out.protos[ProtoAny][0].Any.Any.Any)
|
|
|
+ assert.True(t, oneRuleAt(t, rules.Out, ProtoAny, 0).remoteAny)
|
|
|
|
|
|
+ // A local cidr is likewise limited to its own address family.
|
|
|
rb = NewRulesBuilder(l)
|
|
|
- require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "", anyIp.String(), "", ""))
|
|
|
+ require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "", "0.0.0.0/0", "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.False(t, rules.Out.protos[ProtoAny][0].Any.Any.Any)
|
|
|
- assert.True(t, rules.Out.protos[ProtoAny][0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("1.1.1.1")))
|
|
|
- assert.False(t, rules.Out.protos[ProtoAny][0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("9::9")))
|
|
|
+ r = oneRuleAt(t, rules.Out, ProtoAny, 0)
|
|
|
+ assert.False(t, r.localAny)
|
|
|
+ assert.True(t, r.matchLocal(netip.MustParseAddr("1.1.1.1")))
|
|
|
+ assert.False(t, r.matchLocal(netip.MustParseAddr("9::9")))
|
|
|
|
|
|
rb = NewRulesBuilder(l)
|
|
|
- require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "", anyIp6.String(), "", ""))
|
|
|
+ require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "", "::/0", "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.False(t, rules.Out.protos[ProtoAny][0].Any.Any.Any)
|
|
|
- assert.True(t, rules.Out.protos[ProtoAny][0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("9::9")))
|
|
|
- assert.False(t, rules.Out.protos[ProtoAny][0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("1.1.1.1")))
|
|
|
+ r = oneRuleAt(t, rules.Out, ProtoAny, 0)
|
|
|
+ assert.False(t, r.localAny)
|
|
|
+ assert.True(t, r.matchLocal(netip.MustParseAddr("9::9")))
|
|
|
+ assert.False(t, r.matchLocal(netip.MustParseAddr("1.1.1.1")))
|
|
|
|
|
|
rb = NewRulesBuilder(l)
|
|
|
require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "", "any", "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.True(t, rules.Out.protos[ProtoAny][0].Any.Any.Any)
|
|
|
+ assert.True(t, oneRuleAt(t, rules.Out, ProtoAny, 0).localAny)
|
|
|
|
|
|
// Every protocol number is accepted
|
|
|
rb = NewRulesBuilder(l)
|
|
|
require.NoError(t, rb.AddRule(true, math.MaxUint8, 0, 0, []string{}, "", "", "", "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.True(t, rules.In.protos[math.MaxUint8][PortAny].Any.Any.Any)
|
|
|
+ assert.True(t, oneRuleAt(t, rules.In, math.MaxUint8, PortAny).remoteAny)
|
|
|
|
|
|
// Test error conditions, a bad rule is reported when it's added
|
|
|
rb = NewRulesBuilder(l)
|
|
|
require.Error(t, rb.AddRule(true, ProtoAny, 10, 0, []string{}, "", "", "", "", ""))
|
|
|
+ require.Error(t, rb.AddRule(true, ProtoAny, PortFragment-1, 0, []string{}, "", "", "", "", ""))
|
|
|
+ require.Error(t, rb.AddRule(true, ProtoAny, 1, math.MaxUint16+1, []string{}, "", "", "", "", ""))
|
|
|
require.Error(t, rb.AddRule(true, ProtoAny, 0, 0, []string{}, "", "junk", "", "", ""))
|
|
|
require.Error(t, rb.AddRule(true, ProtoAny, 0, 0, []string{}, "", "", "junk", "", ""))
|
|
|
|
|
|
@@ -169,8 +201,9 @@ func TestRulesBuilder_AddRule(t *testing.T) {
|
|
|
require.NoError(t, rb.AddRule(true, ProtoAny, PortAny, PortAny, []string{"default-group"}, "", "", "", "", ""))
|
|
|
require.NoError(t, rb.AddRule(true, iputil.IPProtocolICMPv6, PortAny, PortAny, []string{"nope"}, "", "", "", "", ""))
|
|
|
rules = rb.Build(nil, nil)
|
|
|
- assert.Len(t, rules.In.protos[iputil.IPProtocolICMP][PortAny].Any.Groups, 2)
|
|
|
- assert.Len(t, rules.In.protos[iputil.IPProtocolICMPv6][PortAny].Any.Groups, 2)
|
|
|
+ assert.Len(t, rules.In.rules, 2)
|
|
|
+ assert.Len(t, rules.In.rulesAt(iputil.IPProtocolICMP, PortAny), 2)
|
|
|
+ assert.Len(t, rules.In.rulesAt(iputil.IPProtocolICMPv6, PortAny), 2)
|
|
|
|
|
|
// No rules allow nothing
|
|
|
rules = NewRulesBuilder(l).Build(nil, nil)
|
|
|
@@ -180,10 +213,84 @@ func TestRulesBuilder_AddRule(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// TestRulesBuilder_sharedLocalCIDR ensures local cidrs shared between rules and ports are copied, not changed,
|
|
|
-// when another rule's local cidrs are added to them. The prefixes share their first octet so the copy has to reach
|
|
|
-// past the root of the shared table.
|
|
|
-func TestRulesBuilder_sharedLocalCIDR(t *testing.T) {
|
|
|
+// TestRulesBuilder_protoIndex checks which packets each kind of port clause covers, and which protocols
|
|
|
+// share an index.
|
|
|
+func TestRulesBuilder_protoIndex(t *testing.T) {
|
|
|
+ rb := NewRulesBuilder(test.NewLogger())
|
|
|
+ require.NoError(t, rb.AddRule(true, ProtoAny, PortAny, PortAny, nil, "any", "", "", "", ""))
|
|
|
+ require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 22, 22, nil, "port", "", "", "", ""))
|
|
|
+ require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1024, math.MaxUint16, nil, "range", "", "", "", ""))
|
|
|
+ require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, PortFragment, PortFragment, nil, "fragment", "", "", "", ""))
|
|
|
+ // A range that includes port 0 is port `any`.
|
|
|
+ require.NoError(t, rb.AddRule(true, iputil.IPProtocolUDP, 0, 100, nil, "zero", "", "", "", ""))
|
|
|
+ // GRE has no ports, so a port rule on it never applies.
|
|
|
+ require.NoError(t, rb.AddRule(true, 47, 22, 22, nil, "gre", "", "", "", ""))
|
|
|
+ rules := rb.Build(nil, nil)
|
|
|
+
|
|
|
+ hosts := func(proto uint8, key int32) []string {
|
|
|
+ var hosts []string
|
|
|
+ for _, r := range rules.In.rulesAt(proto, key) {
|
|
|
+ hosts = append(hosts, r.host)
|
|
|
+ }
|
|
|
+ return hosts
|
|
|
+ }
|
|
|
+
|
|
|
+ assert.Equal(t, []string{"any", "fragment"}, hosts(iputil.IPProtocolTCP, PortFragment))
|
|
|
+ assert.Equal(t, []string{"any"}, hosts(iputil.IPProtocolTCP, PortAny))
|
|
|
+ assert.Equal(t, []string{"any"}, hosts(iputil.IPProtocolTCP, 21))
|
|
|
+ assert.Equal(t, []string{"any", "port"}, hosts(iputil.IPProtocolTCP, 22))
|
|
|
+ assert.Equal(t, []string{"any"}, hosts(iputil.IPProtocolTCP, 23))
|
|
|
+ assert.Equal(t, []string{"any"}, hosts(iputil.IPProtocolTCP, 1023))
|
|
|
+ assert.Equal(t, []string{"any", "range"}, hosts(iputil.IPProtocolTCP, 1024))
|
|
|
+ assert.Equal(t, []string{"any", "range"}, hosts(iputil.IPProtocolTCP, math.MaxUint16))
|
|
|
+
|
|
|
+ assert.Equal(t, []string{"any", "zero"}, hosts(iputil.IPProtocolUDP, PortFragment))
|
|
|
+ assert.Equal(t, []string{"any", "zero"}, hosts(iputil.IPProtocolUDP, PortAny))
|
|
|
+ assert.Equal(t, []string{"any", "zero"}, hosts(iputil.IPProtocolUDP, math.MaxUint16))
|
|
|
+
|
|
|
+ // A protocol without ports sees only the port `any` rules, and the port `fragment` rules for a fragment.
|
|
|
+ assert.False(t, rules.In.protos[47].hasPorts)
|
|
|
+ assert.Equal(t, []string{"any"}, hosts(47, PortAny))
|
|
|
+ assert.Equal(t, []string{"any"}, hosts(47, 22))
|
|
|
+ assert.Equal(t, []string{"any"}, hosts(47, PortFragment))
|
|
|
+ assert.Equal(t, []string{"any"}, hosts(iputil.IPProtocolICMP, PortFragment))
|
|
|
+
|
|
|
+ // Protocols without rules of their own share an index of the proto `any` rules: one for protocols with
|
|
|
+ // ports and one for protocols without.
|
|
|
+ assert.True(t, rules.In.protos[iputil.IPProtocolSCTP].hasPorts)
|
|
|
+ assert.Equal(t, []string{"any"}, hosts(iputil.IPProtocolSCTP, 5000))
|
|
|
+ assert.Same(t, rules.In.protos[iputil.IPProtocolSCTP], rules.In.protos[iputil.IPProtocolDCCP])
|
|
|
+ assert.NotSame(t, rules.In.protos[iputil.IPProtocolSCTP], rules.In.protos[iputil.IPProtocolTCP])
|
|
|
+ assert.Equal(t, []string{"any"}, hosts(50, PortAny))
|
|
|
+ assert.Same(t, rules.In.protos[ProtoAny], rules.In.protos[50])
|
|
|
+ assert.Same(t, rules.In.protos[ProtoAny], rules.In.protos[iputil.IPProtocolICMP])
|
|
|
+ assert.NotSame(t, rules.In.protos[ProtoAny], rules.In.protos[iputil.IPProtocolSCTP])
|
|
|
+ assert.Nil(t, rules.Out.protos[iputil.IPProtocolTCP])
|
|
|
+}
|
|
|
+
|
|
|
+// TestRulesBuilder_manyRules checks that rule ids beyond the first 64 are indexed correctly.
|
|
|
+func TestRulesBuilder_manyRules(t *testing.T) {
|
|
|
+ rb := NewRulesBuilder(test.NewLogger())
|
|
|
+ const n = 130
|
|
|
+ for i := range n {
|
|
|
+ require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, int32(i+1), int32(i+1), nil, fmt.Sprintf("h%d", i), "", "", "", ""))
|
|
|
+ }
|
|
|
+ require.NoError(t, rb.AddRule(true, ProtoAny, PortAny, PortAny, nil, "any", "", "", "", ""))
|
|
|
+ rules := rb.Build(nil, nil)
|
|
|
+
|
|
|
+ assert.Equal(t, 3, rules.In.protos[iputil.IPProtocolTCP].byPort.setLen)
|
|
|
+ for i := range n {
|
|
|
+ rs := rules.In.rulesAt(iputil.IPProtocolTCP, int32(i+1))
|
|
|
+ require.Len(t, rs, 2, "port %d", i+1)
|
|
|
+ assert.Equal(t, "any", rs[0].host)
|
|
|
+ assert.Equal(t, fmt.Sprintf("h%d", i), rs[1].host)
|
|
|
+ }
|
|
|
+ assert.Len(t, rules.In.rulesAt(iputil.IPProtocolTCP, n+1), 1)
|
|
|
+}
|
|
|
+
|
|
|
+// TestRulesBuilder_defaultLocalCIDR checks which local addresses rules allow when there are unsafe networks.
|
|
|
+// A rule without a local cidr allows only the vpn networks, and a rule with one allows only that cidr.
|
|
|
+func TestRulesBuilder_defaultLocalCIDR(t *testing.T) {
|
|
|
vpnNetworks := []netip.Prefix{
|
|
|
netip.MustParsePrefix("10.1.0.5/16"),
|
|
|
netip.MustParsePrefix("10.2.0.5/16"),
|
|
|
@@ -209,8 +316,19 @@ func TestRulesBuilder_sharedLocalCIDR(t *testing.T) {
|
|
|
require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 3, 3, nil, "h3", "", "", "", ""))
|
|
|
rules := rb.Build(vpnNetworks, unsafeNetworks)
|
|
|
|
|
|
+ ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil)
|
|
|
+ caPool := cert.NewCAPool()
|
|
|
+ require.NoError(t, caPool.AddCA(ca))
|
|
|
+ certs := map[string]*cert.CachedCertificate{}
|
|
|
+ for _, name := range []string{"h1", "h2", "h3"} {
|
|
|
+ crt, _, _, _ := cert_test.NewTestCert(cert.Version2, cert.Curve_CURVE25519, ca, caKey, name, time.Time{}, time.Time{}, []netip.Prefix{netip.MustParsePrefix("10.1.0.9/16")}, nil, nil)
|
|
|
+ c, err := caPool.VerifyCertificate(time.Now(), crt)
|
|
|
+ require.NoError(t, err)
|
|
|
+ certs[name] = c
|
|
|
+ }
|
|
|
+
|
|
|
for _, tc := range []struct {
|
|
|
- port int32
|
|
|
+ port uint16
|
|
|
host string
|
|
|
addr netip.Addr
|
|
|
want bool
|
|
|
@@ -238,8 +356,10 @@ func TestRulesBuilder_sharedLocalCIDR(t *testing.T) {
|
|
|
{3, "h3", h1Extra, false},
|
|
|
{3, "h3", h1Extra6, false},
|
|
|
{3, "h3", h2Own, false},
|
|
|
+ // Only h3 is allowed on port 3.
|
|
|
+ {3, "h1", vpn1, false},
|
|
|
} {
|
|
|
- lr := rules.In.protos[iputil.IPProtocolTCP][tc.port].Any.Hosts[tc.host]
|
|
|
- assert.Equal(t, tc.want, lr.match(&Packet{LocalAddr: tc.addr}, nil), "port %d, %s, %s", tc.port, tc.host, tc.addr)
|
|
|
+ p := &Packet{Protocol: iputil.IPProtocolTCP, LocalPort: tc.port, LocalAddr: tc.addr, RemoteAddr: netip.MustParseAddr("10.1.0.9")}
|
|
|
+ assert.Equal(t, tc.want, rules.In.Match(p, true, certs[tc.host], caPool), "port %d, %s, %s", tc.port, tc.host, tc.addr)
|
|
|
}
|
|
|
}
|