Simplify subnet math with Perl's Net::Patricia
Jeffrey Wilson
Network Architect | Expert in Scalable Infrastructure, Cybersecurity, and Automation
There's more than one way to do it
The very mantra that defines Perl (TMTOWTDI) also causes some hesitancy in its adoption, in my observation. "Choose your own adventure" was once a leading franchise in the Scholastic book sales back when I was a kid, but I don't know about kids these days ... Before I get to the point of yelling at everyone to "get off my lawn" let me turn the corner from philosophy to tutorial. (But first, one last parting shot from Perl's founder, Larry Wall: Three Virtues.)
If you haven't already learned of MetaCpan and App::cpanminus, then take a minute and catch up on those topics. And while you're popping open new tabs on your browser, add this one too: Net::Patricia. (My introduction to Net::Patricia came from reading something written by jtk.)
If you're familiar with the basics of IP routing, then handing you the link to Net::Patricia (I'll just call it NP henceforth) is like giving a light saber to a Jedi master. The logic behind NP is the same as is used by an IP router: choose the longest matching prefix, and go!
The logic behind Net::Patricia is the same as is used by an IP router.
I explain further below via a couple of code snippets, below.
TL;DR:
By example:
Let's say I have a service running somewhere in my shop that will tell me the names and cidrs of all the subnets in our core routers' FIBs. (I do have that service, but that's another write-up that I haven't written up yet.)
Step 1: load up your NP object with all the rows of named subnets
use Net::Patricia;
use LWP::UserAgent;
use Text::CSV;
my $ua = new LWP::UserAgent(
ssl_opts => { verify_hostname => 0 },
cookie_jar => {},
);
my $resp = $ua->get(q{https://ipmanager.internal/subnets.php});
my $csv = new Text::CSV;
my $str = $resp->content;
my $pt = new Net::Patricia;
{
# DATA FORMAT: CSV
# "SubnetName","CIDR"
open(my $fh,"<",\$str);
while(my $row = $csv->getline($fh)) {
# some knucklehead put commas in our subnet names
$row->[0] =~ s/,//g;
$pt->add_string($row->[1],$row->[0]);
}
}
Step 2: Query your subnets
Now, any time you want to know what subnet a particular IP address comes from, you ask your $pt variable if it can match_string($ip). I throw it into a subroutine for convenience, er - efficiency. Laziness! Now that's a virtue ??.
# Given an IP address, return the subnet name
sub ip2subnetName {
my ($ip) = @_;
return "Unknown" unless $ip && ($ip =~ /\d+\.\d+\.\d+\.\d+/);
my $res = $pt->match($ip);
# Catch all for unnamed or unknown subnet
$res ||= "Core Router FIB entry";
return $res;
}
I recently learned that Socket::inet_aton's return value is a nifty trick for validating an IP address format. If the return value is false or undef then your input isn't a correctly formatted IP address:
use Socket q/inet_aton/;
sub is_valid_ip {
return inet_aton($_[0]);
}
Step 3: integrate this technique into existing code
So let's say you've already come up with a log parser that can find all the DHCPACK messages to tell you what MAC landed on which IP ... now plug in this NP trick, and you can group together DHCP utilization by named subnet!
The code snippets above come from my DHCP log parser that feeds my monthly Power BI report. I also use NP to enforce the whitelist on my RTBH project.
And so concludes my brief introduction. Comment with your questions. Be sure to scroll back to the links up top for more details and other features available in the Net::Patricia module.
In IT, there are no problems—only IT solutions. Or you can just throw AI at it.
5 个月There is always more Perl to do! :D Ride your Moose into battle!