Merge branch 'port-range-acl' into userspace-router

This commit is contained in:
Viktor Liu
2025-01-23 18:28:37 +01:00
82 changed files with 3217 additions and 1792 deletions

View File

@@ -16,8 +16,8 @@ type PeerRule struct {
ipLayer gopacket.LayerType
matchByIP bool
protoLayer gopacket.LayerType
sPort uint16
dPort uint16
sPort *firewall.Port
dPort *firewall.Port
drop bool
comment string

View File

@@ -336,13 +336,8 @@ func (m *Manager) AddPeerFiltering(
r.matchByIP = false
}
if sPort != nil && len(sPort.Values) == 1 {
r.sPort = uint16(sPort.Values[0])
}
if dPort != nil && len(dPort.Values) == 1 {
r.dPort = uint16(dPort.Values[0])
}
r.sPort = sPort
r.dPort = dPort
switch proto {
case firewall.ProtocolTCP:
@@ -561,7 +556,7 @@ func (m *Manager) checkUDPHooks(d *decoder, dstIP net.IP, packetData []byte) boo
for _, ipKey := range []string{dstIP.String(), "0.0.0.0", "::"} {
if rules, exists := m.outgoingRules[ipKey]; exists {
for _, rule := range rules {
if rule.udpHook != nil && (rule.dPort == 0 || rule.dPort == uint16(d.udp.DstPort)) {
if rule.udpHook != nil && portsMatch(rule.dPort, uint16(d.udp.DstPort)) {
return rule.udpHook(packetData)
}
}
@@ -786,6 +781,23 @@ func (m *Manager) peerACLsBlock(srcIP net.IP, packetData []byte, rules map[strin
return true
}
func portsMatch(rulePort *firewall.Port, packetPort uint16) bool {
if rulePort == nil {
return true
}
if rulePort.IsRange {
return packetPort >= rulePort.Values[0] && packetPort <= rulePort.Values[1]
}
for _, p := range rulePort.Values {
if p == packetPort {
return true
}
}
return false
}
func validateRule(ip net.IP, packetData []byte, rules map[string]PeerRule, d *decoder) (bool, bool) {
payloadLayer := d.decoded[1]
for _, rule := range rules {
@@ -803,13 +815,7 @@ func validateRule(ip net.IP, packetData []byte, rules map[string]PeerRule, d *de
switch payloadLayer {
case layers.LayerTypeTCP:
if rule.sPort == 0 && rule.dPort == 0 {
return rule.drop, true
}
if rule.sPort != 0 && rule.sPort == uint16(d.tcp.SrcPort) {
return rule.drop, true
}
if rule.dPort != 0 && rule.dPort == uint16(d.tcp.DstPort) {
if portsMatch(rule.sPort, uint16(d.tcp.SrcPort)) && portsMatch(rule.dPort, uint16(d.tcp.DstPort)) {
return rule.drop, true
}
case layers.LayerTypeUDP:
@@ -819,13 +825,7 @@ func validateRule(ip net.IP, packetData []byte, rules map[string]PeerRule, d *de
return rule.udpHook(packetData), true
}
if rule.sPort == 0 && rule.dPort == 0 {
return rule.drop, true
}
if rule.sPort != 0 && rule.sPort == uint16(d.udp.SrcPort) {
return rule.drop, true
}
if rule.dPort != 0 && rule.dPort == uint16(d.udp.DstPort) {
if portsMatch(rule.sPort, uint16(d.udp.SrcPort)) && portsMatch(rule.dPort, uint16(d.udp.DstPort)) {
return rule.drop, true
}
case layers.LayerTypeICMPv4, layers.LayerTypeICMPv6:
@@ -872,7 +872,7 @@ func (m *Manager) ruleMatches(rule RouteRule, srcAddr, dstAddr netip.Addr, proto
}
if proto == firewall.ProtocolTCP || proto == firewall.ProtocolUDP {
if !m.portsMatch(rule.srcPort, srcPort) || !m.portsMatch(rule.dstPort, dstPort) {
if !portsMatch(rule.srcPort, srcPort) || !portsMatch(rule.dstPort, dstPort) {
return false
}
}
@@ -880,31 +880,6 @@ func (m *Manager) ruleMatches(rule RouteRule, srcAddr, dstAddr netip.Addr, proto
return true
}
// Add to uspfilter.go, replace existing portsMatch method
func (m *Manager) portsMatch(rulePort *firewall.Port, packetPort uint16) bool {
if rulePort == nil || len(rulePort.Values) == 0 {
return true
}
if rulePort.IsRange {
if len(rulePort.Values) != 2 {
m.logger.Error("Invalid port range configuration: expected 2 values for range")
return false
}
startPort := rulePort.Values[0]
endPort := rulePort.Values[1]
return int(packetPort) >= startPort && int(packetPort) <= endPort
}
// Handle list of individual ports
for _, p := range rulePort.Values {
if uint16(p) == packetPort {
return true
}
}
return false
}
// SetNetwork of the wireguard interface to which filtering applied
func (m *Manager) SetNetwork(network *net.IPNet) {
m.wgNetwork = network
@@ -920,7 +895,7 @@ func (m *Manager) AddUDPPacketHook(
id: uuid.New().String(),
ip: ip,
protoLayer: layers.LayerTypeUDP,
dPort: dPort,
dPort: &firewall.Port{Values: []uint16{dPort}},
ipLayer: layers.LayerTypeIPv6,
comment: fmt.Sprintf("UDP Hook direction: %v, ip:%v, dport:%d", in, ip, dPort),
udpHook: hook,

View File

@@ -115,8 +115,8 @@ func BenchmarkCoreFiltering(b *testing.B) {
for i := 0; i < 1000; i++ { // Simulate realistic ruleset size
ip := generateRandomIPs(1)[0]
_, err := m.AddPeerFiltering(ip, fw.ProtocolTCP,
&fw.Port{Values: []int{1024 + i}},
&fw.Port{Values: []int{80}},
&fw.Port{Values: []uint16{uint16(1024 + i)}},
&fw.Port{Values: []uint16{80}},
fw.ActionAccept, "", "explicit return")
require.NoError(b, err)
}
@@ -591,7 +591,7 @@ func BenchmarkLongLivedConnections(b *testing.B) {
if sc.rules {
// Single rule to allow all return traffic from port 80
_, err := manager.AddPeerFiltering(net.ParseIP("0.0.0.0"), fw.ProtocolTCP,
&fw.Port{Values: []int{80}},
&fw.Port{Values: []uint16{80}},
nil,
fw.ActionAccept, "", "return traffic")
require.NoError(b, err)
@@ -682,7 +682,7 @@ func BenchmarkShortLivedConnections(b *testing.B) {
if sc.rules {
// Single rule to allow all return traffic from port 80
_, err := manager.AddPeerFiltering(net.ParseIP("0.0.0.0"), fw.ProtocolTCP,
&fw.Port{Values: []int{80}},
&fw.Port{Values: []uint16{80}},
nil,
fw.ActionAccept, "", "return traffic")
require.NoError(b, err)
@@ -800,7 +800,7 @@ func BenchmarkParallelLongLivedConnections(b *testing.B) {
// Setup initial state based on scenario
if sc.rules {
_, err := manager.AddPeerFiltering(net.ParseIP("0.0.0.0"), fw.ProtocolTCP,
&fw.Port{Values: []int{80}},
&fw.Port{Values: []uint16{80}},
nil,
fw.ActionAccept, "", "return traffic")
require.NoError(b, err)
@@ -887,7 +887,7 @@ func BenchmarkParallelShortLivedConnections(b *testing.B) {
if sc.rules {
_, err := manager.AddPeerFiltering(net.ParseIP("0.0.0.0"), fw.ProtocolTCP,
&fw.Port{Values: []int{80}},
&fw.Port{Values: []uint16{80}},
nil,
fw.ActionAccept, "", "return traffic")
require.NoError(b, err)
@@ -1014,7 +1014,7 @@ func BenchmarkRouteACLs(b *testing.B) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
port: &fw.Port{Values: []int{80, 443}},
port: &fw.Port{Values: []uint16{80, 443}},
},
{
sources: []netip.Prefix{
@@ -1028,7 +1028,7 @@ func BenchmarkRouteACLs(b *testing.B) {
sources: []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0")},
dest: netip.MustParsePrefix("192.168.0.0/16"),
proto: fw.ProtocolUDP,
port: &fw.Port{Values: []int{53}},
port: &fw.Port{Values: []uint16{53}},
},
}

View File

@@ -70,7 +70,7 @@ func TestPeerACLFiltering(t *testing.T) {
dstPort: 443,
ruleIP: "100.10.0.1",
ruleProto: fw.ProtocolTCP,
ruleDstPort: &fw.Port{Values: []int{443}},
ruleDstPort: &fw.Port{Values: []uint16{443}},
ruleAction: fw.ActionAccept,
shouldBeBlocked: false,
},
@@ -83,7 +83,7 @@ func TestPeerACLFiltering(t *testing.T) {
dstPort: 53,
ruleIP: "100.10.0.1",
ruleProto: fw.ProtocolUDP,
ruleDstPort: &fw.Port{Values: []int{53}},
ruleDstPort: &fw.Port{Values: []uint16{53}},
ruleAction: fw.ActionAccept,
shouldBeBlocked: false,
},
@@ -118,7 +118,7 @@ func TestPeerACLFiltering(t *testing.T) {
dstPort: 443,
ruleIP: "192.168.1.1",
ruleProto: fw.ProtocolTCP,
ruleDstPort: &fw.Port{Values: []int{443}},
ruleDstPort: &fw.Port{Values: []uint16{443}},
ruleAction: fw.ActionAccept,
shouldBeBlocked: false,
},
@@ -294,7 +294,7 @@ func TestRouteACLFiltering(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{443}},
dstPort: &fw.Port{Values: []uint16{443}},
action: fw.ActionAccept,
},
shouldPass: true,
@@ -310,7 +310,7 @@ func TestRouteACLFiltering(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{443}},
dstPort: &fw.Port{Values: []uint16{443}},
action: fw.ActionAccept,
},
shouldPass: true,
@@ -326,7 +326,7 @@ func TestRouteACLFiltering(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0")},
dest: netip.MustParsePrefix("0.0.0.0/0"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{443}},
dstPort: &fw.Port{Values: []uint16{443}},
action: fw.ActionAccept,
},
shouldPass: true,
@@ -342,7 +342,7 @@ func TestRouteACLFiltering(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolUDP,
dstPort: &fw.Port{Values: []int{53}},
dstPort: &fw.Port{Values: []uint16{53}},
action: fw.ActionAccept,
},
shouldPass: true,
@@ -371,7 +371,7 @@ func TestRouteACLFiltering(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolALL,
dstPort: &fw.Port{Values: []int{80}},
dstPort: &fw.Port{Values: []uint16{80}},
action: fw.ActionAccept,
},
shouldPass: true,
@@ -387,7 +387,7 @@ func TestRouteACLFiltering(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{80}},
dstPort: &fw.Port{Values: []uint16{80}},
action: fw.ActionAccept,
},
shouldPass: false,
@@ -403,7 +403,7 @@ func TestRouteACLFiltering(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{80}},
dstPort: &fw.Port{Values: []uint16{80}},
action: fw.ActionAccept,
},
shouldPass: false,
@@ -419,7 +419,7 @@ func TestRouteACLFiltering(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{80}},
dstPort: &fw.Port{Values: []uint16{80}},
action: fw.ActionAccept,
},
shouldPass: false,
@@ -435,7 +435,7 @@ func TestRouteACLFiltering(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
srcPort: &fw.Port{Values: []int{12345}},
srcPort: &fw.Port{Values: []uint16{12345}},
action: fw.ActionAccept,
},
shouldPass: true,
@@ -454,7 +454,7 @@ func TestRouteACLFiltering(t *testing.T) {
},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{80}},
dstPort: &fw.Port{Values: []uint16{80}},
action: fw.ActionAccept,
},
shouldPass: true,
@@ -483,7 +483,7 @@ func TestRouteACLFiltering(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolALL,
dstPort: &fw.Port{Values: []int{80}},
dstPort: &fw.Port{Values: []uint16{80}},
action: fw.ActionAccept,
},
shouldPass: true,
@@ -503,7 +503,7 @@ func TestRouteACLFiltering(t *testing.T) {
},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{80}},
dstPort: &fw.Port{Values: []uint16{80}},
action: fw.ActionAccept,
},
shouldPass: false,
@@ -519,7 +519,7 @@ func TestRouteACLFiltering(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{80, 8080, 443}},
dstPort: &fw.Port{Values: []uint16{80, 8080, 443}},
action: fw.ActionAccept,
},
shouldPass: true,
@@ -535,7 +535,7 @@ func TestRouteACLFiltering(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
srcPort: &fw.Port{Values: []int{12345, 12346, 12347}},
srcPort: &fw.Port{Values: []uint16{12345, 12346, 12347}},
action: fw.ActionAccept,
},
shouldPass: true,
@@ -551,8 +551,8 @@ func TestRouteACLFiltering(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolALL,
srcPort: &fw.Port{Values: []int{12345}},
dstPort: &fw.Port{Values: []int{80}},
srcPort: &fw.Port{Values: []uint16{12345}},
dstPort: &fw.Port{Values: []uint16{80}},
action: fw.ActionAccept,
},
shouldPass: true,
@@ -570,7 +570,7 @@ func TestRouteACLFiltering(t *testing.T) {
proto: fw.ProtocolTCP,
dstPort: &fw.Port{
IsRange: true,
Values: []int{8000, 8100},
Values: []uint16{8000, 8100},
},
action: fw.ActionAccept,
},
@@ -589,7 +589,7 @@ func TestRouteACLFiltering(t *testing.T) {
proto: fw.ProtocolTCP,
dstPort: &fw.Port{
IsRange: true,
Values: []int{8000, 8100},
Values: []uint16{8000, 8100},
},
action: fw.ActionAccept,
},
@@ -608,7 +608,7 @@ func TestRouteACLFiltering(t *testing.T) {
proto: fw.ProtocolTCP,
srcPort: &fw.Port{
IsRange: true,
Values: []int{32000, 33000},
Values: []uint16{32000, 33000},
},
action: fw.ActionAccept,
},
@@ -627,10 +627,10 @@ func TestRouteACLFiltering(t *testing.T) {
proto: fw.ProtocolTCP,
srcPort: &fw.Port{
IsRange: true,
Values: []int{32000, 33000},
Values: []uint16{32000, 33000},
},
dstPort: &fw.Port{
Values: []int{443},
Values: []uint16{443},
},
action: fw.ActionAccept,
},
@@ -649,7 +649,7 @@ func TestRouteACLFiltering(t *testing.T) {
proto: fw.ProtocolTCP,
dstPort: &fw.Port{
IsRange: true,
Values: []int{8000}, // Invalid: only one value for range
Values: []uint16{8000}, // Invalid: only one value for range
},
action: fw.ActionAccept,
},
@@ -668,7 +668,7 @@ func TestRouteACLFiltering(t *testing.T) {
proto: fw.ProtocolTCP,
dstPort: &fw.Port{
IsRange: true,
Values: []int{8000, 8100},
Values: []uint16{8000, 8100},
},
action: fw.ActionAccept,
},
@@ -687,7 +687,7 @@ func TestRouteACLFiltering(t *testing.T) {
proto: fw.ProtocolUDP,
dstPort: &fw.Port{
IsRange: true,
Values: []int{5060, 5070},
Values: []uint16{5060, 5070},
},
action: fw.ActionAccept,
},
@@ -706,7 +706,7 @@ func TestRouteACLFiltering(t *testing.T) {
proto: fw.ProtocolALL,
dstPort: &fw.Port{
IsRange: true,
Values: []int{8000, 8100},
Values: []uint16{8000, 8100},
},
action: fw.ActionAccept,
},
@@ -723,7 +723,7 @@ func TestRouteACLFiltering(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{443}},
dstPort: &fw.Port{Values: []uint16{443}},
action: fw.ActionDrop,
},
shouldPass: false,
@@ -757,7 +757,7 @@ func TestRouteACLFiltering(t *testing.T) {
},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{80}},
dstPort: &fw.Port{Values: []uint16{80}},
action: fw.ActionDrop,
},
shouldPass: false,
@@ -792,7 +792,6 @@ func TestRouteACLFiltering(t *testing.T) {
}
}
func TestRouteACLOrder(t *testing.T) {
manager := setupRoutedManager(t, "10.10.0.100/16")
@@ -832,7 +831,7 @@ func TestRouteACLOrder(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{80, 443}},
dstPort: &fw.Port{Values: []uint16{80, 443}},
action: fw.ActionAccept,
},
{
@@ -840,7 +839,7 @@ func TestRouteACLOrder(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{443}},
dstPort: &fw.Port{Values: []uint16{443}},
action: fw.ActionDrop,
},
},
@@ -894,7 +893,7 @@ func TestRouteACLOrder(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{443}},
dstPort: &fw.Port{Values: []uint16{443}},
action: fw.ActionDrop,
},
{
@@ -902,7 +901,7 @@ func TestRouteACLOrder(t *testing.T) {
sources: []netip.Prefix{netip.MustParsePrefix("100.10.0.0/16")},
dest: netip.MustParsePrefix("192.168.1.0/24"),
proto: fw.ProtocolTCP,
dstPort: &fw.Port{Values: []int{80}},
dstPort: &fw.Port{Values: []uint16{80}},
action: fw.ActionDrop,
},
},

View File

@@ -90,7 +90,7 @@ func TestManagerAddPeerFiltering(t *testing.T) {
ip := net.ParseIP("192.168.1.1")
proto := fw.ProtocolTCP
port := &fw.Port{Values: []int{80}}
port := &fw.Port{Values: []uint16{80}}
action := fw.ActionDrop
comment := "Test rule"
@@ -124,7 +124,7 @@ func TestManagerDeleteRule(t *testing.T) {
ip := net.ParseIP("192.168.1.1")
proto := fw.ProtocolTCP
port := &fw.Port{Values: []int{80}}
port := &fw.Port{Values: []uint16{80}}
action := fw.ActionDrop
comment := "Test rule 2"
@@ -215,8 +215,8 @@ func TestAddUDPPacketHook(t *testing.T) {
t.Errorf("expected ip %s, got %s", tt.ip, addedRule.ip)
return
}
if tt.dPort != addedRule.dPort {
t.Errorf("expected dPort %d, got %d", tt.dPort, addedRule.dPort)
if tt.dPort != addedRule.dPort.Values[0] {
t.Errorf("expected dPort %d, got %d", tt.dPort, addedRule.dPort.Values[0])
return
}
if layers.LayerTypeUDP != addedRule.protoLayer {
@@ -244,7 +244,7 @@ func TestManagerReset(t *testing.T) {
ip := net.ParseIP("192.168.1.1")
proto := fw.ProtocolTCP
port := &fw.Port{Values: []int{80}}
port := &fw.Port{Values: []uint16{80}}
action := fw.ActionDrop
comment := "Test rule"
@@ -493,7 +493,7 @@ func TestUSPFilterCreatePerformance(t *testing.T) {
ip := net.ParseIP("10.20.0.100")
start := time.Now()
for i := 0; i < testMax; i++ {
port := &fw.Port{Values: []int{1000 + i}}
port := &fw.Port{Values: []uint16{uint16(1000 + i)}}
_, err = manager.AddPeerFiltering(ip, "tcp", nil, port, fw.ActionAccept, "", "accept HTTP traffic")
require.NoError(t, err, "failed to add rule")