Net::Netmask(3pm)

πŸ’– Net::Netmask β€” the manual page (aka Net::Netmask(3pm), from User Contributed Perl Documentation) ✨


🌸 NAME 🌸

Net::Netmask β€” bestie, it parses, manipulates and looks up IP network blocks. That's the whole vibe. πŸ’…


πŸŽ€ SYNOPSIS πŸŽ€

okay so here's the group chat highlights of what you can actually do with this thing:

use Net::Netmask;

$block = Net::Netmask->safe_new(network block)
$block = Net::Netmask->safe_new(network block, netmask)
$block = Net::Netmask->new2(network block)
$block = Net::Netmask->new2(network block, netmask)
$block = Net::Netmask->new(network block)   # Don't use in new code!
$block = Net::Netmask->new(network block, netmask)   # Don't use in new code!

print $block;                      # a.b.c.d/bits or 1:2:3::4/bits
print $block->base()
print $block->mask()
print $block->hostmask()
print $block->bits()
print $block->size()
print $block->maxblock()
print $block->broadcast()
print $block->next()
print $block->match($ip);
print $block->nth(1, [$bitstep]);
print $block->protocol();

if ($block->sameblock("network block")) ...
if ($block->cmpblocks("network block")) ...

$newblock = $block->nextblock([count]);

for $ip ($block->enumerate([$bitstep])) { }

for $zone ($block->inaddr()) { }

my $table = {};
$block->storeNetblock([$table])
$block->deleteNetblock([$table])
@missingblocks = $block->cidrs2inverse(@blocks)

