| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Net::IPAM::IP; | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | our $VERSION = '4.01'; | 
| 4 |  |  |  |  |  |  |  | 
| 5 | 10 |  |  | 10 |  | 506701 | use 5.10.0; | 
|  | 10 |  |  |  |  | 91 |  | 
| 6 | 10 |  |  | 10 |  | 46 | use strict; | 
|  | 10 |  |  |  |  | 16 |  | 
|  | 10 |  |  |  |  | 157 |  | 
| 7 | 10 |  |  | 10 |  | 41 | use warnings; | 
|  | 10 |  |  |  |  | 12 |  | 
|  | 10 |  |  |  |  | 248 |  | 
| 8 | 10 |  |  | 10 |  | 4790 | use utf8; | 
|  | 10 |  |  |  |  | 146 |  | 
|  | 10 |  |  |  |  | 45 |  | 
| 9 |  |  |  |  |  |  |  | 
| 10 | 10 |  |  | 10 |  | 253 | use Carp            (); | 
|  | 10 |  |  |  |  | 16 |  | 
|  | 10 |  |  |  |  | 111 |  | 
| 11 | 10 |  |  | 10 |  | 3577 | use Socket          (); | 
|  | 10 |  |  |  |  | 24434 |  | 
|  | 10 |  |  |  |  | 232 |  | 
| 12 | 10 |  |  | 10 |  | 3260 | use Net::IPAM::Util (); | 
|  | 10 |  |  |  |  | 20 |  | 
|  | 10 |  |  |  |  | 205 |  | 
| 13 |  |  |  |  |  |  |  | 
| 14 | 10 |  |  | 10 |  | 56 | use Exporter 'import'; | 
|  | 10 |  |  |  |  | 11 |  | 
|  | 10 |  |  |  |  | 526 |  | 
| 15 |  |  |  |  |  |  | our @EXPORT_OK = qw(sort_ip); | 
| 16 |  |  |  |  |  |  |  | 
| 17 | 10 |  |  | 10 |  | 45 | use constant Is4in6_Prefix => "\x00" x 10 . "\xff\xff"; | 
|  | 10 |  |  |  |  | 18 |  | 
|  | 10 |  |  |  |  | 15484 |  | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | =head1 NAME | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | Net::IPAM::IP - A library for reading, formatting, sorting and converting IP-addresses. | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | use Net::IPAM::IP; | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | # parse and normalize | 
| 28 |  |  |  |  |  |  | $ip1 = Net::IPAM::IP->new('1.2.3.4') // die 'wrong format,'; | 
| 29 |  |  |  |  |  |  | $ip2 = Net::IPAM::IP->new('fe80::1') // die 'wrong format,'; | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | $ip3 = $ip2->incr // die 'overflow,'; | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | say $ip1;    # 1.2.3.4 | 
| 34 |  |  |  |  |  |  | say $ip2;    # fe80::1 | 
| 35 |  |  |  |  |  |  | say $ip3;    # fe80::2 | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | $ip3 = $ip2->decr // die 'underflow,'; | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | say $ip1;    # 1.2.3.4 | 
| 40 |  |  |  |  |  |  | say $ip2;    # fe80::1 | 
| 41 |  |  |  |  |  |  | say $ip3;    # fe80::0 | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | say $ip1->cmp($ip2);    # -1 | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | say $ip2->expand;       # fe80:0000:0000:0000:0000:0000:0000:0001 | 
| 46 |  |  |  |  |  |  | say $ip2->reverse;      # 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | $ip = Net::IPAM::IP->new_from_bytes( pack( 'C4', 192,    168,   0, 1 ) );                 # 192.168.0.1 | 
| 49 |  |  |  |  |  |  | $ip = Net::IPAM::IP->new_from_bytes( pack( 'n8', 0x2001, 0xdb8, 0, 0, 0, 0, 0, 1, ) );    # 2001:db8::1 | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | @ips = Net::IPAM::IP->getaddrs('dns.google.'); | 
| 52 |  |  |  |  |  |  | say "@ips";  #  8.8.8.8 8.8.4.4 2001:4860:4860::8844 2001:4860:4860::8888 | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | =cut | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | =head1 CONSTRUCTORS | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | =head2 new | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | $ip = Net::IPAM::IP->new("::1"); | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | Parse the input string as IPv4/IPv6 address and returns the IP address object. | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | Returns undef on illegal input. | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | =cut | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | sub new { | 
| 69 | 189 |  |  | 189 | 1 | 40871 | my $self  = bless( {}, $_[0] ); | 
| 70 | 189 |  | 66 |  |  | 625 | my $input = $_[1] // Carp::croak 'missing argument'; | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | # IPv4 | 
| 73 | 188 | 100 |  |  |  | 484 | if ( index( $input, ':' ) < 0 ) { | 
| 74 | 49 |  |  |  |  | 148 | my $n = Socket::inet_pton( Socket::AF_INET, $input ); | 
| 75 | 49 | 100 |  |  |  | 153 | return unless defined $n; | 
| 76 |  |  |  |  |  |  |  | 
| 77 | 32 |  |  |  |  | 159 | $self->{version} = 4; | 
| 78 | 32 |  |  |  |  | 67 | $self->{binary}  = 4 . $n; | 
| 79 | 32 |  |  |  |  | 92 | return $self; | 
| 80 |  |  |  |  |  |  | } | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | # IPv4-mapped-IPv6 | 
| 83 | 139 | 100 |  |  |  | 323 | if ( index( $input, '.' ) >= 0 ) { | 
| 84 |  |  |  |  |  |  | # allow only IPv4-mapped-IPv6 in mixed mode | 
| 85 | 40 | 100 |  |  |  | 207 | return unless $input =~ m/^::ffff:/m; | 
| 86 |  |  |  |  |  |  | } | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | # IPv6 address | 
| 89 | 123 |  |  |  |  | 341 | my $n = Socket::inet_pton( Socket::AF_INET6, $input ); | 
| 90 | 123 | 100 |  |  |  | 360 | return unless defined $n; | 
| 91 |  |  |  |  |  |  |  | 
| 92 | 69 |  |  |  |  | 204 | $self->{version} = 6; | 
| 93 | 69 |  |  |  |  | 141 | $self->{binary}  = 6 . $n; | 
| 94 | 69 |  |  |  |  | 208 | return $self; | 
| 95 |  |  |  |  |  |  | } | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | =head2 new_from_bytes | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | $ip = Net::IPAM::IP->new_from_bytes("\x0a\x00\x00\x01") | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | Parse the input as packed IPv4/IPv6/IPv4-mapped-IPv6 address and returns the IP address object. | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | Croaks on illegal input. | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | Can be used for cloning the object: | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | $clone = $obj->new_from_bytes($obj->bytes); | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | =cut | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | sub new_from_bytes { | 
| 112 | 30 |  | 66 | 30 | 1 | 858 | my $self = bless( {}, ref $_[0] || $_[0] ); | 
| 113 | 30 |  |  |  |  | 47 | my $n    = $_[1]; | 
| 114 | 30 | 100 |  |  |  | 119 | Carp::croak('missing argument') unless defined $n; | 
| 115 |  |  |  |  |  |  |  | 
| 116 | 29 | 100 |  |  |  | 77 | if ( length($n) == 4 ) { | 
|  |  | 100 |  |  |  |  |  | 
| 117 | 11 |  |  |  |  | 53 | $self->{version} = 4; | 
| 118 | 11 |  |  |  |  | 28 | $self->{binary}  = 4 . $n; | 
| 119 | 11 |  |  |  |  | 41 | return $self; | 
| 120 |  |  |  |  |  |  | } | 
| 121 |  |  |  |  |  |  | elsif ( length($n) == 16 ) { | 
| 122 | 16 |  |  |  |  | 26 | $self->{version} = 6; | 
| 123 | 16 |  |  |  |  | 33 | $self->{binary}  = 6 . $n; | 
| 124 | 16 |  |  |  |  | 47 | return $self; | 
| 125 |  |  |  |  |  |  | } | 
| 126 |  |  |  |  |  |  |  | 
| 127 | 2 |  |  |  |  | 171 | Carp::croak 'illegal input'; | 
| 128 |  |  |  |  |  |  | } | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | =head2 getaddrs($name, [$error_cb]) | 
| 131 |  |  |  |  |  |  |  | 
| 132 |  |  |  |  |  |  | Returns a list of ip objects for a given $name or undef if there is no RR record for $name. | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | my @ips = Net::IPAM::IP->getaddrs('dns.google.'); | 
| 135 |  |  |  |  |  |  | say "@ips";  #  8.8.8.8 8.8.4.4 2001:4860:4860::8844 2001:4860:4860::8888 | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | L"getaddrs"> calls the L function C<< getaddrinfo() >> under the hood. | 
| 138 |  |  |  |  |  |  |  | 
| 139 |  |  |  |  |  |  | With no error callback L just calls C<< carp() >> with underlying Socket errors. | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | For granular error handling use your own error callback: | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | my $my_error_cb = sub { | 
| 144 |  |  |  |  |  |  | my $error = shift; | 
| 145 |  |  |  |  |  |  | # check the $error and do what you want | 
| 146 |  |  |  |  |  |  | ... | 
| 147 |  |  |  |  |  |  | } | 
| 148 |  |  |  |  |  |  |  | 
| 149 |  |  |  |  |  |  | my @ips = Net::IPAM::IP->getaddrs( $name, $my_error_cb ); | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | or shut up the default error handler with: | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | my @ips = Net::IPAM::IP->getaddrs( $name, sub { } ); | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | ANNOTATION: This constructor could also be named C<< new_from_name >> but it behaves differently | 
| 156 |  |  |  |  |  |  | because it returns a B  of objects and supports an optional argument as error callback,  | 
| 157 |  |  |  |  |  |  | reporting underlying Socket errors. | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  | =cut | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | # heuristic detection of ip addrs as input | 
| 162 |  |  |  |  |  |  | my $v4_rx       = qr/^[0-9.]+$/; | 
| 163 |  |  |  |  |  |  | my $v6_rx       = qr/^[a-fA-F0-9:]+$/; | 
| 164 |  |  |  |  |  |  | my $v4mapv6_rx  = qr/^::[a-fA-F]+:[0-9.]+$/; | 
| 165 |  |  |  |  |  |  | my $v4compv6_rx = qr/^::[0-9.]+$/; | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | my $ip_rx = qr/$v4_rx|$v6_rx|$v4mapv6_rx|$v4compv6_rx/; | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | sub getaddrs { | 
| 170 | 24 |  |  | 24 | 1 | 9794 | my ( $class, $name, $error_cb ) = @_; | 
| 171 | 24 | 100 |  |  |  | 171 | Carp::croak('missing argument') unless defined $name; | 
| 172 |  |  |  |  |  |  |  | 
| 173 | 23 | 100 |  |  |  | 83 | $error_cb = \&Carp::carp unless defined $error_cb; | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | # just ip address as input param, don't rely on (buggy) Socket getaddrinfo | 
| 176 | 23 | 100 |  |  |  | 298 | return $class->new($name) if $name =~ $ip_rx; | 
| 177 |  |  |  |  |  |  |  | 
| 178 |  |  |  |  |  |  | # resolve name | 
| 179 | 3 |  |  |  |  | 182758 | my ( $err, @res ) = | 
| 180 |  |  |  |  |  |  | Socket::getaddrinfo( $name, "", { socktype => Socket::SOCK_RAW, family => Socket::AF_UNSPEC } ); | 
| 181 |  |  |  |  |  |  |  | 
| 182 | 3 | 100 |  |  |  | 71 | if ($err) { | 
| 183 |  |  |  |  |  |  |  | 
| 184 |  |  |  |  |  |  | # no error, just no resolveable name | 
| 185 | 2 | 50 |  |  |  | 44 | return if $err == Socket::EAI_NONAME; | 
| 186 |  |  |  |  |  |  |  | 
| 187 | 0 |  |  |  |  | 0 | $error_cb->("getaddrinfo($name): $err"); | 
| 188 | 0 |  |  |  |  | 0 | return; | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | # unpack sockaddr struct | 
| 192 | 1 |  |  |  |  | 4 | my @ips; | 
| 193 | 1 |  |  |  |  | 7 | while ( my $ai = shift @res ) { | 
| 194 | 4 |  |  |  |  | 7 | my $n; | 
| 195 | 4 | 100 |  |  |  | 14 | if ( $ai->{family} == Socket::AF_INET ) { | 
| 196 | 2 |  |  |  |  | 18 | $n = substr( $ai->{addr}, 4, 4 ); | 
| 197 |  |  |  |  |  |  | } | 
| 198 |  |  |  |  |  |  | else { | 
| 199 | 2 |  |  |  |  | 5 | $n = substr( $ai->{addr}, 8, 16 ); | 
| 200 |  |  |  |  |  |  | } | 
| 201 | 4 |  |  |  |  | 17 | push @ips, $class->new_from_bytes($n); | 
| 202 |  |  |  |  |  |  | } | 
| 203 |  |  |  |  |  |  |  | 
| 204 | 1 |  |  |  |  | 8 | return @ips; | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | =head1 METHODS | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  | L implements the following methods: | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | =head2 cmp | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | Compare IP objects, returns -1, 0, +1 | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | $this->cmp($other) | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | @sorted_ips = sort { $a->cmp($b) } @unsorted_ips; | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | Fast bytewise lexical comparison of the binary representation in network byte order. | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | For even faster sorting import L. | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | =cut | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | # the first byte is the version | 
| 226 |  |  |  |  |  |  | # use fast builtin cmp | 
| 227 |  |  |  |  |  |  | # IPv4 is sorted before IPv6 | 
| 228 |  |  |  |  |  |  | sub cmp { | 
| 229 | 25 |  |  | 25 | 1 | 58 | $_[0]->{binary} cmp $_[1]->{binary}; | 
| 230 |  |  |  |  |  |  | } | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | =head2 version | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | $v = Net::IPAM::IP->new('fe80::1')->version    # 6 | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | Returns 4 or 6. | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | =cut | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | sub version { | 
| 241 | 5 |  |  | 5 | 1 | 16 | $_[0]->{version}; | 
| 242 |  |  |  |  |  |  | } | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | =head2 to_string | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  | Returns the input string in canonical form. | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  | lower case hexadecimal characters | 
| 249 |  |  |  |  |  |  | zero compression | 
| 250 |  |  |  |  |  |  | remove leading zeros | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  | say Net::IPAM::IP->new('Fe80::0001')->to_string;  # fe80::1 | 
| 253 |  |  |  |  |  |  |  | 
| 254 |  |  |  |  |  |  | Stringification is overloaded with L"to_string"> | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | my $ip = Net::IPAM::IP->new('Fe80::0001') // die 'wrong format'; | 
| 257 |  |  |  |  |  |  | say $ip; # fe80::1 | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | =cut | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | # without inet_ntop bug it would be easy, sic | 
| 262 |  |  |  |  |  |  | #sub to_string { | 
| 263 |  |  |  |  |  |  | #  return $_[0]->{as_string} if exists $_[0]->{as_string}; | 
| 264 |  |  |  |  |  |  | #  my ( $v, $n ) = unpack( 'C a*', $_[0]->{binary} ); | 
| 265 |  |  |  |  |  |  | #  return $_[0]->{as_string} = Socket::inet_ntop( $v, $n ); | 
| 266 |  |  |  |  |  |  | #} | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | # circumvent IPv4-compatible-IPv6 bug in Socket::inet_ntop | 
| 269 |  |  |  |  |  |  | sub to_string { | 
| 270 | 74 | 100 |  | 74 | 1 | 574 | return $_[0]->{as_string} if exists $_[0]->{as_string}; | 
| 271 |  |  |  |  |  |  |  | 
| 272 | 63 |  |  |  |  | 153 | my $n = substr( $_[0]->{binary}, 1, ); | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | # no bug in Socket::inet_ntop for IPv4, just return | 
| 275 | 63 | 100 |  |  |  | 131 | if ( $_[0]->{version} == 4 ) { | 
| 276 | 26 |  |  |  |  | 204 | return $_[0]->{as_string} = Socket::inet_ntop( Socket::AF_INET, $n ); | 
| 277 |  |  |  |  |  |  | } | 
| 278 |  |  |  |  |  |  |  | 
| 279 |  |  |  |  |  |  | # IPv6 case | 
| 280 |  |  |  |  |  |  | # handle bug in Socket::inet_ntop for deprecated IPv4-compatible-IPv6 addresses | 
| 281 |  |  |  |  |  |  | # ::aaaa:bbbb are returned as ::hex(aa).hex(aa).hex(bb).hex(bb) = ::170.170.187.187 | 
| 282 |  |  |  |  |  |  | # e.g: ::cafe:affe => ::202.254.175.254 | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | # handle IPv4MappedIPv6 address special | 
| 285 | 37 | 100 |  |  |  | 98 | if ( substr( $n, 0, 12 ) eq Is4in6_Prefix ) { | 
| 286 |  |  |  |  |  |  | # concat ::ffff:1.2.3.4 | 
| 287 | 8 |  |  |  |  | 77 | return $_[0]->{as_string} = '::ffff:' . Socket::inet_ntop( Socket::AF_INET, substr( $n, -4 ) ); | 
| 288 |  |  |  |  |  |  | } | 
| 289 |  |  |  |  |  |  |  | 
| 290 | 29 |  |  |  |  | 93 | my $str = Socket::inet_ntop( Socket::AF_INET6, $n ); | 
| 291 |  |  |  |  |  |  |  | 
| 292 |  |  |  |  |  |  | # first handle normal case, no dot '.' | 
| 293 | 29 | 100 |  |  |  | 66 | if ( index( $str, '.' ) < 0 ) { | 
| 294 | 28 |  |  |  |  | 214 | return $_[0]->{as_string} = $str; | 
| 295 |  |  |  |  |  |  | } | 
| 296 |  |  |  |  |  |  |  | 
| 297 |  |  |  |  |  |  | # handle the bug, use our pure perl inet_ntop_pp | 
| 298 | 1 |  |  |  |  | 4 | return $_[0]->{as_string} = Net::IPAM::Util::inet_ntop_pp( Socket::AF_INET6, $n ); | 
| 299 |  |  |  |  |  |  | } | 
| 300 |  |  |  |  |  |  |  | 
| 301 |  |  |  |  |  |  | =head2 TO_JSON | 
| 302 |  |  |  |  |  |  |  | 
| 303 |  |  |  |  |  |  | helper method for JSON serialization, just calls $ip->to_string. | 
| 304 |  |  |  |  |  |  | See also L. | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | =cut | 
| 307 |  |  |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | sub TO_JSON { | 
| 309 | 0 |  |  | 0 | 1 | 0 | $_[0]->to_string; | 
| 310 |  |  |  |  |  |  | } | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | =head2 incr | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | Returns the next IP address, returns undef on overflow. | 
| 315 |  |  |  |  |  |  |  | 
| 316 |  |  |  |  |  |  | $next_ip = Net::IPAM::IP->new('fe80::1')->incr // die 'overflow,'; | 
| 317 |  |  |  |  |  |  | say $next_ip;   # fe80::2 | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | =cut | 
| 320 |  |  |  |  |  |  |  | 
| 321 |  |  |  |  |  |  | sub incr { | 
| 322 | 7 |  |  | 7 | 1 | 24 | my $n_plus1 = Net::IPAM::Util::incr_n( $_[0]->bytes ); | 
| 323 |  |  |  |  |  |  |  | 
| 324 |  |  |  |  |  |  | # overflow? | 
| 325 | 7 | 100 |  |  |  | 19 | return unless defined $n_plus1; | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | # sort of cloning | 
| 328 | 5 |  |  |  |  | 10 | $_[0]->new_from_bytes($n_plus1); | 
| 329 |  |  |  |  |  |  | } | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | =head2 decr | 
| 332 |  |  |  |  |  |  |  | 
| 333 |  |  |  |  |  |  | Returns the previous IP address, returns undef on underflow. | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | $prev_ip = Net::IPAM::IP->new('fe80::1')->decr // die 'underflow,'; | 
| 336 |  |  |  |  |  |  | say $prev_ip;   # fe80:: | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | =cut | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | sub decr { | 
| 341 | 6 |  |  | 6 | 1 | 19 | my $n_minus1 = Net::IPAM::Util::decr_n( $_[0]->bytes ); | 
| 342 |  |  |  |  |  |  |  | 
| 343 |  |  |  |  |  |  | # underflow? | 
| 344 | 6 | 100 |  |  |  | 15 | return unless defined $n_minus1; | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | # sort of cloning | 
| 347 | 4 |  |  |  |  | 9 | $_[0]->new_from_bytes($n_minus1); | 
| 348 |  |  |  |  |  |  | } | 
| 349 |  |  |  |  |  |  |  | 
| 350 |  |  |  |  |  |  | =head2 expand | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | Expand IP address into canonical form, useful for C<< grep >>, aligned output and lexical C<< sort >> | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | Net::IPAM::IP->new('1.2.3.4')->expand;   # '001.002.003.004' | 
| 355 |  |  |  |  |  |  | Net::IPAM::IP->new('fe80::1')->expand;   # 'fe80:0000:0000:0000:0000:0000:0000:0001' | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | =cut | 
| 358 |  |  |  |  |  |  |  | 
| 359 |  |  |  |  |  |  | sub expand { | 
| 360 | 5 | 100 |  | 5 | 1 | 19 | return $_[0]->{expand} if exists $_[0]->{expand}; | 
| 361 |  |  |  |  |  |  |  | 
| 362 | 4 |  |  |  |  | 12 | my $n = substr( $_[0]->{binary}, 1, ); | 
| 363 |  |  |  |  |  |  |  | 
| 364 | 4 | 100 |  |  |  | 10 | if ( $_[0]->{version} == 6 ) { | 
| 365 | 2 |  |  |  |  | 8 | my @hextets = unpack( 'H4' x 8, $n ); | 
| 366 |  |  |  |  |  |  |  | 
| 367 |  |  |  |  |  |  | # cache it and return | 
| 368 | 2 |  |  |  |  | 14 | return $_[0]->{expand} = join( ':', @hextets ); | 
| 369 |  |  |  |  |  |  | } | 
| 370 |  |  |  |  |  |  |  | 
| 371 |  |  |  |  |  |  | # IPv4 | 
| 372 | 2 |  |  |  |  | 11 | my @octets = unpack( 'C4', $n ); | 
| 373 |  |  |  |  |  |  |  | 
| 374 |  |  |  |  |  |  | # cache it and return | 
| 375 | 2 |  |  |  |  | 16 | return $_[0]->{expand} = sprintf( "%03d.%03d.%03d.%03d", @octets ); | 
| 376 |  |  |  |  |  |  | } | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | =head2 reverse | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | Reverse IP address, needed for PTR entries in DNS zone files. | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | Net::IPAM::IP->new('fe80::1')->reverse; # '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f' | 
| 383 |  |  |  |  |  |  | Net::IPAM::IP->new('1.2.3.4')->reverse; # '4.3.2.1' | 
| 384 |  |  |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | =cut | 
| 386 |  |  |  |  |  |  |  | 
| 387 |  |  |  |  |  |  | sub reverse { | 
| 388 | 5 | 100 |  | 5 | 1 | 19 | return $_[0]->{reverse} if exists $_[0]->{reverse}; | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | # unpack to version and network byte order (from Socket::inet_pton) | 
| 391 |  |  |  |  |  |  | # my ( $v, $n ) = unpack( 'C a*', $_[0]->{binary} ); | 
| 392 |  |  |  |  |  |  | # substr() ist faster | 
| 393 | 4 |  |  |  |  | 8 | my $n = substr( $_[0]->{binary}, 1, ); | 
| 394 |  |  |  |  |  |  |  | 
| 395 | 4 | 100 |  |  |  | 10 | if ( $_[0]->{version} == 6 ) { | 
| 396 | 3 |  |  |  |  | 8 | my $hex_str = unpack( 'H*',     $n ); | 
| 397 | 3 |  |  |  |  | 24 | my @nibbles = unpack( 'A' x 32, $hex_str ); | 
| 398 |  |  |  |  |  |  |  | 
| 399 |  |  |  |  |  |  | # cache it and return | 
| 400 | 3 |  |  |  |  | 25 | return $_[0]->{reverse} = join( '.', reverse @nibbles ); | 
| 401 |  |  |  |  |  |  | } | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  | # IPv4 | 
| 404 | 1 |  |  |  |  | 4 | my @octets = unpack( 'C4', $n ); | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | # cache it and return | 
| 407 | 1 |  |  |  |  | 7 | return $_[0]->{reverse} = join( '.', reverse @octets ); | 
| 408 |  |  |  |  |  |  | } | 
| 409 |  |  |  |  |  |  |  | 
| 410 |  |  |  |  |  |  | =head2 getname([$error_cb]) | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | Returns the DNS name for the ip object or undef if there is no PTR RR. | 
| 413 |  |  |  |  |  |  |  | 
| 414 |  |  |  |  |  |  | say Net::IPAM::IP->new('2001:4860:4860::8888')->getname;   # dns.google. | 
| 415 |  |  |  |  |  |  |  | 
| 416 |  |  |  |  |  |  | L"getname"> calls the L function C<< getnameinfo() >> under the hood. | 
| 417 |  |  |  |  |  |  |  | 
| 418 |  |  |  |  |  |  | With no error callback L just calls C<< carp() >> with underlying Socket errors. | 
| 419 |  |  |  |  |  |  |  | 
| 420 |  |  |  |  |  |  | =head3 LIMITATION: | 
| 421 |  |  |  |  |  |  |  | 
| 422 |  |  |  |  |  |  | Returns just one name even if the IP has more than one PTR RR. This is a limitation | 
| 423 |  |  |  |  |  |  | of Socket::getnameinfo. If you need all names for IPs with more than one PTR RR then you should | 
| 424 |  |  |  |  |  |  | use L or similar modules. | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | =cut | 
| 427 |  |  |  |  |  |  |  | 
| 428 |  |  |  |  |  |  | sub getname { | 
| 429 | 1 |  |  | 1 | 1 | 497 | my ( $self, $error_cb ) = @_; | 
| 430 | 1 | 50 |  |  |  | 6 | $error_cb = \&Carp::carp unless defined $error_cb; | 
| 431 |  |  |  |  |  |  |  | 
| 432 | 1 |  |  |  |  | 2 | my $sock_addr; | 
| 433 | 1 | 50 |  |  |  | 13 | if ( $self->{version} == 4 ) { | 
| 434 | 1 |  |  |  |  | 5 | $sock_addr = Socket::pack_sockaddr_in( 0, $self->bytes ); | 
| 435 |  |  |  |  |  |  | } | 
| 436 |  |  |  |  |  |  | else { | 
| 437 | 0 |  |  |  |  | 0 | $sock_addr = Socket::pack_sockaddr_in6( 0, $self->bytes ); | 
| 438 |  |  |  |  |  |  | } | 
| 439 |  |  |  |  |  |  |  | 
| 440 | 1 |  |  |  |  | 114415 | my ( $err, $name ) = Socket::getnameinfo( $sock_addr, Socket::NI_NAMEREQD, Socket::NIx_NOSERV ); | 
| 441 |  |  |  |  |  |  |  | 
| 442 | 1 | 50 |  |  |  | 23 | if ($err) { | 
| 443 |  |  |  |  |  |  |  | 
| 444 |  |  |  |  |  |  | # no error, just no resolveable name | 
| 445 | 0 | 0 |  |  |  | 0 | return if $err == Socket::EAI_NONAME; | 
| 446 |  |  |  |  |  |  |  | 
| 447 | 0 |  |  |  |  | 0 | $error_cb->("getnameinfo($self): $err"); | 
| 448 | 0 |  |  |  |  | 0 | return; | 
| 449 |  |  |  |  |  |  | } | 
| 450 |  |  |  |  |  |  |  | 
| 451 | 1 |  |  |  |  | 17 | $name; | 
| 452 |  |  |  |  |  |  | } | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | =head2 bytes | 
| 455 |  |  |  |  |  |  |  | 
| 456 |  |  |  |  |  |  | $ip = Net::IPAM::IP->new('fe80::'); | 
| 457 |  |  |  |  |  |  | $bytes = $ip->bytes;    # "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | $ip    = Net::IPAM::IP->new('10.0.0.1'); | 
| 460 |  |  |  |  |  |  | $bytes = $ip->bytes;    # "\x0a\x00\x00\x01" | 
| 461 |  |  |  |  |  |  |  | 
| 462 |  |  |  |  |  |  | Returns the packed IP address as byte-string. It's the opposite to L"new_from_bytes"> | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  | =cut | 
| 465 |  |  |  |  |  |  |  | 
| 466 |  |  |  |  |  |  | # drop first byte (version) and return the packed IP address, | 
| 467 |  |  |  |  |  |  | sub bytes { | 
| 468 | 20 |  |  | 20 | 1 | 78 | substr( $_[0]->{binary}, 1 ); | 
| 469 |  |  |  |  |  |  | } | 
| 470 |  |  |  |  |  |  |  | 
| 471 |  |  |  |  |  |  | =head2 is4in6 | 
| 472 |  |  |  |  |  |  |  | 
| 473 |  |  |  |  |  |  | $ip = Net::IPAM::IP->new('::ffff:1.2.3.4') | 
| 474 |  |  |  |  |  |  | if ( $ip->is4in6 ) { | 
| 475 |  |  |  |  |  |  | ... | 
| 476 |  |  |  |  |  |  | } | 
| 477 |  |  |  |  |  |  |  | 
| 478 |  |  |  |  |  |  | Returns true if the IP address is a IPv4-mapped IPv6 address. | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | =cut | 
| 481 |  |  |  |  |  |  |  | 
| 482 |  |  |  |  |  |  | sub is4in6 { | 
| 483 | 3 |  |  | 3 | 1 | 13 | return substr( $_[0]->{binary}, 1, 12 ) eq Is4in6_Prefix; | 
| 484 |  |  |  |  |  |  | } | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | =head1 FUNCTIONS | 
| 487 |  |  |  |  |  |  |  | 
| 488 |  |  |  |  |  |  | =head2 sort_ip | 
| 489 |  |  |  |  |  |  |  | 
| 490 |  |  |  |  |  |  | use Net::IPAM::IP 'sort_ip'; | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  | @sorted_ips = sort_ip @unsorted_ips; | 
| 493 |  |  |  |  |  |  |  | 
| 494 |  |  |  |  |  |  | Faster sort implemention (Schwartzian transform) as explcit sort function: | 
| 495 |  |  |  |  |  |  |  | 
| 496 |  |  |  |  |  |  | @sorted_ips = sort { $a->cmp($b) } @unsorted_ips; | 
| 497 |  |  |  |  |  |  |  | 
| 498 |  |  |  |  |  |  | =cut | 
| 499 |  |  |  |  |  |  |  | 
| 500 |  |  |  |  |  |  | sub sort_ip { | 
| 501 | 11 |  |  |  |  | 15 | return map { $_->[0] } | 
| 502 | 25 |  |  |  |  | 28 | sort     { $a->[1] cmp $b->[1] } | 
| 503 | 1 |  |  | 1 | 1 | 360 | map      { [ $_, $_->{binary} ] } @_; | 
|  | 11 |  |  |  |  | 21 |  | 
| 504 |  |  |  |  |  |  | } | 
| 505 |  |  |  |  |  |  |  | 
| 506 |  |  |  |  |  |  | =head1 OPERATORS | 
| 507 |  |  |  |  |  |  |  | 
| 508 |  |  |  |  |  |  | L overloads the following operators. | 
| 509 |  |  |  |  |  |  |  | 
| 510 |  |  |  |  |  |  | =head2 bool | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | my $bool = !!$ip; | 
| 513 |  |  |  |  |  |  |  | 
| 514 |  |  |  |  |  |  | Always true. | 
| 515 |  |  |  |  |  |  |  | 
| 516 |  |  |  |  |  |  | =head2 stringify | 
| 517 |  |  |  |  |  |  |  | 
| 518 |  |  |  |  |  |  | my $str = "$ip"; | 
| 519 |  |  |  |  |  |  |  | 
| 520 |  |  |  |  |  |  | Alias for L"to_string">. | 
| 521 |  |  |  |  |  |  |  | 
| 522 |  |  |  |  |  |  | =cut | 
| 523 |  |  |  |  |  |  |  | 
| 524 |  |  |  |  |  |  | use overload | 
| 525 | 55 |  |  | 55 |  | 7255 | '""'     => sub { shift->to_string }, | 
| 526 | 32 |  |  | 32 |  | 2832 | bool     => sub { 1 }, | 
| 527 | 10 |  |  | 10 |  | 10152 | fallback => 1; | 
|  | 10 |  |  |  |  | 7077 |  | 
|  | 10 |  |  |  |  | 79 |  | 
| 528 |  |  |  |  |  |  |  | 
| 529 |  |  |  |  |  |  | =head1 WARNING | 
| 530 |  |  |  |  |  |  |  | 
| 531 |  |  |  |  |  |  | Some Socket::inet_XtoY implementations are hopelessly buggy. | 
| 532 |  |  |  |  |  |  |  | 
| 533 |  |  |  |  |  |  | Tests are made during loading and in case of errors, these functions are redefined | 
| 534 |  |  |  |  |  |  | with a (slower) pure-perl implementation. | 
| 535 |  |  |  |  |  |  |  | 
| 536 |  |  |  |  |  |  | =cut | 
| 537 |  |  |  |  |  |  |  | 
| 538 |  |  |  |  |  |  | # On some platforms, inet_pton accepts various forms of invalid input or discards valid input. | 
| 539 |  |  |  |  |  |  | # In this case use a (slower) pure-perl implementation for Socket::inet_pton. | 
| 540 |  |  |  |  |  |  | # and also for Socket::inet_ntop, I don't trust that too. | 
| 541 |  |  |  |  |  |  | BEGIN { | 
| 542 | 10 | 100 | 66 | 10 |  | 627 | if (    # wrong valid | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 66 |  |  |  |  | 
| 543 |  |  |  |  |  |  | defined Socket::inet_pton( Socket::AF_INET,  '010.0.0.1' ) | 
| 544 |  |  |  |  |  |  | || defined Socket::inet_pton( Socket::AF_INET,  '10.000.0.1' ) | 
| 545 |  |  |  |  |  |  | || defined Socket::inet_pton( Socket::AF_INET6, 'cafe:::' ) | 
| 546 |  |  |  |  |  |  | || defined Socket::inet_pton( Socket::AF_INET6, 'cafe::1::' ) | 
| 547 |  |  |  |  |  |  | || defined Socket::inet_pton( Socket::AF_INET6, 'cafe::1:' ) | 
| 548 |  |  |  |  |  |  | || defined Socket::inet_pton( Socket::AF_INET6, ':cafe::' ) | 
| 549 |  |  |  |  |  |  |  | 
| 550 |  |  |  |  |  |  | # wrong invalid | 
| 551 |  |  |  |  |  |  | || !defined Socket::inet_pton( Socket::AF_INET6, 'caFe::' ) | 
| 552 |  |  |  |  |  |  | || !defined Socket::inet_pton( Socket::AF_INET6, '::' ) | 
| 553 |  |  |  |  |  |  | || !defined Socket::inet_pton( Socket::AF_INET,  '0.0.0.0' ) | 
| 554 |  |  |  |  |  |  | ) | 
| 555 |  |  |  |  |  |  | { | 
| 556 | 10 |  |  | 10 |  | 1546 | no warnings 'redefine'; | 
|  | 10 |  |  |  |  | 18 |  | 
|  | 10 |  |  |  |  | 650 |  | 
| 557 | 2 |  |  |  |  | 45 | *Socket::inet_pton = \&Net::IPAM::Util::inet_pton_pp; | 
| 558 | 2 |  |  |  |  | 70 | *Socket::inet_ntop = \&Net::IPAM::Util::inet_ntop_pp; | 
| 559 |  |  |  |  |  |  | } | 
| 560 |  |  |  |  |  |  | } | 
| 561 |  |  |  |  |  |  |  | 
| 562 |  |  |  |  |  |  | =head1 AUTHOR | 
| 563 |  |  |  |  |  |  |  | 
| 564 |  |  |  |  |  |  | Karl Gaissmaier, C<<  >> | 
| 565 |  |  |  |  |  |  |  | 
| 566 |  |  |  |  |  |  | =head1 BUGS | 
| 567 |  |  |  |  |  |  |  | 
| 568 |  |  |  |  |  |  | Please report any bugs or feature requests to C, or through | 
| 569 |  |  |  |  |  |  | the web interface at L.  I will be notified, and then you'll | 
| 570 |  |  |  |  |  |  | automatically be notified of progress on your bug as I make changes. | 
| 571 |  |  |  |  |  |  |  | 
| 572 |  |  |  |  |  |  | =head1 SUPPORT | 
| 573 |  |  |  |  |  |  |  | 
| 574 |  |  |  |  |  |  | You can find documentation for this module with the perldoc command. | 
| 575 |  |  |  |  |  |  |  | 
| 576 |  |  |  |  |  |  | perldoc Net::IPAM::IP | 
| 577 |  |  |  |  |  |  |  | 
| 578 |  |  |  |  |  |  |  | 
| 579 |  |  |  |  |  |  | You can also look for information at: | 
| 580 |  |  |  |  |  |  |  | 
| 581 |  |  |  |  |  |  | =over 4 | 
| 582 |  |  |  |  |  |  |  | 
| 583 |  |  |  |  |  |  | =item * on github | 
| 584 |  |  |  |  |  |  |  | 
| 585 |  |  |  |  |  |  | TODO | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  | =back | 
| 588 |  |  |  |  |  |  |  | 
| 589 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 590 |  |  |  |  |  |  |  | 
| 591 |  |  |  |  |  |  | L | 
| 592 |  |  |  |  |  |  | L | 
| 593 |  |  |  |  |  |  | L | 
| 594 |  |  |  |  |  |  |  | 
| 595 |  |  |  |  |  |  | =head1 LICENSE AND COPYRIGHT | 
| 596 |  |  |  |  |  |  |  | 
| 597 |  |  |  |  |  |  | This software is copyright (c) 2020-2022 by Karl Gaissmaier. | 
| 598 |  |  |  |  |  |  |  | 
| 599 |  |  |  |  |  |  | This is free software; you can redistribute it and/or modify it under | 
| 600 |  |  |  |  |  |  | the same terms as the Perl 5 programming language system itself. | 
| 601 |  |  |  |  |  |  |  | 
| 602 |  |  |  |  |  |  |  | 
| 603 |  |  |  |  |  |  | =cut | 
| 604 |  |  |  |  |  |  |  | 
| 605 |  |  |  |  |  |  | 1;    # End of Net::IPAM::IP |