kube-router/pkg/utils/ip_test.go
Aaron U'Ren 3c895955f7 fact(utils): factor out single subnet ip logic
Removes repeated logic of calculating IP address subnets for single
subnet hosts and consolidates it in one place.
2025-06-29 17:42:18 -05:00

345 lines
6.9 KiB
Go

package utils
import (
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIP_Equal(t *testing.T) {
tests := []struct {
name string
ip1 net.IP
ip2 net.IP
expected bool
}{
{
name: "IPv4 equal",
ip1: net.IPv4(192, 168, 1, 1),
ip2: net.IPv4(192, 168, 1, 1),
expected: true,
},
{
name: "IPv4 not equal",
ip1: net.IPv4(192, 168, 1, 1),
ip2: net.IPv4(192, 168, 1, 2),
expected: false,
},
{
name: "IPv4 mapped IPv6 equal to IPv4",
ip1: net.IPv4(192, 168, 1, 1),
ip2: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1},
expected: true,
},
{
name: "nil IPs equal",
ip1: nil,
ip2: nil,
expected: true,
},
{
name: "nil and non-nil IP not equal",
ip1: nil,
ip2: net.IPv4(192, 168, 1, 1),
expected: false,
},
{
name: "IPv6 equal",
ip1: net.IPv6loopback,
ip2: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.ip1.Equal(tt.ip2))
})
}
}
func TestIP_To4(t *testing.T) {
tests := []struct {
name string
ip net.IP
expected net.IP
}{
{
name: "Valid IPv4",
ip: net.IPv4(192, 168, 1, 1),
expected: net.IP{192, 168, 1, 1},
},
{
name: "IPv4-mapped IPv6",
ip: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1},
expected: net.IP{192, 168, 1, 1},
},
{
name: "Pure IPv6",
ip: net.IPv6loopback,
expected: nil,
},
{
name: "nil IP",
ip: nil,
expected: nil,
},
{
name: "Invalid length IP",
ip: net.IP{1, 2, 3},
expected: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.ip.To4())
})
}
}
func TestIPNet_Contains(t *testing.T) {
tests := []struct {
name string
network *net.IPNet
ip net.IP
expected bool
}{
{
name: "IPv4 in network",
network: &net.IPNet{
IP: net.IPv4(192, 168, 1, 0),
Mask: net.IPv4Mask(255, 255, 255, 0),
},
ip: net.IPv4(192, 168, 1, 1),
expected: true,
},
{
name: "IPv4 not in network",
network: &net.IPNet{
IP: net.IPv4(192, 168, 1, 0),
Mask: net.IPv4Mask(255, 255, 255, 0),
},
ip: net.IPv4(192, 168, 2, 1),
expected: false,
},
{
name: "IPv6 in network",
network: &net.IPNet{
IP: net.IPv6loopback,
Mask: net.CIDRMask(128, 128),
},
ip: net.IPv6loopback,
expected: true,
},
{
name: "Mismatched IP versions",
network: &net.IPNet{
IP: net.IPv4(192, 168, 1, 0),
Mask: net.IPv4Mask(255, 255, 255, 0),
},
ip: net.IPv6loopback,
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.network.Contains(tt.ip))
})
}
}
func TestParseCIDR(t *testing.T) {
tests := []struct {
name string
cidr string
expectError bool
expectedIP net.IP
expectedNet *net.IPNet
}{
{
name: "Valid IPv4 CIDR",
cidr: "192.168.1.0/24",
expectError: false,
expectedIP: net.IPv4(192, 168, 1, 0),
expectedNet: &net.IPNet{
IP: net.IPv4(192, 168, 1, 0),
Mask: net.IPv4Mask(255, 255, 255, 0),
},
},
{
name: "Invalid CIDR format",
cidr: "192.168.1.0",
expectError: true,
},
{
name: "Invalid prefix length",
cidr: "192.168.1.0/33",
expectError: true,
},
{
name: "Invalid IP address",
cidr: "300.168.1.0/24",
expectError: true,
},
{
name: "Empty string",
cidr: "",
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ip, ipNet, err := net.ParseCIDR(tt.cidr)
if tt.expectError {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.True(t, tt.expectedIP.Equal(ip))
assert.True(t, tt.expectedNet.IP.Equal(ipNet.IP))
assert.Equal(t, tt.expectedNet.Mask, ipNet.Mask)
})
}
}
func TestIP_DefaultMask(t *testing.T) {
tests := []struct {
name string
ip net.IP
expected net.IPMask
}{
{
name: "Class A",
ip: net.IPv4(10, 0, 0, 0),
expected: net.IPv4Mask(255, 0, 0, 0),
},
{
name: "Class B",
ip: net.IPv4(172, 16, 0, 0),
expected: net.IPv4Mask(255, 255, 0, 0),
},
{
name: "Class C",
ip: net.IPv4(192, 168, 0, 0),
expected: net.IPv4Mask(255, 255, 255, 0),
},
{
name: "IPv6 address",
ip: net.IPv6loopback,
expected: nil,
},
{
name: "nil IP",
ip: nil,
expected: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.ip.DefaultMask())
})
}
}
func TestIP_IsPrivate(t *testing.T) {
tests := []struct {
name string
ip net.IP
expected bool
}{
{
name: "Private IPv4 10.x.x.x",
ip: net.IPv4(10, 0, 0, 1),
expected: true,
},
{
name: "Private IPv4 172.16.x.x",
ip: net.IPv4(172, 16, 0, 1),
expected: true,
},
{
name: "Private IPv4 192.168.x.x",
ip: net.IPv4(192, 168, 0, 1),
expected: true,
},
{
name: "Public IPv4",
ip: net.IPv4(8, 8, 8, 8),
expected: false,
},
{
name: "Private IPv6 fc00::/7",
ip: net.IP{0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
expected: true,
},
{
name: "Public IPv6",
ip: net.IPv6loopback,
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.ip.IsPrivate())
})
}
}
func TestGetSingleIPNet(t *testing.T) {
tests := []struct {
name string
ip net.IP
expected *net.IPNet
}{
{
name: "IPv4 address",
ip: net.IPv4(192, 168, 1, 1),
expected: &net.IPNet{
IP: net.IPv4(192, 168, 1, 1),
Mask: net.CIDRMask(32, 32),
},
},
{
name: "IPv4-mapped IPv6 address",
ip: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1},
expected: &net.IPNet{
IP: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1},
Mask: net.CIDRMask(32, 32),
},
},
{
name: "IPv6 address",
ip: net.IPv6loopback,
expected: &net.IPNet{
IP: net.IPv6loopback,
Mask: net.CIDRMask(128, 128),
},
},
{
name: "Another IPv6 address",
ip: net.IP{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
expected: &net.IPNet{
IP: net.IP{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Mask: net.CIDRMask(128, 128),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := GetSingleIPNet(tt.ip)
require.NotNil(t, result)
assert.True(t, tt.expected.IP.Equal(result.IP))
assert.Equal(t, tt.expected.Mask, result.Mask)
})
}
}