$block = findNetblock(ip, [$table])
$block = findOuterNetblock(ip, [$table])
@blocks = findAllNetblock(ip, [$table])
if ($block->checkNetblock([$table]) ...
$block2 = $block1->findOuterNetblock([$table])
@blocks = dumpNetworkTable([$table])

@blocks = range2cidrlist($beginip, $endip);
@blocks = cidrs2cidrs(@blocks_with_dups)

@listofblocks = cidrs2contiglists(@blocks);

@blocks = sort @blocks
@blocks = sort_network_blocks(@blocks)

@sorted_ip_addrs = sort_by_ip_address(@unsorted_ip_addrs)

✨ DESCRIPTION ✨

Net::Netmask parses and understands IPv4 and IPv6 CIDR blocks β€” and if you're like "CIDR who?" the original is pointing you at <https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing> for the lore. It's built with an object-oriented interface, so all the functions are methods that operate on a Net::Netmask object. Obsessed, honestly. 🫢

These methods give you pretty much every kind of info about a network block you could possibly want. No cap.

There are also functions to shove a network block into a table and then later look up network blocks by IP address using that table. There are functions to turn an IP address range into a list of CIDR blocks. There are functions to turn a list of CIDR blocks into a list of IP addresses. ✨

And there's a function for sorting by text IP address. Sorting? She does that.

All functions understand both IPv4 and IPv6. Matches, finds, etc, will always return false when an IPv4 address is matched against an IPv6 address. They don't mix like that. πŸ’€

IPv6 support was added in 1.9104. So if that's you, hi, glad you're here. πŸ¦‹

🌈 CONSTRUCTING 🌈

Net::Netmask objects are created with an IP address and optionally a mask. There are so many forms she recognises, it's giving ✨versatility✨:

  • '216.240.32.0/24' β€” the preferred IPv4 form. main character energy.
  • '216.240.32.0:255.255.255.0'
  • '216.240.32.0-255.255.255.0'
  • '216.240.32.0', '255.255.255.0'
  • '216.240.32.0', '0xffffff00'
  • '216.240.32.0 - 216.240.32.255'
  • '216.240.32.4' β€” this one's a /32 block.
  • 'default' or 'any' β€” 0.0.0.0/0 (the default route).
  • '216.240.32.0#0.0.31.255' β€” a hostmask (as used by Cisco access-lists β€” that is, the hostmask is the bitwise inverse of a netmask).
  • '2001:db8:1234:5678::/64' β€” the preferred IPv6 form. πŸ’œ
  • '2001:db8:1234:5678::9876' β€” a /128 block.
  • 'default6' or 'any6' β€” ::/0 (the default route).

There are two constructor methods: new and safe_new (also known as new2).

safe_new differs from new in that it will return undef for invalid netmasks, while new will return a netmask object even if the constructor could not figure out what the network block should be. so unserious of it. πŸ™ˆ

With new, the error string can be found as $block->{'ERROR'}. With safe_new the error can be found as Net::Netmask::errstr or $Net::Netmask::error.

IMPORTANT: seriously bestie, you want to use safe_new or new2 (new2 is a synonym for safe_new) in new code! πŸ‘‘

πŸ’… Abbreviated IPv4 netblocks (the shortnet situation) πŸ’…

As of version 2.000, the following abbreviated IPv4 netblocks are not accepted by default, but can be accepted with options:

  • '216.240.32' β€” always a /24 block.
  • '216.240' β€” always a /16 block.
  • '140' β€” always a /8 block.
  • '216.240.32/24'
  • '216.240/16'

To accept these, you can call the constructor with a "shortnet" option set to a true value. Example:

my $block = Net::Netmask->safe_new("216.240/16", shortnet => 1);

For compatibility with older codebases, it's also possible to change the default to use the old behavior. To do this, you can set the $Net::Netmask::SHORTNET_DEFAULT variable to a true value. It is recommended that this be done by localizing the variable. Example:

local $Net::Netmask::SHORTNET_DEFAULT = 1
my $block = Net::Netmask->safe_new("216.240/16");
⚠️ Please be aware that there are security implications to this as other Perl modules, system libraries, or utilities may not parse these addresses the same way. This is why the default was changed. 🚨

For instance:

perl -MNet::Netmask -E "say Net::Netmask->safe_new("10.20", shortnet => 1)"

Will print "10.2.0.0/16". However:

perl -MSocket -E "say inet_ntoa(inet_aton('10.20'))"

Will often print "10.0.0.20" which is obviously very different, and if the Net::Netmask module was used to check an IP against an ACL, and then another program was executed (that uses inet_aton(), for instance), the ACL processing might not match the connection.

Thus, it is advised to use this with caution. 🫢


✨ METHODS ✨

okay so this is the good part β€” all the things you can actually do with a Net::Netmask object. every single one, no skips, bestie πŸ’…


->desc()

Hands you a lil description of the network block, like "216.240.32.0/19" or "2001:db8:1234::/48". oh and it's also what you get from overloaded stringification, so when you stringify the object this is the vibe you get πŸŽ€

->base()

The base address of the network block, as a string. so like "216.240.32.0". or "2001:db8:1234::/48". heads up though β€” base gives you zero hints about how big the block is. it's just the address, that's it, no size energy 😌

->mask()

The netmask, as a string. e.g. "255.255.255.0" or "ffff:ffff:ffff:ffff::".

->hostmask()

The host mask β€” which is literally just the netmask's opposite. e.g. "0.0.0.255" or "::ffff:ffff:ffff:ffff".

->bits()

The netmask expressed as a number of bits sitting in the network portion of this block's address. e.g. 24. so cute, so small ✨

->size()

How many IP addresses live in the block. e.g. 256. ⚠️ little plot twist: for IPv6 addresses this comes back as a Math::BigInt object, not a plain number. so treat it accordingly, no cap.

->broadcast()

The block's broadcast address β€” aka the very last IP address inside the block, the final boss πŸ‘‘

  • 192.168.1.0/24 β†’ 192.168.1.255
  • 2001:db8::/64 β†’ 2001:db8::ffff:ffff:ffff:ffff

->next()

The first IP address after the block β€” specifically, the one right after the broadcast address. it's giving exit sign πŸšͺ

  • 192.168.1.0/24 β†’ 192.168.2.0
  • 2001:db8:0:1::/64 β†’ 2001:db8:0:2::/64

->first() & ->last()

Synonyms for ->base() and ->broadcast(). that's it, that's the whole thing, they're just nicknames 🫢

->protocol()

Added in version 1.9102.

Returns the address family / protocol that this block represents. either 'IPv4' or 'IPv6'. short king of a method honestly.

->match($ip)

Returns a true value if the IP number $ip matches the given network β€” meaning $ip sits somewhere between base() and broadcast(). for example, if we have the network 192.168.1.0/24, then:

192.168.0.255 => 0
192.168.1.0   => "0 "
192.168.1.1   => 1
...
192.168.1.255 => 255

$ip should be a dotted-quad (e.g. "192.168.66.3") or an IPv6 address in standard notation (e.g. "2001:db8::1").

okay so here's the tea: it just happens that the return value is the position within the block. and since zero is a totally legal position, the true string "0 " gets returned in its place instead. yes, with the space. and yes, "0 " is numerically zero, which is the chaotic part πŸ’€. so if you actually want to know the position inside the block, this idiom is the move:

$pos = $block->match($ip) or die;
$pos += 0;

->maxblock()

Most of the time you literally cannot figure out the size of a network block just from its base address. for example, with the network block '216.240.32.0/27', if all you had was the '216.240.32.0' part, you'd have no idea what the size is. that address could be anything from a '/23' all the way to a '/32'. so maxblock() gives you the size of the largest block the current block's address would allow it to be. the size is given in bits. e.g. 23. detective work but make it bits πŸ”

->enumerate([$bitstep)

Returns a list of every IP address in the block. 🚨 be VERY careful, do not call this on large blocks β€” we are serious, this is the method that will eat your memory and your afternoon. the IP addresses come back as strings. e.g. '216.240.32.0', '216.240.32.1', ... '216.240.32.255'.

If you hand it the optional argument, it steps through the block in increments of a given network size. to step by 4, use a bitstep of 30 (as in a /30 network). cute little stride πŸ¦‹

Note that for IPv6, this will return failure if more than 1,000,000,000 addresses would be returned. that's the hard line, bestie.

->nth($index, [$bitstep])

Returns the *n*th element of the array that enumerate would hand you if you'd called it. so:

  • to get the first usable address in a block, use nth(1)
  • to get the broadcast address, use nth(-1)
  • to get the last usable address, use nth(-2)

indexing without the emotional damage of actually enumerating. iconic behavior πŸ’…

->inaddr()

Returns an inline list of tuples.

For IPv4:

There's a tuple for each DNS zone name (at the /24 level) in the block. if the block is smaller than a /24, then the zone of the enclosing /24 gets returned instead.

Each tuple contains: the DNS zone name, the last component of the first IP address in the block in that zone, and the last component of the last IP address in the block in that zone.

Examples: the list returned for the block '216.240.32.0/23' would be:

'32.240.216.in-addr.arpa', 0, 255,
'33.240.216.in-addr.arpa', 0, 255

The list returned for the block '216.240.32.64/27' would be:

'32.240.216.in-addr.arpa', 64, 95

For IPv6:

A list is returned with each DNS zone name at the shortest-prefix length possible. this one is not returned as a tuple β€” it's just a list of strings.

Examples: the list returned for the block '2002::/16' would be a one element list, containing just:

2.0.0.2.ip6.arpa'

The list for '2002::/17' would return a two element list containing:

'0.2.0.0.2.ip6.arpa' and '1.2.0.0.2.ip6.arpa'

->nextblock([$count])

Without a $count, it returns the next block of the same size right after the current one. with a count, it returns the *N*th block after the current one. a count of -1 returns the previous block. and if you go out of legal address space, you get undef. no notes, just vibes 🧚

->sameblock($block)

Compares two blocks. the second block gets auto-converted from a string if it isn't already a Net::Netmask object. returns 1 if they're identical. twin check πŸ’–

->cmpblocks($block)

Compares two blocks. the second block gets auto-converted from a string if it isn't already a Net::Netmask object. returns -1, 0, or 1, depending on which one has the lower base address β€” or which one is larger, if they happen to share the same base address. it's giving ranked choice πŸ†

->contains($block)

Compares two blocks. the second block gets auto-converted from a string if it isn't already a Net::Netmask object. returns 1 if the second block fits inside the first block. returns 0 otherwise. nesting assessment, periodt.

->storeNetblock([$t])

Adds the current block to a table of network blocks. that table can then be used to query which network block a given IP address lives in. obsessed with the efficiency honestly πŸ”₯

The optional argument lets you have more than one table. by default an internal table is used β€” but if you need more than one table, supply a reference to a HASH to store the data in.

->deleteNetblock([$t])

Deletes the current block from a table of network blocks. bye bestie πŸ‘‹

The optional argument lets you have more than one table. by default an internal table is used β€” but if you need more than one table, supply a reference to a HASH to store the data in.

->checkNetblock([$t])

Returns true if the netblock is already in the network table. a little "are you on the guest list" moment ✨

->tag($name [, $value])

Tag network blocks with your own data, like little sticky notes πŸŽ€ the first argument is the name of your tag (a hash key) and the second argument β€” if you pass one β€” is the new value. the old value is returned. so you can peek at what was there before you rewrote it, sneaky.

->split($parts)

Splits a netmask into a number of sub netblocks. this number must be a base 2 number (2, 4, 8, 16, etc.) and the number must not exceed the number of IPs within this netmask. math says no if you try it 🚫

For instance, this:

Net::Netmask->safe_new( '10.0.0.0/24' )->split(2)

is equivalent to this:

( Net::Netmask( '10.0.0.0/25'), Net::Netmask( '10.0.0.128/25' ) )

one block in, two blocks out, slay βœ‚οΈ

✨ METHOD/FUNCTION COMBOS ✨

These ones are like... the collab tracks. Method meets function, they link up, magic happens πŸ’–

  • findOuterNetblock(ip, [$t]) β€” bestie, this one goes digging through your table of network blocks (the one you built with storeNetBlock) to see if any of them contain the IP address you handed it. And plot twist: that IP can be a plain ol' string or a Net::Netmask object (that's the method-invocation version πŸ‘‘). If more than one block in the table is holding that IP address or block, the largest network block is the one that comes back.

What you get back is either a Net::Netmask object or undef. That's it, that's the whole vibe.

  • cidrs2inverse(block, @listOfBlocks) β€” okay so you give this one a block and a whole list of blocks, and it returns a list of blocks for all the IP addresses that are in your block but not in the list. Basically it's finding the gaps πŸ•³οΈβœ¨

The block gets auto-converted from a string if it isn't already a Net::Netmask object, no stress. The list of blocks should be Net::Netmask objects though, fr fr.

Return value: a list of Net::Netmask objects. Slay.


πŸ’… OVERLOADING πŸ’…

We're doing operator magic over here, no cap:

  • "" β€” stringification is overloaded to be the ->desc() method. So when you use a block in string context, you're lowkey calling desc(). Periodt.
  • cmp β€” numerical and string comparisons have both been overloaded to the ->cmpblocks() method. Why do we care? Because now you can sort blocks without writing a custom sort function. Iconic behavior honestly πŸŽ€

🌈 FUNCTIONS 🌈

  • sort_by_ip_address β€” so this function lives in Net::Netmask simply because there did not seem to be a better place to put it on CPAN 😭 (real talk). Turns out there's exactly one way to sort dotted-quads (a.b.c.d) that's faster than every other way, and this is that way. Use it like sort_by_ip_address(@list_of_ips). That was the theory anyway β€” then someone sent a faster version... πŸ™ˆ

Also! It will sort IPv6 addresses too, but it is not performance optimized. It is, however, correct. We take those πŸ‘‘

  • sort_network_blocks β€” this is your function for sorting Net::Netmask objects, and it's faster than the simpler sort @blocks that also works. Same energy, more speed πŸ”₯
  • findNetblock(ip, [$t]) β€” searches the table of network blocks (the one made with storeNetBlock) to see if any of them contain the given IP address. Here the IP address is expected to be a string. And heads up: if more than one block in the table contains that IP, the smallest network block is the one that comes back. (Yeah, it's the opposite of findOuterNetblock β€” they're foils, it's giving enemies to lovers πŸ’€)

Returns either a Net::Netmask object or undef.

  • findAllNetblock(ip, [$t]) β€” searches the table of network blocks (again, created with storeNetBlock) for any that contain the given IP address. The IP address is expected to be a string here too. And this time? ALL the network blocks in the table that contain that IP get returned. No picking favorites.

Return value is a list of Net::Netmask objects. Collect them all 🫢

  • dumpNetworkTable([$t]) β€” hands you back a list of the networks in a network table (as created by ->storeNetblock()). Simple. Clean. A lil peek behind the curtain ✨
  • range2cidrlist($startip, $endip) β€” give it a range of IP addresses, get back a list of blocks that span that range. It's giving glow up.

For example, range2cidrlist('216.240.32.128', '216.240.36.127') will return a list of Net::Netmask objects that correspond to:

216.240.32.128/25
216.240.33.0/24
216.240.34.0/23
216.240.36.0/25
  • cidrs2contiglists(@listOfBlocks) β€” cidrs2contiglists rearranges a list of Net::Netmask objects so that contiguous sets end up in sublists, and each sublist is discontiguous with the next one. Like seating chart drama but for IP blocks, and everyone ends up with their people πŸ’–

For example, given a list of Net::Netmask objects corresponding to these blocks:

216.240.32.128/25
216.240.33.0/24
216.240.36.0/25

cidrs2contiglists will return a list with two sublists:

216.240.32.128/25 216.240.33.0/24

216.240.36.0/25

And overlapping blocks? They get placed in the same sublist. No drama, just adjacency 🌸

  • cidrs2cidrs(@listOfBlocks) β€” cidrs2cidrs collapses a list of Net::Netmask objects by combining adjacent blocks into larger blocks. It returns a list of blocks that covers exactly the same IP space. Overlapping blocks get collapsed too. Consolidation queen πŸ‘‘

πŸ‘‘ AUTHORS πŸ‘‘

  • Joelle Maslak <jmaslak@antelope.net> (current maintainer)
  • David Muir Sharnoff (original creator/author)

πŸ“œ LICENSE πŸ“œ

  • Copyright (C) 1998-2006 David Muir Sharnoff.
  • Copyright (C) 2011-2013 Google, Inc.
  • Copyright (C) 2018-2021 Joelle Maslak

This module may be used, modified and redistributed under the same terms as Perl itself.


okay bestie that's the whole page, we made it to the end and I'm so proud of us 😭🫢 this one came from perl v5.40.1, dated 2025-12-18, and it's Net::Netmask(3pm) β€” read it, love it, go subnet something cute with it. bye!! πŸ’–βœ¨πŸŽ€πŸ¦‹

πŸ“¦ where this page comes from