This commit is contained in:
JackDoan committed 2026-09-30 15:06:35 -05:00
1 parent e99e48cca7
commit ed8ae57fee
4 files changed
+49 -64

No files matched your search

+3 -6
View File
@@ -84,13 +84,10 @@ func addRulesFromConfig(l *slog.Logger, inbound bool, c *config.C, ra ruleAdder)
}
proto = uint8(n)
}
// ICMPv6 rules are ICMP rules, see RulesBuilder.AddRule
if proto == iputil.IPProtocolICMPv6 {
proto = iputil.IPProtocolICMP
}
var startPort, endPort int32
if proto == iputil.IPProtocolICMP {
if proto == iputil.IPProtocolICMP || proto == iputil.IPProtocolICMPv6 {
// RulesBuilder.AddRule coerces ICMP ports to `any` as well. This copy stays so that an ICMP rule's
// port is warned about rather than parsed, whatever it holds.
startPort = PortAny
endPort = PortAny
if sPort != "" {
+13 -5
View File
@@ -170,13 +170,21 @@ func TestAddRulesFromConfig(t *testing.T) {
assert.Equal(t, addRuleCall{incoming: false, proto: 47, startPort: PortAny, endPort: PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall, proto)
}
// Test ICMP and ICMPv6, by name and by number, still ignore ports. An ICMPv6 rule is added as an ICMP rule.
for _, proto := range []any{"icmp", "icmpv6", iputil.IPProtocolICMP, iputil.IPProtocolICMPv6} {
// Test ICMP and ICMPv6, by name and by number, still ignore ports
for _, tc := range []struct {
given any
want uint8
}{
{"icmp", iputil.IPProtocolICMP},
{"icmpv6", iputil.IPProtocolICMPv6},
{iputil.IPProtocolICMP, iputil.IPProtocolICMP},
{iputil.IPProtocolICMPv6, iputil.IPProtocolICMPv6},
} {
conf = config.NewC(test.NewLogger())
mf = &mockFirewall{}
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": proto, "host": "a"}}}
require.NoError(t, addRulesFromConfig(l, false, conf, mf), proto)
assert.Equal(t, addRuleCall{incoming: false, proto: iputil.IPProtocolICMP, startPort: PortAny, endPort: PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall, proto)
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": tc.given, "host": "a"}}}
require.NoError(t, addRulesFromConfig(l, false, conf, mf), tc.given)
assert.Equal(t, addRuleCall{incoming: false, proto: tc.want, startPort: PortAny, endPort: PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall, tc.given)
}
// Test protocols that aren't a known name or a number from 1 to 255
+25 -33
View File
@@ -4,40 +4,32 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"hash/fnv"
"io"
"log/slog"
"math"
"net/netip"
"slices"
"strings"
"github.com/slackhq/nebula/iputil"
)
// RulesBuilder collects the inbound and outbound rules, Build turns them into a Table for each direction
type RulesBuilder struct {
in, out tableRules
// in and out are each direction's rules in the order they were added
in, out []rule
// defaultLocalCIDRAny makes rules without a local cidr allow any local address, even with unsafe networks
defaultLocalCIDRAny bool
// ruleSHA and ruleFNV hash every rule as it's added, see Rules
ruleSHA hash.Hash
ruleFNV hash.Hash32
// ruleText describes every rule as it is added, one line each. Build hashes it; see Rules.
ruleText strings.Builder
l *slog.Logger
}
// tableRules are one direction's rules in the order they were added, waiting to be built into a Table
type tableRules []rule
func NewRulesBuilder(l *slog.Logger) *RulesBuilder {
return &RulesBuilder{
ruleSHA: sha256.New(),
ruleFNV: fnv.New32a(),
l: l,
}
return &RulesBuilder{l: l}
}
// AddRule adds a rule for incoming or outgoing traffic, on proto or on every protocol when proto is ProtoAny.
@@ -57,19 +49,19 @@ func (b *RulesBuilder) AddRule(incoming bool, proto uint8, startPort int32, endP
endPort = PortAny
}
r, err := parseRule(startPort, endPort, groups, host, cidr, localCidr, caName, caSha)
r, err := parseRule(proto, startPort, endPort, groups, host, cidr, localCidr, caName, caSha)
if err != nil {
return err
}
if incoming {
b.in.add(proto, r)
b.in = append(b.in, r)
} else {
b.out.add(proto, r)
b.out = append(b.out, r)
}
// The rule hashes are of these lines, changing them changes every hash
fmt.Fprintf(io.MultiWriter(b.ruleSHA, b.ruleFNV),
fmt.Fprintf(&b.ruleText,
"incoming: %v, proto: %v, startPort: %v, endPort: %v, groups: %v, host: %v, ip: %v, localIp: %v, caName: %v, caSha: %s\n",
incoming, proto, startPort, endPort, groups, host, cidr, localCidr, caName, caSha,
)
@@ -94,15 +86,20 @@ func (b *RulesBuilder) Build(vpnNetworks, unsafeNetworks []netip.Prefix) Rules {
defaultLocal = append([]netip.Prefix{}, vpnNetworks...)
}
text := []byte(b.ruleText.String())
sha := sha256.Sum256(text)
h := fnv.New32a()
h.Write(text)
return Rules{
In: b.in.build(defaultLocal),
Out: b.out.build(defaultLocal),
Hash: hex.EncodeToString(b.ruleSHA.Sum(nil)),
HashFNV: b.ruleFNV.Sum32(),
In: buildTable(b.in, defaultLocal),
Out: buildTable(b.out, defaultLocal),
Hash: hex.EncodeToString(sha[:]),
HashFNV: h.Sum32(),
}
}
func parseRule(startPort, endPort int32, groups []string, host, cidr, localCidr, caName, caSha string) (rule, error) {
func parseRule(proto uint8, startPort, endPort int32, groups []string, host, cidr, localCidr, caName, caSha string) (rule, error) {
if startPort > endPort {
return rule{}, fmt.Errorf("start port was lower than end port")
}
@@ -115,6 +112,7 @@ func parseRule(startPort, endPort int32, groups []string, host, cidr, localCidr,
}
r := rule{
proto: proto,
startPort: startPort,
endPort: endPort,
groups: groups,
@@ -146,18 +144,12 @@ func parseRule(startPort, endPort int32, groups []string, host, cidr, localCidr,
return r, nil
}
// add keeps r for proto, or for every protocol when proto is ProtoAny.
func (tr *tableRules) add(proto uint8, r rule) {
r.proto = proto
*tr = append(*tr, r)
}
// build turns the rules into a Table. Rules without a local cidr allow defaultLocal,
// buildTable turns one direction's rules into a Table. Rules without a local cidr allow defaultLocal,
// or any local address when defaultLocal is nil.
func (tr tableRules) build(defaultLocal []netip.Prefix) *Table {
t := &Table{rules: make([]rule, len(tr))}
func buildTable(rules []rule, defaultLocal []netip.Prefix) *Table {
t := &Table{rules: make([]rule, len(rules))}
var hasOwn [256]bool
for id, r := range tr {
for id, r := range rules {
t.rules[id] = r.withDefaultLocal(defaultLocal)
if r.proto != ProtoAny {
hasOwn[r.proto] = true