Skip to content

Commit

Permalink
Add benchmarks
Browse files Browse the repository at this point in the history
  • Loading branch information
lixmal committed Dec 21, 2024
1 parent 8661ead commit 1306da2
Showing 1 changed file with 261 additions and 0 deletions.
261 changes: 261 additions & 0 deletions client/firewall/uspfilter/uspfilter_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
package uspfilter

import (
"fmt"
"math/rand"
"net"
"os"
"testing"

"github.com/google/gopacket"
"github.com/google/gopacket/layers"

fw "github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/iface/device"
)

// generateRandomIPs generates n different random IPs in the 100.64.0.0/10 range
func generateRandomIPs(n int) []net.IP {
ips := make([]net.IP, n)
seen := make(map[string]bool)

for i := 0; i < n; {
ip := make(net.IP, 4)
ip[0] = 100
ip[1] = byte(64 + rand.Intn(63)) // 64-126
ip[2] = byte(rand.Intn(256))
ip[3] = byte(1 + rand.Intn(254)) // avoid .0 and .255

key := ip.String()
if !seen[key] {
ips[i] = ip
seen[key] = true
i++
}
}
return ips
}

func generatePacket(srcIP, dstIP net.IP, srcPort, dstPort uint16, protocol layers.IPProtocol) []byte {
ipv4 := &layers.IPv4{
TTL: 64,
Version: 4,
SrcIP: srcIP,
DstIP: dstIP,
Protocol: protocol,
}

var transportLayer gopacket.SerializableLayer
switch protocol {
case layers.IPProtocolTCP:
tcp := &layers.TCP{
SrcPort: layers.TCPPort(srcPort),
DstPort: layers.TCPPort(dstPort),
SYN: true,
}
tcp.SetNetworkLayerForChecksum(ipv4)

Check failure on line 56 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Error return value of `tcp.SetNetworkLayerForChecksum` is not checked (errcheck)

Check failure on line 56 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (macos-latest)

Error return value of `tcp.SetNetworkLayerForChecksum` is not checked (errcheck)

Check failure on line 56 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

Error return value of `tcp.SetNetworkLayerForChecksum` is not checked (errcheck)
transportLayer = tcp
case layers.IPProtocolUDP:
udp := &layers.UDP{
SrcPort: layers.UDPPort(srcPort),
DstPort: layers.UDPPort(dstPort),
}
udp.SetNetworkLayerForChecksum(ipv4)

Check failure on line 63 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Error return value of `udp.SetNetworkLayerForChecksum` is not checked (errcheck)

Check failure on line 63 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (macos-latest)

Error return value of `udp.SetNetworkLayerForChecksum` is not checked (errcheck)

Check failure on line 63 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

Error return value of `udp.SetNetworkLayerForChecksum` is not checked (errcheck)
transportLayer = udp
}

buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true}
gopacket.SerializeLayers(buf, opts, ipv4, transportLayer, gopacket.Payload([]byte("test")))

Check failure on line 69 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Error return value of `gopacket.SerializeLayers` is not checked (errcheck)

Check failure on line 69 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (macos-latest)

Error return value of `gopacket.SerializeLayers` is not checked (errcheck)

Check failure on line 69 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

Error return value of `gopacket.SerializeLayers` is not checked (errcheck)
return buf.Bytes()
}

