Simple-IPInfo/lib/Simple/IPInfo.pm
# ABSTRACT: Get IP/IPList Info (location, as number, etc)
package Simple::IPInfo;
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(
read_ipinfo
iterate_ipinfo
get_ipinfo
get_ip_loc
get_ip_as
ip_to_inet
inet_to_ip
cidr_to_range
);
use utf8;
use Data::Validate::IP qw/is_ipv4 is_ipv6 is_public_ipv4/;
use SimpleR::Reshape;
use Data::Dumper;
use JSON;
use File::Spec;
use Net::CIDR qw/cidr2range/;
use Socket qw/inet_aton inet_ntoa/;
use Memoize;
memoize( 'read_ipinfo' );
our $DEBUG = 0;
our $VERSION = 0.12;
my ( $vol, $dir, $file ) = File::Spec->splitpath( __FILE__ );
our $IPINFO_LOC_F = File::Spec->catpath( $vol, $dir, "inet_loc.csv" );
our $IPINFO_AS_F = File::Spec->catpath( $vol, $dir, "inet_as.csv" );
my @key = qw/country area isp country_code area_code isp_code as/;
our %UNKNOWN = map { $_ => '' } @key;
our %ERROR = map { $_ => 'error' } @key;
our %LOCAL = map { $_ => 'local' } @key;
sub cidr_to_range {
my ( $cidr, %opt ) = @_;
$opt{inet} //= 1;
my ( $addr_range ) = cidr2range( $cidr );
my @addr = split /-/, $addr_range;
return @addr unless ( $opt{inet} );
my @inet = map { unpack( 'N', inet_aton( $_ ) ) } @addr;
return @inet;
}
sub get_ipinfo {
my ( $ip_list, %opt ) = @_;
$opt{i} ||= 0;
$opt{return_arrayref} //= 1;
my $i = 0;
my $ip_inet = read_table(
$ip_list,
conv_sub => sub {
my ( $r ) = @_;
my ( $ip, $inet, $rr ) = calc_ip_inet( $r->[ $opt{i} ], \%opt );
return [ $i++, $inet ];
},
return_arrayref => 1,
);
my @sorted_ip_inet = sort { $a->[1] <=> $b->[1] } @$ip_inet;
my $get_ip_info = iterate_ipinfo(
\@sorted_ip_inet,
i => 1,
ipinfo_file => $opt{ipinfo_file},
ipinfo_names => $opt{ipinfo_names},
return_arrayref => 1,
);
my @sorted_ip_info = sort { $a->[0] <=> $b->[0] } @$get_ip_info;
$i = 0;
my $start_col = $opt{reserve_inet} ? 1 : 2;
my $end_col = $#{ $sorted_ip_info[0] };
my $res = read_table(
$ip_list,
%opt,
conv_sub => sub {
my ( $r ) = @_;
return [ @$r, @{ $sorted_ip_info[ $i++ ] }[ $start_col .. $end_col ] ];
},
);
return $res || $opt{write_file};
} ## end sub get_ipinfo
sub iterate_ipinfo {
#deal with large ip_list file, ip_list is inet-sorted
my ( $ip_list, %opt ) = @_;
$opt{i} ||= 0;
$opt{return_arrayref} ||= 0;
$opt{ipinfo_file} ||= $IPINFO_LOC_F;
$opt{ipinfo_names} ||= [qw/country area isp country_code area_code isp_code/];
my $ip_info = read_ipinfo( $opt{ipinfo_file} );
my $n = $#$ip_info;
my ( $i, $ir ) = ( 0, $ip_info->[0] );
my ( $s, $e ) = @{$ir}{qw/s e/};
my $res = read_table(
$ip_list,
conv_sub => sub {
my ( $r ) = @_;
my ( $ip, $inet, $rr ) = calc_ip_inet( $r->[ $opt{i} ], \%opt );
return [ @$r, @{$rr}{ @{ $opt{ipinfo_names} } } ] if($rr);
#start from i=0
if($i>$n){
$i = 0;
( $s, $e ) = @{$ip_info->[$i]}{qw/s e/};
}
#<- to nearby $i
while($inet<$s and $i>0){
$i = int($i/2);
( $s, $e ) = @{$ip_info->[$i]}{qw/s e/};
}
#-> to nearby $i
while ( $inet > $e and $i < $n ) {
$i++;
$ir = $ip_info->[$i];
( $s, $e ) = @{$ir}{qw/s e/};
}
my $res_r;
if ( $inet >= $s and $inet <= $e and $i <= $n ) {
$res_r = $ir;
print "$i : $ip, start $res_r->{s}, end $res_r->{e}, inet $inet\n" if ( $DEBUG );
}elsif ( $inet < $s or $i > $n ) {
$res_r = \%UNKNOWN;
}
return [ @$r, @{$res_r}{ @{ $opt{ipinfo_names} } } ];
},
%opt,
);
print "\n" if ( $DEBUG );
return $res || $opt{write_file};
} ## end sub iterate_ipinfo
sub inet_to_ip {
my ( $inet ) = @_;
return inet_ntoa( pack( 'N', $inet ) );
}
sub ip_to_inet {
my ( $ip ) = @_;
return ( -1, \%ERROR ) unless ( is_ip( $ip ) );
my $inet = unpack( "N", inet_aton( $ip ) );
return ( $inet, \%LOCAL ) unless ( is_public_ip( $ip ) );
return ( $inet, undef );
}
sub calc_ip_inet {
my ( $c, $opt ) = @_;
my $ip = $c =~ /^\d+$/ ? inet_to_ip( $c ) : $c;
my @res = ( $ip, ip_to_inet( $ip ) );
#$res[0]=~s/\.\d+$/.0/ if($opt->{use_ip_c});
return @res;
}
sub get_ip_as {
my ( $ip_list, %opt ) = @_;
$opt{ipinfo_file} = $IPINFO_AS_F;
$opt{ipinfo_names} = [qw/as/];
return get_ipinfo( $ip_list, %opt );
}
sub get_ip_loc {
my ( $ip_list, %opt ) = @_;
$opt{ipinfo_file} = $IPINFO_LOC_F;
return get_ipinfo( $ip_list, %opt );
}
sub read_ipinfo {
my ( $f, $charset ) = @_;
$f //= $IPINFO_LOC_F;
$charset //= 'utf8';
my @d;
open my $fh, "<:$charset", $f;
chomp( my $h = <$fh> );
my @head = split /,/, $h;
while ( my $c = <$fh> ) {
chomp( $c );
my @line = split /,/, $c;
my %k = map { $head[$_] => $line[$_] } ( 0 .. $#head );
push @d, \%k;
}
close $fh;
return \@d;
} ## end sub read_ipinfo
sub is_ip {
my ( $ip ) = @_;
return 1 if ( is_ipv4( $ip ) );
return 1 if ( is_ipv6( $ip ) );
return;
}
sub is_public_ip {
my ( $ip ) = @_;
return 1 if ( is_public_ipv4( $ip ) );
return;
}
1;