Net-Subnet
view release on metacpan or search on metacpan
lib/Net/Subnet.pm view on Meta::CPAN
package Net::Subnet;
use strict;
use Socket;
BEGIN {
if (defined &Socket::inet_pton) {
Socket->import(qw(inet_pton AF_INET6));
} else {
require Socket6;
Socket6->import(qw(inet_pton AF_INET6));
}
};
use base 'Exporter';
our @EXPORT = qw(subnet_matcher subnet_classifier sort_subnets);
our $VERSION = '1.03';
sub cidr2mask_v4 {
my ($length) = @_;
return pack "N", 0xffffffff << (32 - $length);
}
sub cidr2mask_v6 {
my ($length) = @_;
return pack('B128', '1' x $length);
}
sub subnet_matcher {
@_ > 1 and goto &multi_matcher;
my ($net, $mask) = split m[/], shift;
return $net =~ /:/
? ipv6_matcher($net, $mask)
: ipv4_matcher($net, $mask);
}
sub ipv4_matcher {
my ($net, $mask) = @_;
$net = inet_aton($net);
$mask = $mask =~ /\./ ? inet_aton($mask) : cidr2mask_v4($mask);
my $masked_net = $net & $mask;
return sub { ((inet_aton(shift) // return !1) & $mask) eq $masked_net };
}
sub ipv6_matcher {
my ($net, $mask) = @_;
$net = inet_pton(AF_INET6, $net);
$mask = $mask =~ /:/ ? inet_pton(AF_INET6, $mask) : cidr2mask_v6($mask);
my $masked_net = $net & $mask;
return sub { ((inet_pton(AF_INET6,shift)//return!1) & $mask) eq $masked_net}
}
sub multi_matcher {
my @v4 = map subnet_matcher($_), grep !/:/, @_;
my @v6 = map subnet_matcher($_), grep /:/, @_;
return sub {
$_->($_[0]) and return 1 for $_[0] =~ /:/ ? @v6 : @v4;
return !!0;
}
}
use constant MATCHER => 0;
use constant SUBNET => 1;
sub subnet_classifier {
# MATCHER, SUBNET
my @v4 = map [ subnet_matcher($_), $_ ], grep !/:/, @_;
my @v6 = map [ subnet_matcher($_), $_ ], grep /:/, @_;
return sub {
$_->[MATCHER]->($_[0]) and return $_->[SUBNET]
for $_[0] =~ /:/ ? @v6 : @v4;
return undef;
}
}
sub sort_subnets {
my @unsorted;
for (@_) {
my ($net, $mask) = split m[/];
$mask = $net =~ /:/
? ($mask =~ /:/ ? inet_pton(AF_INET6, $mask) : cidr2mask_v6($mask))
: ($mask =~ /\./ ? inet_aton($mask) : cidr2mask_v4($mask));
$net = $net =~ /:/
? inet_pton(AF_INET6, $net)
: inet_aton($net);
push @unsorted, sprintf "%-16s%-16s%s", ($net & $mask), $mask, $_;
}
return map substr($_, 32), reverse sort @unsorted;
}
1;
__END__
=head1 NAME
Net::Subnet - Fast IP-in-subnet matcher for IPv4 and IPv6, CIDR or mask.
=head1 SYNOPSIS
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'
( run in 1.983 second using v1.01-cache-2.11-cpan-71847e10f99 )