Manipulate ipv4/ipv6 netblocks in cidr notation
use Net::CIDR; use Net::CIDR ':all'; print join("\n", Net::CIDR::range2cidr("192.68.0.0-192.68.255.255", "10.0.0.0-10.3.255.255")) . "\n"; # # Output from above: # # 192.68.0.0/16 # 10.0.0.0/14 print join("\n", Net::CIDR::range2cidr( "dead:beef::-dead:beef:ffff:ffff:ffff:ffff:ffff:ffff")) . "\n"; # # Output from above: # # dead:beef::/32 print join("\n", Net::CIDR::range2cidr("192.68.1.0-192.68.2.255")) . "\n"; # # Output from above: # # 192.68.1.0/24 # 192.68.2.0/24 print join("\n", Net::CIDR::cidr2range("192.68.0.0/16")) . "\n"; # # Output from above: # # 192.68.0.0-192.68.255.255 print join("\n", Net::CIDR::cidr2range("dead::beef::/46")) . "\n"; # # Output from above: # # dead:beef::-dead:beef:3:ffff:ffff:ffff:ffff:ffff @list=("192.68.0.0/24"); @list=Net::CIDR::cidradd("192.68.1.0-192.68.1.255", @list); print join("\n", @list) . "\n"; # # Output from above: # # 192.68.0.0/23 print join("\n", Net::CIDR::cidr2octets("192.68.0.0/22")) . "\n"; # # Output from above: # # 192.68.0 # 192.68.1 # 192.68.2 # 192.68.3 print join("\n", Net::CIDR::cidr2octets("dead::beef::/46")) . "\n"; # # Output from above: # # dead:beef:0000 # dead:beef:0001 # dead:beef:0002 # dead:beef:0003 @list=("192.68.0.0/24"); print Net::CIDR::cidrlookup("192.68.0.12", @list); # # Output from above: # # 1 @list = Net::CIDR::addr2cidr("192.68.0.31"); print join("\n", @list); # # Output from above: # # 192.68.0.31/32 # 192.68.0.30/31 # 192.68.0.28/30 # 192.68.0.24/29 # 192.68.0.16/28 # 192.68.0.0/27 # 192.68.0.0/26 # 192.68.0.0/25 # 192.68.0.0/24 # 192.68.0.0/23 # [and so on] print Net::CIDR::addrandmask2cidr("195.149.50.61", "255.255.255.248")."\n"; # # Output from above: # # 195.149.50.56/29
The Net::CIDR package contains functions that manipulate lists of \s-1IP\s0 netblocks expressed in \s-1CIDR\s0 notation. The Net::CIDR functions handle both IPv4 and IPv6 addresses. Each element in the @range_list is a string \*(L"start-finish\*(R", where \*(L"start\*(R" is the first \s-1IP\s0 address and \*(L"finish\*(R" is the last \s-1IP\s0 address. range2cidr() converts each range into an equivalent \s-1CIDR\s0 netblock. It returns a list of netblocks except in the case where it is given only one parameter and is called in scalar context.
For example:
@a=Net::CIDR::range2cidr("192.68.0.0-192.68.255.255");
The result is a one-element array, with $a[0] being \*(L"192.68.0.0/16\*(R". range2cidr() processes each \*(L"start-finish\*(R" element in @range_list separately. But if invoked like so:
$a=Net::CIDR::range2cidr("192.68.0.0-192.68.255.255");
The result is a scalar \*(L"192.68.0.0/16\*(R".
Where each element cannot be expressed as a single \s-1CIDR\s0 netblock range2cidr() will generate as many \s-1CIDR\s0 netblocks as are necessary to cover the full range of \s-1IP\s0 addresses. Example:
@a=Net::CIDR::range2cidr("192.68.1.0-192.68.2.255");
The result is a two element array: (\*(L"192.68.1.0/24\*(R",\*(L"192.68.2.0/24\*(R");
@a=Net::CIDR::range2cidr( "d08c:43::-d08c:43:ffff:ffff:ffff:ffff:ffff:ffff");
The result is an one element array: (\*(L"d08c:43::/32\*(R") that reflects this IPv6 netblock in \s-1CIDR\s0 notation.
range2cidr() does not merge adjacent or overlapping netblocks in @range_list. The cidr2range() functions converts a netblock list in \s-1CIDR\s0 notation to a list of \*(L"start-finish\*(R" \s-1IP\s0 address ranges:
@a=Net::CIDR::cidr2range("10.0.0.0/14", "192.68.0.0/24");
The result is a two-element array: (\*(L"10.0.0.0-10.3.255.255\*(R", \*(L"192.68.0.0-192.68.0.255\*(R").
@a=Net::CIDR::cidr2range("d08c:43::/32");
The result is a one-element array: (\*(L"d08c:43::-d08c:43:ffff:ffff:ffff:ffff:ffff:ffff\*(R").
cidr2range() does not merge adjacent or overlapping netblocks in @cidr_list. The addr2cidr function takes an \s-1IP\s0 address and returns a list of all the \s-1CIDR\s0 netblocks it might belong to:
@a=Net::CIDR::addr2cidr('192.68.0.31');
The result is a thirtythree-element array: ('192.68.0.31/32', '192.68.0.30/31', '192.68.0.28/30', '192.68.0.24/29', [and so on]) consisting of all the possible subnets containing this address from 0.0.0.0/0 to address/32.
Any addresses supplied to addr2cidr after the first will be ignored. It works similarly for IPv6 addresses, returning a list of one hundred and twenty nine elements. The addrandmask2cidr function takes an \s-1IP\s0 address and a netmask, and returns the \s-1CIDR\s0 range whose size fits the netmask and which contains the address. It is an error to supply one parameter in IPv4-ish format and the other in IPv6-ish format, and it is an error to supply a netmask which does not consist solely of 1 bits followed by 0 bits. For example, '255.255.248.192' is an invalid netmask, as is '255.255.255.32' because both contain 0 bits in between 1 bits.
Technically speaking both of those *are* valid netmasks, but a) you'd have to be insane to use them, and b) there's no corresponding \s-1CIDR\s0 range. cidr2octets() takes @cidr_list and returns a list of leading octets representing those netblocks. Example:
@octet_list=Net::CIDR::cidr2octets("10.0.0.0/14", "192.68.0.0/24");
The result is the following five-element array: (\*(L"10.0\*(R", \*(L"10.1\*(R", \*(L"10.2\*(R", \*(L"10.3\*(R", \*(L"192.68.0\*(R").
For IPv6 addresses, the hexadecimal words in the resulting list are zero-padded:
@octet_list=Net::CIDR::cidr2octets("::dead:beef:0:0/110");
The result is a four-element array: (\*(L"0000:0000:0000:0000:dead:beef:0000\*(R", \*(L"0000:0000:0000:0000:dead:beef:0001\*(R", \*(L"0000:0000:0000:0000:dead:beef:0002\*(R", \*(L"0000:0000:0000:0000:dead:beef:0003\*(R"). Prefixes of IPv6 \s-1CIDR\s0 blocks should be even multiples of 16 bits, otherwise they can potentially expand out to a 32,768-element array, each! The cidradd() functions allows a \s-1CIDR\s0 list to be built one \s-1CIDR\s0 netblock at a time, merging adjacent and overlapping ranges. $block is a single netblock, expressed as either \*(L"start-finish\*(R", or \*(L"address/prefix\*(R". Example:
@cidr_list=Net::CIDR::range2cidr("192.68.0.0-192.68.0.255"); @cidr_list=Net::CIDR::cidradd("10.0.0.0/8", @cidr_list); @cidr_list=Net::CIDR::cidradd("192.68.1.0-192.68.1.255", @cidr_list);
The result is a two-element array: (\*(L"10.0.0.0/8\*(R", \*(L"192.68.0.0/23\*(R"). IPv6 addresses are handled in an analogous fashion. Search for $ip in @cidr_list. $ip can be a single \s-1IP\s0 address, or a netblock in \s-1CIDR\s0 or start-finish notation. lookup() returns 1 if $ip overlaps any netblock in @cidr_list, 0 if not. Validate whether $ip is a valid IPv4 or IPv6 address, or a \s-1CIDR\s0. Returns its argument or undef. Spaces are removed, and IPv6 hexadecimal address are converted to lowercase.
$ip with less than four octets gets filled out with additional octets, and the modified value gets returned. This turns \*(L"192.168/16\*(R" into a proper \*(L"192.168.0.0/16\*(R".
If $ip contains a \*(L"/\*(R", it must be a valid \s-1CIDR\s0, otherwise it must be a valid IPv4 or an IPv6 address.
A technically invalid \s-1CIDR\s0, such as \*(L"192.168.0.1/24\*(R" fails validation, returning undef.
Garbage in, garbage out. Always use cidrvalidate() before doing anything with untrusted input. Otherwise, \*(L"slightly\*(R" invalid input will work (extraneous whitespace is generally \s-1OK\s0), but the functions will croak if you're totally off the wall.
Sam Varshavchik <[email protected]>
With some contributions from David Cantrell <[email protected]>