-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add a function to convert IP ranges to a list of IP prefixes (#4)
* implement a function to convert an IP range to a list of prefixes * add a IPAddressRange.Prefixes method
- Loading branch information
1 parent
3423521
commit d8c074a
Showing
4 changed files
with
164 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package connectip | ||
|
||
import "net/netip" | ||
|
||
// rangeToPrefixes converts an IP range defined by start and end addresses | ||
// into a slice of CIDR prefixes that exactly cover the range. | ||
// It assumes that the start and end addresses are of the same IP version, | ||
// and that the start address is less than or equal to the end address. | ||
func rangeToPrefixes(start, end netip.Addr) []netip.Prefix { | ||
var prefixes []netip.Prefix | ||
for current := start; current.Compare(end) <= 0; { | ||
// find the largest prefix that fits within our remaining range | ||
prefix := findLargestPrefix(current, end) | ||
prefixes = append(prefixes, prefix) | ||
|
||
// move to the next IP address after this prefix | ||
lastIP := lastIPInPrefix(prefix) | ||
if lastIP.Compare(end) >= 0 { | ||
break | ||
} | ||
current = lastIP.Next() | ||
} | ||
return prefixes | ||
} | ||
|
||
// findLargestPrefix finds the largest prefix starting at 'start' that doesn't exceed 'end' | ||
func findLargestPrefix(start, end netip.Addr) netip.Prefix { | ||
if start == end { | ||
return netip.PrefixFrom(start, start.BitLen()) | ||
} | ||
|
||
// Start with the smallest possible prefix (/32 for IPv4, /128 for IPv6), | ||
// and try progressively larger prefixes until we find one that exceeds our range. | ||
var prefixLen int | ||
for prefixLen = start.BitLen(); prefixLen > 0; prefixLen-- { | ||
prefix := netip.PrefixFrom(start, prefixLen-1) // Try one bit larger | ||
if lastIPInPrefix(prefix).Compare(end) > 0 || !isAligned(start, prefixLen-1) { | ||
break | ||
} | ||
} | ||
return netip.PrefixFrom(start, prefixLen) | ||
} | ||
|
||
// lastIPInPrefix returns the last IP address in a prefix | ||
func lastIPInPrefix(prefix netip.Prefix) netip.Addr { | ||
addr := prefix.Addr() | ||
bits := addr.As16() | ||
|
||
hostBits := addr.BitLen() - prefix.Bits() | ||
|
||
// Set all host bits to 1 | ||
for i := len(bits) - 1; i >= 0 && hostBits > 0; i-- { | ||
bitsInThisByte := min(8, hostBits) | ||
mask := byte((1 << bitsInThisByte) - 1) | ||
bits[i] |= mask | ||
hostBits -= bitsInThisByte | ||
} | ||
|
||
if addr.Is4() { | ||
return netip.AddrFrom4([4]byte(bits[12:16])) | ||
} | ||
return netip.AddrFrom16(bits) | ||
} | ||
|
||
// isAligned checks if an IP address is aligned to the given prefix length | ||
func isAligned(addr netip.Addr, prefixLen int) bool { | ||
bits := addr.As16() | ||
|
||
hostBits := addr.BitLen() - prefixLen | ||
for i := len(bits) - 1; i >= 0 && hostBits > 0; i-- { | ||
bitsInThisByte := min(8, hostBits) | ||
mask := byte((1 << bitsInThisByte) - 1) | ||
if bits[i]&mask != 0 { | ||
return false | ||
} | ||
hostBits -= bitsInThisByte | ||
} | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package connectip | ||
|
||
import ( | ||
"fmt" | ||
"net/netip" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestIPRanges(t *testing.T) { | ||
tests := []struct { | ||
start, end netip.Addr | ||
want []netip.Prefix | ||
}{ | ||
{ | ||
start: netip.MustParseAddr("192.168.1.1"), | ||
end: netip.MustParseAddr("192.168.1.1"), | ||
want: []netip.Prefix{netip.MustParsePrefix("192.168.1.1/32")}, | ||
}, | ||
{ | ||
start: netip.MustParseAddr("192.168.1.0"), | ||
end: netip.MustParseAddr("192.168.1.1"), | ||
want: []netip.Prefix{netip.MustParsePrefix("192.168.1.0/31")}, | ||
}, | ||
{ | ||
start: netip.MustParseAddr("192.168.1.1"), | ||
end: netip.MustParseAddr("192.168.1.2"), | ||
want: []netip.Prefix{netip.MustParsePrefix("192.168.1.1/32"), netip.MustParsePrefix("192.168.1.2/32")}, | ||
}, | ||
{ | ||
start: netip.MustParseAddr("192.168.1.0"), | ||
end: netip.MustParseAddr("192.168.1.255"), | ||
want: []netip.Prefix{netip.MustParsePrefix("192.168.1.0/24")}, | ||
}, | ||
{ | ||
start: netip.MustParseAddr("10.0.0.0"), | ||
end: netip.MustParseAddr("10.1.0.255"), | ||
want: []netip.Prefix{netip.MustParsePrefix("10.0.0.0/16"), netip.MustParsePrefix("10.1.0.0/24")}, | ||
}, | ||
{ | ||
start: netip.MustParseAddr("2001:0db8:85a3::8a2e:0370:7334"), | ||
end: netip.MustParseAddr("2001:0db8:85a3::8a2e:0370:7334"), | ||
want: []netip.Prefix{netip.MustParsePrefix("2001:0db8:85a3::8a2e:0370:7334/128")}, | ||
}, | ||
{ | ||
start: netip.MustParseAddr("2001:db8::0"), | ||
end: netip.MustParseAddr("2001:db8::ffff:ffff:ffff:ffff"), | ||
want: []netip.Prefix{netip.MustParsePrefix("2001:db8::/64")}, | ||
}, | ||
{ | ||
start: netip.MustParseAddr("2001:db8::1"), | ||
end: netip.MustParseAddr("2001:db8::2"), | ||
want: []netip.Prefix{netip.MustParsePrefix("2001:db8::1/128"), netip.MustParsePrefix("2001:db8::2/128")}, | ||
}, | ||
{ | ||
start: netip.MustParseAddr("2001:db8:1234:5678::"), | ||
end: netip.MustParseAddr("2001:db8:1234:5679::"), | ||
want: []netip.Prefix{ | ||
netip.MustParsePrefix("2001:db8:1234:5678::/64"), | ||
netip.MustParsePrefix("2001:db8:1234:5679::/128"), | ||
}, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(fmt.Sprintf("%s-%s", test.start, test.end), func(t *testing.T) { | ||
prefixes := rangeToPrefixes(test.start, test.end) | ||
require.Equal(t, test.want, prefixes) | ||
}) | ||
} | ||
} |