Add IPv4 range optimization for IP to AS lookups
- Add v4_ip_start and v4_ip_end columns to live_routes table - Calculate IPv4 CIDR ranges as 32-bit integers for fast lookups - Update PrefixHandler to populate IPv4 range fields - Add GetASInfoForIP method with optimized IPv4 queries - Add comprehensive tests for IP conversion functions
This commit is contained in:
301
internal/database/database_test.go
Normal file
301
internal/database/database_test.go
Normal file
@@ -0,0 +1,301 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIPToUint32(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ip string
|
||||
expected uint32
|
||||
}{
|
||||
{
|
||||
name: "Simple IP",
|
||||
ip: "192.168.1.1",
|
||||
expected: 3232235777, // 192<<24 + 168<<16 + 1<<8 + 1
|
||||
},
|
||||
{
|
||||
name: "Minimum IP",
|
||||
ip: "0.0.0.0",
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "Maximum IP",
|
||||
ip: "255.255.255.255",
|
||||
expected: 4294967295,
|
||||
},
|
||||
{
|
||||
name: "10.0.0.0",
|
||||
ip: "10.0.0.0",
|
||||
expected: 167772160,
|
||||
},
|
||||
{
|
||||
name: "172.16.0.0",
|
||||
ip: "172.16.0.0",
|
||||
expected: 2886729728,
|
||||
},
|
||||
{
|
||||
name: "8.8.8.8",
|
||||
ip: "8.8.8.8",
|
||||
expected: 134744072,
|
||||
},
|
||||
{
|
||||
name: "1.2.3.4",
|
||||
ip: "1.2.3.4",
|
||||
expected: 16909060,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ip := net.ParseIP(tt.ip)
|
||||
if ip == nil {
|
||||
t.Fatalf("Failed to parse IP: %s", tt.ip)
|
||||
}
|
||||
|
||||
result := ipToUint32(ip)
|
||||
if result != tt.expected {
|
||||
t.Errorf("ipToUint32(%s) = %d, want %d", tt.ip, result, tt.expected)
|
||||
}
|
||||
|
||||
// Test with IPv4-mapped IPv6 address
|
||||
ip6 := net.ParseIP(tt.ip).To16()
|
||||
if ip6 != nil {
|
||||
result6 := ipToUint32(ip6)
|
||||
if result6 != tt.expected {
|
||||
t.Errorf("ipToUint32(%s as IPv6) = %d, want %d", tt.ip, result6, tt.expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateIPv4Range(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cidr string
|
||||
wantStart uint32
|
||||
wantEnd uint32
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Single IP /32",
|
||||
cidr: "192.168.1.1/32",
|
||||
wantStart: 3232235777,
|
||||
wantEnd: 3232235777,
|
||||
},
|
||||
{
|
||||
name: "Class C /24",
|
||||
cidr: "192.168.1.0/24",
|
||||
wantStart: 3232235776, // 192.168.1.0
|
||||
wantEnd: 3232236031, // 192.168.1.255
|
||||
},
|
||||
{
|
||||
name: "Class B /16",
|
||||
cidr: "192.168.0.0/16",
|
||||
wantStart: 3232235520, // 192.168.0.0
|
||||
wantEnd: 3232301055, // 192.168.255.255
|
||||
},
|
||||
{
|
||||
name: "Class A /8",
|
||||
cidr: "10.0.0.0/8",
|
||||
wantStart: 167772160, // 10.0.0.0
|
||||
wantEnd: 184549375, // 10.255.255.255
|
||||
},
|
||||
{
|
||||
name: "Entire IPv4 space /0",
|
||||
cidr: "0.0.0.0/0",
|
||||
wantStart: 0,
|
||||
wantEnd: 4294967295,
|
||||
},
|
||||
{
|
||||
name: "Small subnet /30",
|
||||
cidr: "192.168.1.0/30",
|
||||
wantStart: 3232235776, // 192.168.1.0
|
||||
wantEnd: 3232235779, // 192.168.1.3
|
||||
},
|
||||
{
|
||||
name: "Medium subnet /20",
|
||||
cidr: "172.16.0.0/20",
|
||||
wantStart: 2886729728, // 172.16.0.0
|
||||
wantEnd: 2886733823, // 172.16.15.255
|
||||
},
|
||||
{
|
||||
name: "Private range 172.16/12",
|
||||
cidr: "172.16.0.0/12",
|
||||
wantStart: 2886729728, // 172.16.0.0
|
||||
wantEnd: 2887778303, // 172.31.255.255
|
||||
},
|
||||
{
|
||||
name: "Google DNS /29",
|
||||
cidr: "8.8.8.8/29",
|
||||
wantStart: 134744072, // 8.8.8.8 (network is actually 8.8.8.8 with /29)
|
||||
wantEnd: 134744079, // 8.8.8.15
|
||||
},
|
||||
{
|
||||
name: "Non-zero host bits",
|
||||
cidr: "192.168.1.5/24",
|
||||
wantStart: 3232235776, // 192.168.1.0 (network address)
|
||||
wantEnd: 3232236031, // 192.168.1.255
|
||||
},
|
||||
{
|
||||
name: "Invalid CIDR",
|
||||
cidr: "192.168.1.1/33",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid IP",
|
||||
cidr: "256.256.256.256/24",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "IPv6 CIDR",
|
||||
cidr: "2001:db8::/32",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Empty CIDR",
|
||||
cidr: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Missing mask",
|
||||
cidr: "192.168.1.1",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
start, end, err := CalculateIPv4Range(tt.cidr)
|
||||
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("CalculateIPv4Range(%s) expected error, got nil", tt.cidr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("CalculateIPv4Range(%s) unexpected error: %v", tt.cidr, err)
|
||||
return
|
||||
}
|
||||
|
||||
if start != tt.wantStart {
|
||||
t.Errorf("CalculateIPv4Range(%s) start = %d, want %d", tt.cidr, start, tt.wantStart)
|
||||
}
|
||||
|
||||
if end != tt.wantEnd {
|
||||
t.Errorf("CalculateIPv4Range(%s) end = %d, want %d", tt.cidr, end, tt.wantEnd)
|
||||
}
|
||||
|
||||
// Verify that start <= end
|
||||
if start > end {
|
||||
t.Errorf("CalculateIPv4Range(%s) start (%d) > end (%d)", tt.cidr, start, end)
|
||||
}
|
||||
|
||||
// Verify the range size matches the CIDR mask
|
||||
if !tt.wantErr && tt.cidr != "" {
|
||||
_, ipNet, _ := net.ParseCIDR(tt.cidr)
|
||||
if ipNet != nil {
|
||||
ones, bits := ipNet.Mask.Size()
|
||||
expectedSize := uint32(1) << uint(bits-ones)
|
||||
actualSize := end - start + 1
|
||||
if actualSize != expectedSize {
|
||||
t.Errorf("CalculateIPv4Range(%s) range size = %d, want %d", tt.cidr, actualSize, expectedSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPv4RangeIntegration(t *testing.T) {
|
||||
// Test that our functions work correctly together
|
||||
tests := []struct {
|
||||
name string
|
||||
cidr string
|
||||
testIPs []string
|
||||
shouldContain []bool
|
||||
}{
|
||||
{
|
||||
name: "192.168.1.0/24",
|
||||
cidr: "192.168.1.0/24",
|
||||
testIPs: []string{
|
||||
"192.168.1.0",
|
||||
"192.168.1.1",
|
||||
"192.168.1.255",
|
||||
"192.168.0.255",
|
||||
"192.168.2.0",
|
||||
},
|
||||
shouldContain: []bool{true, true, true, false, false},
|
||||
},
|
||||
{
|
||||
name: "10.0.0.0/8",
|
||||
cidr: "10.0.0.0/8",
|
||||
testIPs: []string{
|
||||
"10.0.0.0",
|
||||
"10.255.255.255",
|
||||
"10.1.2.3",
|
||||
"9.255.255.255",
|
||||
"11.0.0.0",
|
||||
},
|
||||
shouldContain: []bool{true, true, true, false, false},
|
||||
},
|
||||
{
|
||||
name: "172.16.0.0/12",
|
||||
cidr: "172.16.0.0/12",
|
||||
testIPs: []string{
|
||||
"172.16.0.0",
|
||||
"172.31.255.255",
|
||||
"172.20.1.1",
|
||||
"172.15.255.255",
|
||||
"172.32.0.0",
|
||||
},
|
||||
shouldContain: []bool{true, true, true, false, false},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
start, end, err := CalculateIPv4Range(tt.cidr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to calculate range for %s: %v", tt.cidr, err)
|
||||
}
|
||||
|
||||
for i, testIP := range tt.testIPs {
|
||||
ip := net.ParseIP(testIP)
|
||||
if ip == nil {
|
||||
t.Fatalf("Failed to parse test IP: %s", testIP)
|
||||
}
|
||||
|
||||
ipUint := ipToUint32(ip)
|
||||
contained := ipUint >= start && ipUint <= end
|
||||
|
||||
if contained != tt.shouldContain[i] {
|
||||
t.Errorf("IP %s in range %s: got %v, want %v", testIP, tt.cidr, contained, tt.shouldContain[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIPToUint32(b *testing.B) {
|
||||
ip := net.ParseIP("192.168.1.1")
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ipToUint32(ip)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCalculateIPv4Range(b *testing.B) {
|
||||
cidr := "192.168.0.0/16"
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _, _ = CalculateIPv4Range(cidr)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user