File Coverage

blib/lib/Net/IPAM/IP.pm
Criterion Covered Total %
statement 131 138 94.9
branch 54 60 90.0
condition 17 30 56.6
subroutine 26 27 96.3
pod 14 14 100.0
total 242 269 89.9


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