// BenchmarkCoreFiltering focuses on the essential performance comparisons between
// stateful and stateless filtering approaches
func BenchmarkCoreFiltering(b *testing.B) {
scenarios := []struct {
name string
stateful bool
setupFunc func(*Manager)
desc string
}{
{
name: "stateless_single_allow_all",
stateful: false,
setupFunc: func(m *Manager) {
// Single rule allowing all traffic
m.AddPeerFiltering(net.ParseIP("0.0.0.0"), fw.ProtocolALL, nil, nil,

Check failure on line 87 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Error return value of `m.AddPeerFiltering` is not checked (errcheck)

Check failure on line 87 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (macos-latest)

Error return value of `m.AddPeerFiltering` is not checked (errcheck)

Check failure on line 87 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

Error return value of `m.AddPeerFiltering` is not checked (errcheck)
fw.RuleDirectionIN, fw.ActionAccept, "", "allow all")
},
desc: "Baseline: Single 'allow all' rule without connection tracking",
},
{
name: "stateful_no_rules",
stateful: true,
setupFunc: func(m *Manager) {
// No explicit rules - rely purely on connection tracking
},
desc: "Pure connection tracking without any rules",
},
{
name: "stateless_explicit_return",
stateful: false,
setupFunc: func(m *Manager) {
// Add explicit rules matching return traffic pattern
for i := 0; i < 1000; i++ { // Simulate realistic ruleset size
ip := generateRandomIPs(1)[0]
m.AddPeerFiltering(ip, fw.ProtocolTCP,

Check failure on line 107 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Error return value of `m.AddPeerFiltering` is not checked (errcheck)

Check failure on line 107 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (macos-latest)

Error return value of `m.AddPeerFiltering` is not checked (errcheck)

Check failure on line 107 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

Error return value of `m.AddPeerFiltering` is not checked (errcheck)
&fw.Port{Values: []int{1024 + i}},
&fw.Port{Values: []int{80}},
fw.RuleDirectionIN, fw.ActionAccept, "", "explicit return")
}
},
desc: "Explicit rules matching return traffic patterns without state",
},
{
name: "stateful_with_established",
stateful: true,
setupFunc: func(m *Manager) {
// Add some basic rules but rely on state for established connections
m.AddPeerFiltering(net.ParseIP("0.0.0.0"), fw.ProtocolTCP, nil, nil,

Check failure on line 120 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Error return value of `m.AddPeerFiltering` is not checked (errcheck)

Check failure on line 120 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (macos-latest)

Error return value of `m.AddPeerFiltering` is not checked (errcheck)

Check failure on line 120 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

Error return value of `m.AddPeerFiltering` is not checked (errcheck)
fw.RuleDirectionIN, fw.ActionDrop, "", "default drop")
},
desc: "Connection tracking with established connections",
},
}

// Test both TCP and UDP
protocols := []struct {
name string
proto layers.IPProtocol
}{
{"TCP", layers.IPProtocolTCP},
{"UDP", layers.IPProtocolUDP},
}

for _, sc := range scenarios {
for _, proto := range protocols {
b.Run(fmt.Sprintf("%s_%s", sc.name, proto.name), func(b *testing.B) {
// Configure stateful/stateless mode
if !sc.stateful {
os.Setenv("NB_DISABLE_CONNTRACK", "1")
} else {
os.Unsetenv("NB_DISABLE_CONNTRACK")
}

// Create manager and basic setup
manager, _ := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
})
defer manager.Reset(nil)

Check failure on line 150 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Error return value of `manager.Reset` is not checked (errcheck)

Check failure on line 150 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (macos-latest)

Error return value of `manager.Reset` is not checked (errcheck)

Check failure on line 150 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

Error return value of `manager.Reset` is not checked (errcheck)

manager.wgNetwork = &net.IPNet{
IP: net.ParseIP("100.64.0.0"),
Mask: net.CIDRMask(10, 32),
}

// Apply scenario-specific setup
sc.setupFunc(manager)

// Generate test packets
srcIP := generateRandomIPs(1)[0]
dstIP := generateRandomIPs(1)[0]
srcPort := uint16(1024 + b.N%60000)
dstPort := uint16(80)

outbound := generatePacket(srcIP, dstIP, srcPort, dstPort, proto.proto)
inbound := generatePacket(dstIP, srcIP, dstPort, srcPort, proto.proto)

// For stateful scenarios, establish the connection
if sc.stateful {
manager.processOutgoingHooks(outbound)
}

// Measure inbound packet processing
b.ResetTimer()
for i := 0; i < b.N; i++ {
manager.dropFilter(inbound, manager.incomingRules)
}
})
}
}
}

// BenchmarkStateScaling measures how performance scales with connection table size
func BenchmarkStateScaling(b *testing.B) {
connCounts := []int{100, 1000, 10000, 100000}

for _, count := range connCounts {
b.Run(fmt.Sprintf("conns_%d", count), func(b *testing.B) {
manager, _ := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
})
defer manager.Reset(nil)

Check failure on line 193 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Error return value of `manager.Reset` is not checked (errcheck)

Check failure on line 193 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (macos-latest)

Error return value of `manager.Reset` is not checked (errcheck)

Check failure on line 193 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

Error return value of `manager.Reset` is not checked (errcheck)

manager.wgNetwork = &net.IPNet{
IP: net.ParseIP("100.64.0.0"),
Mask: net.CIDRMask(10, 32),
}

// Pre-populate connection table
srcIPs := generateRandomIPs(count)
dstIPs := generateRandomIPs(count)
for i := 0; i < count; i++ {
outbound := generatePacket(srcIPs[i], dstIPs[i],
uint16(1024+i), 80, layers.IPProtocolTCP)
manager.processOutgoingHooks(outbound)
}

// Test packet
testOut := generatePacket(srcIPs[0], dstIPs[0], 1024, 80, layers.IPProtocolTCP)
testIn := generatePacket(dstIPs[0], srcIPs[0], 80, 1024, layers.IPProtocolTCP)

Check failure on line 211 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / codespell

testIn ==> testing

// First establish our test connection
manager.processOutgoingHooks(testOut)

b.ResetTimer()
for i := 0; i < b.N; i++ {
manager.dropFilter(testIn, manager.incomingRules)

Check failure on line 218 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / codespell

testIn ==> testing
}
})
}
}

// BenchmarkEstablishmentOverhead measures the overhead of connection establishment
func BenchmarkEstablishmentOverhead(b *testing.B) {
scenarios := []struct {
name string
established bool
}{
{"established", true},
{"new", false},
}

for _, sc := range scenarios {
b.Run(sc.name, func(b *testing.B) {
manager, _ := Create(&IFaceMock{
SetFilterFunc: func(device.PacketFilter) error { return nil },
})
defer manager.Reset(nil)

Check failure on line 239 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Error return value of `manager.Reset` is not checked (errcheck)

Check failure on line 239 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (macos-latest)

Error return value of `manager.Reset` is not checked (errcheck)

Check failure on line 239 in client/firewall/uspfilter/uspfilter_bench_test.go

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

Error return value of `manager.Reset` is not checked (errcheck)

manager.wgNetwork = &net.IPNet{
IP: net.ParseIP("100.64.0.0"),
Mask: net.CIDRMask(10, 32),
}

srcIP := generateRandomIPs(1)[0]
dstIP := generateRandomIPs(1)[0]
outbound := generatePacket(srcIP, dstIP, 1024, 80, layers.IPProtocolTCP)
inbound := generatePacket(dstIP, srcIP, 80, 1024, layers.IPProtocolTCP)

if sc.established {
manager.processOutgoingHooks(outbound)
}

b.ResetTimer()
for i := 0; i < b.N; i++ {
manager.dropFilter(inbound, manager.incomingRules)
}
})
}
}

0 comments on commit 1306da2

Please sign in to comment.