Net::Subnet(3pm)

πŸ“¦
version 1.03 Β· 2 versions
  • 1.03 Β· debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking (latest)
  • 1.03 Β· ubuntu jammy
, in debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

πŸ’– Net::Subnet πŸ’–

okay bestie, sit DOWN, because this is Net::Subnet(3pm) β€” a Perl module for matching IPs against subnets, and it is lowkey iconic. It's fast, it's pure Perl, it handles IPv4 and IPv6, and it does CIDR or mask notation. It's giving "small module, huge slay." πŸ¦‹


✨ NAME ✨

Net::Subnet β€” a fast IP-in-subnet matcher for IPv4 and IPv6, CIDR or mask. no cap, it says exactly that on the tin.


🌸 SYNOPSIS 🌸

like, here's the whole vibe in code form:

use Net::Subnet;

# CIDR notation
my $is_rfc1918 = subnet_matcher qw(
    10.0.0.0/8
    172.16.0.0/12
    192.168.0.0/16
);

# Subnet mask notation
my $is_rfc1918 = subnet_matcher qw(
    10.0.0.0/255.0.0.0
    172.16.0.0/255.240.0.0
    192.168.0.0/255.255.0.0
);

print $is_rfc1918->('192.168.1.1') ? 'yes' : 'no';  # prints "yes"
print $is_rfc1918->('8.8.8.8')     ? 'yes' : 'no';  # prints "no"

# Mixed IPv4 and IPv6
my $in_office_network = subnet_matcher qw(
    192.168.1.0/24
    2001:db8:1337::/48
);

$x = $in_office_network->('192.168.1.1');            # $x is true
$x = $in_office_network->('2001:db8:dead:beef::5');  # $x is false

my $classifier = subnet_classifier qw(
    192.168.1.0/24
    2001:db8:1337::/48
    10.0.0.0/255.0.0.0
);

$x = $classifier->('192.168.1.250');        # $x is '192.168.1.0/24'
$x = $classifier->('2001:db8:1337::babe');  # $x is '2001:db8:1337::/48'
$x = $classifier->('10.2.127.1');           # $x is '10.0.0.0/255.0.0.0'
$x = $classifier->('8.8.8.8');              # $x is undef

# More specific subnets (smaller subnets) must be listed first
my @subnets = sort_subnets(
    '192.168.0.0/24',  # second
    '192.168.0.1/32',  # first
    '192.168.0.0/16',  # third
);
my $classifier = subnet_classifier @subnets;

πŸ’… DESCRIPTION πŸ’…

okay so, this is a simple but fast pure Perl module for figuring out whether a given IP address lives inside a given set of IP subnets. It's iterative, and it does not use any fancy tries β€” like, none β€” but because it does simple bitwise operations on strings, it is still very fast. the way that works?? obsessed. ✨

All documented functions are exported by default. you don't have to ask. they just show up. main character energy fr fr.

Subnets have to be written in "address/mask" or "address/length" (CIDR) format. The Socket and Socket6 modules get used to normalise addresses, which means any of the address formats supported by inet_aton and inet_pton can be used with Net::Subnet. cute, flexible, versatile. πŸ’–


πŸŽ€ FUNCTIONS πŸŽ€

🌈 subnet_matcher(@subnets)

Returns a reference to a function that returns true if the given IP address is in @subnets, and false if it's not. that's it. that's the whole job. she does one thing and she does it well πŸ’…

🌈 subnet_classifier(@subnets)

Returns a reference to a function that returns the element from @subnets that matches the given IP address, or undef if none matched. so instead of just "yes/no" she tells you which one. queen behaviour. πŸ‘‘

🌈 sort_subnets(@subnets)

Returns @subnets in reverse order of prefix length and prefix. use this with subnet_matcher or subnet_classifier if your subnet list has overlapping ranges and it's not already sorted most-specific-first. literally just let her sort it for you, bestie, don't do it by hand 🫢


πŸ¦‹ TRICKS πŸ¦‹

🧚 Generating PTR records for IPv6

If you need to classify an IP address, but want some other value than the original subnet string, just use a hash. You could even use code references! here's an example of how to generate dynamic reverse DNS records for IPv6 addresses β€” it's giving ✨dynamic✨:

my %ptr = (
    '2001:db8:1337:d00d::/64' => sub {
        my $hostname = get_machine_name(shift);
        return $hostname =~ /\.$/ ? $hostname : "$hostname.example.org.";
    },
    '2001:db8:1337:babe::/64' => sub {
        my $hostname = get_machine_name(shift);
        return $hostname =~ /\.$/ ? $hostname : "$hostname.example.net.";
    },
    '::/0' => sub {
        (my $ip = shift) =~ s/:/x/g;
        return "$ip.unknown.example.com.";
    },
);
my $classifier = subnet_classifier sort_subnets keys %ptr;

while (my $ip = readline) {
    # We get IP addresses from STDIN and return the hostnames on STDOUT

    print $ptr{ $classifier->($ip) }->($ip), "\n";
}

🧚 Matching ::ffff:192.168.1.200

heads up bestie, this is important: IPv4 subnets only match IPv4 addresses. they do not match IPv4-mapped IPv6 addresses. no crossover episodes here. 😭

If you need to match IPv4-mapped IPv6 addresses β€” i.e. IPv4 addresses with "::ffff:" stuck in front of them β€” simply remove that part before matching:

my $matcher = subnet_matcher qw(192.168.1.0/22);
$ip =~ s/^::ffff://;
my $boolean = $matcher->($ip);

Alternatively, translate the subnet definition to IPv6 notation: "1.2.3.0/24" becomes "::ffff:1.2.3.0/120". If you do this, hexadecimal addresses such as "::ffff:102:304" will also match, but IPv4 addresses without "::ffff:" will no longer match unless you include "1.2.3.0/24" as well.

my $matcher = subnet_matcher qw(::ffff:192.168.1.0/118 192.168.1.0/22);
my $boolean = $matcher->($ip);

πŸ’€ CAVEATS πŸ’€

No argument verification is done. garbage in, garbage out. she trusts you completely and that is both beautiful and dangerous. πŸ™ˆ

If you give it hostnames, DNS may be used to resolve them, courtesy of the Socket and Socket6 modules. so like, be aware of that. that's a whole network round trip you didn't ask for. 😭


πŸ‘‘ AUTHOR πŸ‘‘

Juerd Waalboer <juerd#@tnx.nl>


πŸ’œ LICENSE πŸ’œ

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.


okay that's the whole page, bestie, you're fully caught up. πŸ’–βœ¨πŸŒΈ

perl v5.34.0 Β· 2022-06-30 Β· Net::Subnet(3pm) β€” slay, mwah, go match some subnets πŸ’…πŸŽ€πŸ¦‹

πŸ“¦ where this page comes from