From 1b1247823c23183507e17d8054287001b553fc82 Mon Sep 17 00:00:00 2001 From: mhx Date: Fri, 31 Jul 2026 19:14:22 +0200 Subject: [PATCH] :recycle: cidr: build keys through net.IPNet instead of hand-masking bytes CIDRKeys masked the address byte by byte and formatted the result with string concatenation, while SetCIDR/DeleteCIDR format their keys with net.IPNet.String() via NormalizeCIDR. The two agreed only by coincidence: any divergence in formatting silently stops every range decision from matching, with no test covering the invariant. Mask with net.IP.Mask and format through net.IPNet.String() so both sides go through the same formatter. Output is byte for byte identical to the previous implementation (checked against a golden dump of both IPv4 and IPv6 keys, including ::ffff: forms). Dropping the inner byte loops also removes the only intrange violation in the tree, so the linter exclusion added for them is no longer needed, and the redundant import alias on pkg/ip goes away with it. --- .golangci.yml | 1 - pkg/cache/cache.go | 2 +- pkg/ip/cidr.go | 50 +++++++++++++++++++++++----------------------- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 3ee3205..33598d6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -75,7 +75,6 @@ linters: - mnd - exportloopref - contextcheck - - intrange # not supported by yaegi issues: exclude-use-default: false max-same-issues: 0 diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 83f8743..b33b5cb 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -11,7 +11,7 @@ import ( ttl_map "github.com/leprosus/golang-ttl-map" simpleredis "github.com/maxlerebourg/simpleredis" - ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip" + "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip" ) const ( diff --git a/pkg/ip/cidr.go b/pkg/ip/cidr.go index 49a0129..c30e189 100644 --- a/pkg/ip/cidr.go +++ b/pkg/ip/cidr.go @@ -2,38 +2,18 @@ package ip import ( "net" - "strconv" "strings" ) // CIDRKeys returns all possible CIDR prefixes of an IP, from the most specific (/32 for IPv4, /128 for IPv6) to the least specific (/0). func CIDRKeys(ipStr string) []string { - ip := net.ParseIP(ipStr) - if ip == nil { + parsed, maxBits := parseForPrefix(ipStr) + if parsed == nil { return nil } - ip4 := ip.To4() - if ip4 != nil { - keys := make([]string, 0, 33) - for bits := 32; bits >= 0; bits-- { - mask := net.CIDRMask(bits, 32) - n := make(net.IP, 4) - for i := 0; i < 4; i++ { - n[i] = ip4[i] & mask[i] - } - keys = append(keys, n.String()+"/"+strconv.Itoa(bits)) - } - return keys - } - ip16 := ip.To16() - keys := make([]string, 0, 129) - for bits := 128; bits >= 0; bits-- { - mask := net.CIDRMask(bits, 128) - n := make(net.IP, 16) - for i := 0; i < 16; i++ { - n[i] = ip16[i] & mask[i] - } - keys = append(keys, n.String()+"/"+strconv.Itoa(bits)) + keys := make([]string, 0, maxBits+1) + for bits := maxBits; bits >= 0; bits-- { + keys = append(keys, cidrKey(parsed, bits, maxBits)) } return keys } @@ -46,3 +26,23 @@ func NormalizeCIDR(cidrStr string) string { } return ipNet.String() } + +// parseForPrefix returns the IP in the native form of its family, and that family's bit length. +func parseForPrefix(ipStr string) (net.IP, int) { + parsed := net.ParseIP(ipStr) + if parsed == nil { + return nil, 0 + } + if parsed4 := parsed.To4(); parsed4 != nil { + return parsed4, 32 + } + return parsed.To16(), 128 +} + +// cidrKey builds the key of the CIDR of bits length containing the IP. +// It formats through net.IPNet like NormalizeCIDR, so writes and lookups agree. +func cidrKey(parsed net.IP, bits, maxBits int) string { + mask := net.CIDRMask(bits, maxBits) + ipNet := net.IPNet{IP: parsed.Mask(mask), Mask: mask} + return ipNet.String() +}