Signed-off-by: ayushi-work <ayushi.work007@gmail.com>
parent
1d091c4100
commit
2f3bb0dce3
@ -0,0 +1,236 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IPMatcher checks whether an IP address matches a given filter.
|
||||
// The filter can be:
|
||||
// - A single IP address (e.g., "192.168.1.1" or "::1")
|
||||
// - A CIDR notation (e.g., "192.168.1.0/24" or "2001:db8::/32")
|
||||
// - An IP range (e.g., "192.168.1.1-192.168.1.255")
|
||||
// - A wildcard pattern (e.g., "192.168.1.*" or "192.168.*.*")
|
||||
type IPMatcher struct {
|
||||
cidr *net.IPNet
|
||||
ipStart net.IP
|
||||
ipEnd net.IP
|
||||
exact net.IP
|
||||
}
|
||||
|
||||
// NewIPMatcher creates an IPMatcher from a filter string.
|
||||
// Supported formats:
|
||||
// - CIDR: "192.168.1.0/24", "2001:db8::/32"
|
||||
// - Range: "192.168.1.1-192.168.1.255", "::1-::10"
|
||||
// - Wildcard: "192.168.1.*", "192.168.*.*", "10.*.*.*"
|
||||
// - Single IP: "192.168.1.1", "::1"
|
||||
func NewIPMatcher(filter string) (*IPMatcher, error) {
|
||||
filter = strings.TrimSpace(filter)
|
||||
if filter == "" {
|
||||
return nil, fmt.Errorf("empty IP filter")
|
||||
}
|
||||
|
||||
// Try CIDR notation first
|
||||
if strings.Contains(filter, "/") {
|
||||
_, cidrNet, err := net.ParseCIDR(filter)
|
||||
if err == nil {
|
||||
return &IPMatcher{cidr: cidrNet}, nil
|
||||
}
|
||||
// If it has "/" but isn't valid CIDR, continue to try other formats
|
||||
}
|
||||
|
||||
// Try IP range (e.g., "192.168.1.1-192.168.1.255")
|
||||
if strings.Count(filter, "-") == 1 {
|
||||
parts := strings.SplitN(filter, "-", 2)
|
||||
start := net.ParseIP(strings.TrimSpace(parts[0]))
|
||||
end := net.ParseIP(strings.TrimSpace(parts[1]))
|
||||
if start != nil && end != nil {
|
||||
// Ensure same IP version
|
||||
if (start.To4() != nil) == (end.To4() != nil) {
|
||||
return &IPMatcher{ipStart: start, ipEnd: end}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("IP range must contain same IP version: %s", filter)
|
||||
}
|
||||
}
|
||||
|
||||
// Try wildcard pattern (e.g., "192.168.1.*" or "192.168.*.*")
|
||||
if strings.Contains(filter, "*") {
|
||||
cidr, err := wildcardToCIDR(filter)
|
||||
if err == nil {
|
||||
return &IPMatcher{cidr: cidr}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Try single exact IP
|
||||
ip := net.ParseIP(filter)
|
||||
if ip != nil {
|
||||
return &IPMatcher{exact: ip}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid IP filter format: %s", filter)
|
||||
}
|
||||
|
||||
// Match checks if the given IP address matches the filter.
|
||||
// The ip parameter should be a string representation of an IP address.
|
||||
// Returns an error if the IP string is invalid.
|
||||
func (m *IPMatcher) Match(ip string) (bool, error) {
|
||||
parsedIP := net.ParseIP(strings.TrimSpace(ip))
|
||||
if parsedIP == nil {
|
||||
return false, fmt.Errorf("invalid IP address: %s", ip)
|
||||
}
|
||||
|
||||
return m.MatchIP(parsedIP), nil
|
||||
}
|
||||
|
||||
// MatchIP checks if the given parsed IP address matches the filter.
|
||||
func (m *IPMatcher) MatchIP(ip net.IP) bool {
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
switch {
|
||||
case m.cidr != nil:
|
||||
return m.cidr.Contains(ip)
|
||||
case m.ipStart != nil && m.ipEnd != nil:
|
||||
return ipInRange(ip, m.ipStart, m.ipEnd)
|
||||
case m.exact != nil:
|
||||
return m.exact.Equal(ip)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ipInRange checks if ip is within the inclusive range [start, end].
|
||||
func ipInRange(ip, start, end net.IP) bool {
|
||||
// Normalize to 16-byte representation for comparison
|
||||
ip16 := ip.To16()
|
||||
start16 := start.To16()
|
||||
end16 := end.To16()
|
||||
|
||||
return bytesCompare(start16, ip16) <= 0 && bytesCompare(ip16, end16) <= 0
|
||||
}
|
||||
|
||||
// bytesCompare compares two byte slices lexicographically.
|
||||
// Returns -1 if a < b, 0 if a == b, 1 if a > b.
|
||||
func bytesCompare(a, b []byte) int {
|
||||
for i := 0; i < len(a) && i < len(b); i++ {
|
||||
if a[i] < b[i] {
|
||||
return -1
|
||||
}
|
||||
if a[i] > b[i] {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
if len(a) < len(b) {
|
||||
return -1
|
||||
}
|
||||
if len(a) > len(b) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// wildcardToCIDR converts a wildcard IP pattern to a CIDR net.
|
||||
// Only supports IPv4 wildcards (e.g., "192.168.1.*" -> "192.168.1.0/24").
|
||||
func wildcardToCIDR(pattern string) (*net.IPNet, error) {
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
parts := strings.Split(pattern, ".")
|
||||
|
||||
if len(parts) != 4 {
|
||||
return nil, fmt.Errorf("wildcard pattern currently only supports IPv4: %s", pattern)
|
||||
}
|
||||
|
||||
// Count how many fixed octets
|
||||
fixedOctets := 0
|
||||
for i, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "*" {
|
||||
// Fill remaining octets with 0 for the network address
|
||||
for j := i; j < 4; j++ {
|
||||
parts[j] = "0"
|
||||
}
|
||||
break
|
||||
}
|
||||
fixedOctets++
|
||||
}
|
||||
|
||||
// All wildcards would be 0.0.0.0/0
|
||||
if fixedOctets == 0 {
|
||||
return &net.IPNet{
|
||||
IP: net.IPv4(0, 0, 0, 0),
|
||||
Mask: net.CIDRMask(0, 32),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Construct CIDR network
|
||||
cidrStr := fmt.Sprintf("%s/%d", strings.Join(parts, "."), fixedOctets*8)
|
||||
_, cidrNet, err := net.ParseCIDR(cidrStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid wildcard pattern: %s (%w)", pattern, err)
|
||||
}
|
||||
|
||||
return cidrNet, nil
|
||||
}
|
||||
|
||||
// IsCIDRNotation checks if a filter string is a valid CIDR notation.
|
||||
func IsCIDRNotation(filter string) bool {
|
||||
_, _, err := net.ParseCIDR(strings.TrimSpace(filter))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IPFilterToCIDRs converts an IP filter string to a list of CIDR networks.
|
||||
// This is useful when you need to convert user-friendly IP filters
|
||||
// to CIDR notation for display or storage.
|
||||
// Supports CIDR, wildcard, single IP, and IP range (expanded).
|
||||
func IPFilterToCIDRs(filter string) ([]*net.IPNet, error) {
|
||||
matcher, err := NewIPMatcher(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case matcher.cidr != nil:
|
||||
return []*net.IPNet{matcher.cidr}, nil
|
||||
case matcher.exact != nil:
|
||||
// Single IP -> /32 or /128
|
||||
if matcher.exact.To4() != nil {
|
||||
return []*net.IPNet{{
|
||||
IP: matcher.exact,
|
||||
Mask: net.CIDRMask(32, 32),
|
||||
}}, nil
|
||||
}
|
||||
return []*net.IPNet{{
|
||||
IP: matcher.exact,
|
||||
Mask: net.CIDRMask(128, 128),
|
||||
}}, nil
|
||||
case matcher.ipStart != nil && matcher.ipEnd != nil:
|
||||
// IP range - return as two /32 or /128 CIDRs
|
||||
// Note: This is a simplified representation; the caller should
|
||||
// use Match/MatchIP for exact range matching
|
||||
cidrs := make([]*net.IPNet, 0)
|
||||
if matcher.ipStart.To4() != nil {
|
||||
cidrs = append(cidrs, &net.IPNet{
|
||||
IP: matcher.ipStart,
|
||||
Mask: net.CIDRMask(32, 32),
|
||||
})
|
||||
cidrs = append(cidrs, &net.IPNet{
|
||||
IP: matcher.ipEnd,
|
||||
Mask: net.CIDRMask(32, 32),
|
||||
})
|
||||
} else {
|
||||
cidrs = append(cidrs, &net.IPNet{
|
||||
IP: matcher.ipStart,
|
||||
Mask: net.CIDRMask(128, 128),
|
||||
})
|
||||
cidrs = append(cidrs, &net.IPNet{
|
||||
IP: matcher.ipEnd,
|
||||
Mask: net.CIDRMask(128, 128),
|
||||
})
|
||||
}
|
||||
return cidrs, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("cannot convert filter to CIDR: %s", filter)
|
||||
}
|
||||
@ -0,0 +1,432 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewIPMatcher_CIDR(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
filter string
|
||||
wantErr bool
|
||||
}{
|
||||
{"IPv4 CIDR /24", "192.168.1.0/24", false},
|
||||
{"IPv4 CIDR /16", "10.0.0.0/16", false},
|
||||
{"IPv4 CIDR /32", "192.168.1.1/32", false},
|
||||
{"IPv4 CIDR /0", "0.0.0.0/0", false},
|
||||
{"IPv6 CIDR /64", "2001:db8::/64", false},
|
||||
{"IPv6 CIDR /128", "::1/128", false},
|
||||
{"Invalid CIDR - bad mask", "192.168.1.0/33", true},
|
||||
{"Invalid CIDR - bad IP", "999.999.999.999/24", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matcher, err := NewIPMatcher(tt.filter)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("NewIPMatcher(%q) expected error, got nil", tt.filter)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("NewIPMatcher(%q) unexpected error: %v", tt.filter, err)
|
||||
return
|
||||
}
|
||||
if matcher.cidr == nil {
|
||||
t.Errorf("NewIPMatcher(%q) expected CIDR matcher, got nil", tt.filter)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIPMatcher_Range(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
filter string
|
||||
wantErr bool
|
||||
}{
|
||||
{"IPv4 range", "192.168.1.1-192.168.1.255", false},
|
||||
{"IPv4 range with spaces", "192.168.1.1 - 192.168.1.255", false},
|
||||
{"IPv6 range", "::1-::10", false},
|
||||
{"Cross IP version", "192.168.1.1-::1", true},
|
||||
{"Invalid start", "abc-192.168.1.255", true},
|
||||
{"Invalid end", "192.168.1.1-abc", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matcher, err := NewIPMatcher(tt.filter)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("NewIPMatcher(%q) expected error, got nil", tt.filter)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("NewIPMatcher(%q) unexpected error: %v", tt.filter, err)
|
||||
return
|
||||
}
|
||||
if matcher.ipStart == nil || matcher.ipEnd == nil {
|
||||
t.Errorf("NewIPMatcher(%q) expected range matcher, got nil", tt.filter)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIPMatcher_Wildcard(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
filter string
|
||||
wantErr bool
|
||||
}{
|
||||
{"One wildcard", "192.168.1.*", false},
|
||||
{"Two wildcards", "192.168.*.*", false},
|
||||
{"Three wildcards", "192.*.*.*", false},
|
||||
{"All wildcards", "*.*.*.*", false},
|
||||
{"IPv6 not supported", "2001:db8::*", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matcher, err := NewIPMatcher(tt.filter)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("NewIPMatcher(%q) expected error, got nil", tt.filter)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("NewIPMatcher(%q) unexpected error: %v", tt.filter, err)
|
||||
return
|
||||
}
|
||||
if matcher.cidr == nil {
|
||||
t.Errorf("NewIPMatcher(%q) expected CIDR matcher (from wildcard), got nil", tt.filter)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIPMatcher_SingleIP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
filter string
|
||||
wantErr bool
|
||||
}{
|
||||
{"IPv4 single", "192.168.1.1", false},
|
||||
{"IPv6 single", "::1", false},
|
||||
{"IPv6 full", "2001:db8::1", false},
|
||||
{"Invalid", "not-an-ip", true},
|
||||
{"Empty", "", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matcher, err := NewIPMatcher(tt.filter)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("NewIPMatcher(%q) expected error, got nil", tt.filter)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("NewIPMatcher(%q) unexpected error: %v", tt.filter, err)
|
||||
return
|
||||
}
|
||||
if matcher.exact == nil {
|
||||
t.Errorf("NewIPMatcher(%q) expected exact IP matcher, got nil", tt.filter)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcher_Match_CIDR(t *testing.T) {
|
||||
matcher, err := NewIPMatcher("192.168.1.0/24")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create matcher: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
ip string
|
||||
match bool
|
||||
}{
|
||||
{"192.168.1.1", true},
|
||||
{"192.168.1.255", true},
|
||||
{"192.168.1.0", true},
|
||||
{"192.168.2.1", false},
|
||||
{"10.0.0.1", false},
|
||||
{"invalid", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.ip, func(t *testing.T) {
|
||||
matched, err := matcher.Match(tt.ip)
|
||||
if tt.ip == "invalid" {
|
||||
if err == nil {
|
||||
t.Errorf("Match(%q) expected error", tt.ip)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("Match(%q) unexpected error: %v", tt.ip, err)
|
||||
return
|
||||
}
|
||||
if matched != tt.match {
|
||||
t.Errorf("Match(%q) = %v, want %v", tt.ip, matched, tt.match)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcher_Match_IPv6CIDR(t *testing.T) {
|
||||
matcher, err := NewIPMatcher("2001:db8::/32")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create matcher: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
ip string
|
||||
match bool
|
||||
}{
|
||||
{"2001:db8::1", true},
|
||||
{"2001:db8:1234::1", true},
|
||||
{"2001:db9::1", false},
|
||||
{"::1", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.ip, func(t *testing.T) {
|
||||
matched, err := matcher.Match(tt.ip)
|
||||
if err != nil {
|
||||
t.Errorf("Match(%q) unexpected error: %v", tt.ip, err)
|
||||
return
|
||||
}
|
||||
if matched != tt.match {
|
||||
t.Errorf("Match(%q) = %v, want %v", tt.ip, matched, tt.match)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcher_Match_Range(t *testing.T) {
|
||||
matcher, err := NewIPMatcher("192.168.1.10-192.168.1.20")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create matcher: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
ip string
|
||||
match bool
|
||||
}{
|
||||
{"192.168.1.10", true},
|
||||
{"192.168.1.15", true},
|
||||
{"192.168.1.20", true},
|
||||
{"192.168.1.9", false},
|
||||
{"192.168.1.21", false},
|
||||
{"192.168.2.1", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.ip, func(t *testing.T) {
|
||||
matched, err := matcher.Match(tt.ip)
|
||||
if err != nil {
|
||||
t.Errorf("Match(%q) unexpected error: %v", tt.ip, err)
|
||||
return
|
||||
}
|
||||
if matched != tt.match {
|
||||
t.Errorf("Match(%q) = %v, want %v", tt.ip, matched, tt.match)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcher_Match_Wildcard(t *testing.T) {
|
||||
matcher, err := NewIPMatcher("192.168.*.*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create matcher: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
ip string
|
||||
match bool
|
||||
}{
|
||||
{"192.168.1.1", true},
|
||||
{"192.168.255.255", true},
|
||||
{"192.168.0.0", true},
|
||||
{"192.169.1.1", false},
|
||||
{"10.0.0.1", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.ip, func(t *testing.T) {
|
||||
matched, err := matcher.Match(tt.ip)
|
||||
if err != nil {
|
||||
t.Errorf("Match(%q) unexpected error: %v", tt.ip, err)
|
||||
return
|
||||
}
|
||||
if matched != tt.match {
|
||||
t.Errorf("Match(%q) = %v, want %v", tt.ip, matched, tt.match)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcher_Match_SingleIP(t *testing.T) {
|
||||
matcher, err := NewIPMatcher("192.168.1.1")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create matcher: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
ip string
|
||||
match bool
|
||||
}{
|
||||
{"192.168.1.1", true},
|
||||
{"192.168.1.2", false},
|
||||
{"192.168.1.0", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.ip, func(t *testing.T) {
|
||||
matched, _ := matcher.Match(tt.ip)
|
||||
if matched != tt.match {
|
||||
t.Errorf("Match(%q) = %v, want %v", tt.ip, matched, tt.match)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcher_MatchIP_WithNetIP(t *testing.T) {
|
||||
matcher, err := NewIPMatcher("10.0.0.0/8")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create matcher: %v", err)
|
||||
}
|
||||
|
||||
// Test MatchIP with pre-parsed net.IP
|
||||
ip := net.ParseIP("10.255.255.255")
|
||||
if !matcher.MatchIP(ip) {
|
||||
t.Errorf("MatchIP(%v) = false, want true", ip)
|
||||
}
|
||||
|
||||
ip = net.ParseIP("11.0.0.1")
|
||||
if matcher.MatchIP(ip) {
|
||||
t.Errorf("MatchIP(%v) = true, want false", ip)
|
||||
}
|
||||
|
||||
// Test nil IP
|
||||
if matcher.MatchIP(nil) {
|
||||
t.Errorf("MatchIP(nil) = true, want false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcardToCIDR(t *testing.T) {
|
||||
tests := []struct {
|
||||
pattern string
|
||||
cidr string
|
||||
wantErr bool
|
||||
}{
|
||||
{"192.168.1.*", "192.168.1.0/24", false},
|
||||
{"192.168.*.*", "192.168.0.0/16", false},
|
||||
{"192.*.*.*", "192.0.0.0/8", false},
|
||||
{"*.*.*.*", "0.0.0.0/0", false},
|
||||
{"10.0.*.*", "10.0.0.0/16", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.pattern, func(t *testing.T) {
|
||||
cidrNet, err := wildcardToCIDR(tt.pattern)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("wildcardToCIDR(%q) expected error, got nil", tt.pattern)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("wildcardToCIDR(%q) unexpected error: %v", tt.pattern, err)
|
||||
return
|
||||
}
|
||||
if cidrNet.String() != tt.cidr {
|
||||
t.Errorf("wildcardToCIDR(%q) = %q, want %q", tt.pattern, cidrNet.String(), tt.cidr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCIDRNotation(t *testing.T) {
|
||||
tests := []struct {
|
||||
filter string
|
||||
isCIDR bool
|
||||
}{
|
||||
{"192.168.1.0/24", true},
|
||||
{"2001:db8::/32", true},
|
||||
{"0.0.0.0/0", true},
|
||||
{"192.168.1.1", false},
|
||||
{"not-a-cidr", false},
|
||||
{"192.168.1.*", false},
|
||||
{"192.168.1.1-192.168.1.255", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.filter, func(t *testing.T) {
|
||||
if got := IsCIDRNotation(tt.filter); got != tt.isCIDR {
|
||||
t.Errorf("IsCIDRNotation(%q) = %v, want %v", tt.filter, got, tt.isCIDR)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcher_BackwardCompatible(t *testing.T) {
|
||||
// Ensures existing exact IP filtering still works
|
||||
matcher, err := NewIPMatcher("123.45.67.89")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create matcher for exact IP: %v", err)
|
||||
}
|
||||
|
||||
matched, err := matcher.Match("123.45.67.89")
|
||||
if err != nil {
|
||||
t.Fatalf("Match failed: %v", err)
|
||||
}
|
||||
if !matched {
|
||||
t.Error("Exact IP should match")
|
||||
}
|
||||
|
||||
matched, err = matcher.Match("123.45.67.90")
|
||||
if err != nil {
|
||||
t.Fatalf("Match failed: %v", err)
|
||||
}
|
||||
if matched {
|
||||
t.Error("Different exact IP should not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcher_IPv6Range(t *testing.T) {
|
||||
matcher, err := NewIPMatcher("::1-::5")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create matcher: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
ip string
|
||||
match bool
|
||||
}{
|
||||
{"::1", true},
|
||||
{"::3", true},
|
||||
{"::5", true},
|
||||
{"::6", false},
|
||||
{"::0", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.ip, func(t *testing.T) {
|
||||
matched, err := matcher.Match(tt.ip)
|
||||
if err != nil {
|
||||
t.Errorf("Match(%q) unexpected error: %v", tt.ip, err)
|
||||
return
|
||||
}
|
||||
if matched != tt.match {
|
||||
t.Errorf("Match(%q) = %v, want %v", tt.ip, matched, tt.match)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue