feat(ja4ebpf): add multi-interface TC, LPM_TRIE ignore_src, unit tests, and fix bugs
- Add multi-interface TC attachment (default "any" = all UP interfaces) - Add BPF LPM_TRIE map ignored_src for kernel-side CIDR filtering - Add userspace ignore_src filtering for SSL/accept4 path via net.IPNet.Contains() - Add AcceptCache for fd→SessionKey correlation with TTL and Close() - Add 5 test files covering writer, procutil, dispatcher, accept_cache, and cmd - Fix formatTCPOptions infinite loop on EOL (case 0 break→return) - Fix pseudoOrderToShort panic on empty slice (negative cap) - Fix AcceptCache goroutine leak (add done channel + Close()) - Update config.yml.example with interfaces, listen_ports, ignore_src - Rewrite docs/services/ja4ebpf.md (was massively stale: XDP, RingBuffer, etc.) - Fix stale XDP/RingBuffer references in docs/architecture.md, thesis, tls.go Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
122
services/ja4ebpf/internal/procutil/proc_lookup_test.go
Normal file
122
services/ja4ebpf/internal/procutil/proc_lookup_test.go
Normal file
@ -0,0 +1,122 @@
|
||||
package procutil
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseHexIPv4(t *testing.T) {
|
||||
tests := []struct {
|
||||
hex string
|
||||
want string
|
||||
isErr bool
|
||||
}{
|
||||
// "0201010A" → 10.1.1.2 (little-endian kernel encoding)
|
||||
{"0201010A", "10.1.1.2", false},
|
||||
// "0100007F" → 127.0.0.1
|
||||
{"0100007F", "127.0.0.1", false},
|
||||
// "00000000" → 0.0.0.0
|
||||
{"00000000", "0.0.0.0", false},
|
||||
// Invalid: wrong length
|
||||
{"0101", "", true},
|
||||
{"", "", true},
|
||||
// Invalid: non-hex
|
||||
{"ZZZZZZZZ", "", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
ip, err := parseHexIPv4(tt.hex)
|
||||
if tt.isErr {
|
||||
if err == nil {
|
||||
t.Errorf("parseHexIPv4(%q): expected error, got ip=%v", tt.hex, ip)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("parseHexIPv4(%q): unexpected error: %v", tt.hex, err)
|
||||
continue
|
||||
}
|
||||
got := ip.String()
|
||||
if got != tt.want {
|
||||
t.Errorf("parseHexIPv4(%q) = %v, want %v", tt.hex, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseHexIPv6(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hex string
|
||||
want string
|
||||
isErr bool
|
||||
}{
|
||||
// IPv4-mapped ::ffff:127.0.0.1
|
||||
// In /proc/net/tcp6: 4 x 32-bit LE words
|
||||
// word0=00000000 word1=00000000 word2=0xffff0000 (LE for 00 00 ff ff) word3=0x0100007f (LE for 7f 00 00 01)
|
||||
{
|
||||
"ipv4-mapped loopback",
|
||||
"0000000000000000FFFF00000100007F",
|
||||
"127.0.0.1",
|
||||
false,
|
||||
},
|
||||
// Invalid: wrong length
|
||||
{"too short", "0000", "", true},
|
||||
// Invalid: non-hex
|
||||
{"bad hex", "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ", "", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ip, err := parseHexIPv6(tt.hex)
|
||||
if tt.isErr {
|
||||
if err == nil {
|
||||
t.Errorf("parseHexIPv6(%q): expected error, got ip=%v", tt.hex, ip)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("parseHexIPv6(%q): unexpected error: %v", tt.hex, err)
|
||||
return
|
||||
}
|
||||
got := ip.String()
|
||||
if got != tt.want {
|
||||
t.Errorf("parseHexIPv6(%q) = %v, want %v", tt.hex, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseHexIPv6_Native(t *testing.T) {
|
||||
// Full IPv6: 2001:db8::1
|
||||
// Little-endian chunks: 0000:0000:0000:0000:0000:0000:0000:0001 → but in /proc format
|
||||
// "00000000000000000000000001000000" → maps to a real IPv6
|
||||
ip, err := parseHexIPv6("01000000000000000000000000000000")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
// Should be native IPv6, not IPv4-mapped
|
||||
if ip.To4() != nil && !isIPv4MappedIPv6(ip.To16()) {
|
||||
t.Errorf("expected native IPv6, got IPv4: %v", ip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPv4MappedIPv6(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ip net.IP
|
||||
want bool
|
||||
}{
|
||||
{"nil", nil, false},
|
||||
{"4-byte", net.IP{10, 0, 0, 1}, false},
|
||||
{"loopback mapped", net.ParseIP("::ffff:127.0.0.1"), true},
|
||||
{"mapped 10.0.0.1", net.ParseIP("::ffff:10.0.0.1"), true},
|
||||
{"not mapped", net.ParseIP("2001:db8::1"), false},
|
||||
{"all zeros", net.IPv6zero, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := isIPv4MappedIPv6(tt.ip)
|
||||
if got != tt.want {
|
||||
t.Errorf("isIPv4MappedIPv6(%v) = %v, want %v", tt.ip, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user