File Coverage

blib/lib/Net/IPAM/IP.pm
Criterion Covered Total %
statement 125 132 94.7
branch 52 58 89.6
condition 17 30 56.6
subroutine 28 29 96.5
pod 15 15 100.0
total 237 264 89.7


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 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
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 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
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.
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