File Coverage

blib/lib/Net/IPAM/IP.pm
Criterion Covered Total %
statement 131 137 95.6
branch 54 60 90.0
condition 17 30 56.6
subroutine 26 26 100.0
pod 13 13 100.0
total 241 266 90.6


line stmt bran cond sub pod time code
1             package Net::IPAM::IP;
2              
3             our $VERSION = '3.00';
4              
5 10     10   588144 use 5.10.0;
  10         111  
6 10     10   56 use strict;
  10         20  
  10         236  
7 10     10   57 use warnings;
  10         26  
  10         280  
8 10     10   6292 use utf8;
  10         136  
  10         47  
9              
10 10     10   582 use Carp ();
  10         17  
  10         124  
11 10     10   4206 use Socket ();
  10         28084  
  10         307  
12 10     10   4272 use Net::IPAM::Util ();
  10         23  
  10         234  
13              
14 10     10   62 use Exporter 'import';
  10         18  
  10         16582  
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 32110 my $self = bless( {}, $_[0] );
72 180   66     581 my $input = $_[1] // Carp::croak 'missing argument';
73              
74             # IPv4
75 179 100       431 if ( index( $input, ':' ) < 0 ) {
76 49         151 my $n = Socket::inet_pton( Socket::AF_INET, $input );
77 49 100       165 return unless defined $n;
78              
79 32         197 $self->{version} = 4;
80 32         75 $self->{binary} = 4 . $n;
81 32         95 return $self;
82             }
83              
84             # IPv4-mapped-IPv6
85 130 100       268 if ( index( $input, '.' ) >= 0 ) {
86 33         55 my $ip4m6 = $input;
87              
88             # remove leading ::ffff: or return undef
89 33 100       239 return unless $ip4m6 =~ s/^::ffff://i;
90              
91 17         60 my $n = Socket::inet_pton( Socket::AF_INET, $ip4m6 );
92 17 100       53 return unless defined $n;
93              
94 16         41 $self->{version} = 4;
95 16         32 $self->{ip4in6} = 1;
96 16         36 $self->{binary} = 4 . $n;
97 16         56 return $self;
98             }
99              
100             # IPv6 address
101 97         267 my $n = Socket::inet_pton( Socket::AF_INET6, $input );
102 97 100       299 return unless defined $n;
103              
104 50         180 $self->{version} = 6;
105 50         109 $self->{binary} = 6 . $n;
106 50         163 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 905 my $self = bless( {}, ref $_[0] || $_[0] );
125 28         49 my $n = $_[1];
126 28 100       129 Carp::croak('missing argument') unless defined $n;
127              
128 27 100       91 if ( length($n) == 4 ) {
    100          
129 13         60 $self->{version} = 4;
130 13         34 $self->{binary} = 4 . $n;
131 13         58 return $self;
132             }
133             elsif ( length($n) == 16 ) {
134              
135             # check for IPv4-mapped IPv6 address ::ffff:1.2.3.4
136 12 100       37 if ( index( $n, "\x00" x 10 . "\xff\xff" ) == 0 ) {
137 2         3 $self->{version} = 4;
138 2         3 $self->{ip4in6} = 1;
139 2         7 $self->{binary} = 4 . substr( $n, 12 );
140 2         5 return $self;
141             }
142              
143 10         19 $self->{version} = 6;
144 10         26 $self->{binary} = 6 . $n;
145 10         35 return $self;
146             }
147              
148 2         189 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 7909 my ( $class, $name, $error_cb ) = @_;
192 24 100       142 Carp::croak('missing argument') unless defined $name;
193              
194 23 100       68 $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       226 return $class->new($name) if $name =~ $ip_rx;
198              
199             # resolve name
200 3         150090 my ( $err, @res ) =
201             Socket::getaddrinfo( $name, "", { socktype => Socket::SOCK_RAW, family => Socket::AF_UNSPEC } );
202              
203 3 100       55 if ($err) {
204              
205             # no error, just no resolveable name
206 2 50       61 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         4 my @ips;
214 1         7 while ( my $ai = shift @res ) {
215 4         7 my $n;
216 4 100       13 if ( $ai->{family} == Socket::AF_INET ) {
217 2         8 $n = substr( $ai->{addr}, 4, 4 );
218             }
219             else {
220 2         5 $n = substr( $ai->{addr}, 8, 16 );
221             }
222 4         23 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 76 $_[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 18 $_[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 658 return $_[0]->{as_string} if exists $_[0]->{as_string};
294              
295 61         124 my $n = substr( $_[0]->{binary}, 1, );
296              
297             # no bug in Socket::inet_ntop for IPv4, just return
298 61 100       139 if ( $_[0]->{version} == 4 ) {
299 34         206 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         91 my $str = Socket::inet_ntop( Socket::AF_INET6, $n );
308              
309             # first handle normal case, no dot '.'
310 27 100       70 if ( index( $str, '.' ) < 0 ) {
311 26         105 return $_[0]->{as_string} = $str;
312             }
313              
314             # handle the bug, use our pure perl inet_ntop_pp
315 1         5 return $_[0]->{as_string} = Net::IPAM::Util::inet_ntop_pp( Socket::AF_INET6, $n );
316             }
317              
318             =head2 incr
319              
320             Returns the next IP address, returns undef on overflow.
321              
322             $next_ip = Net::IPAM::IP->new('fe80::1')->incr // die 'overflow,';
323             say $next_ip; # fe80::2
324              
325             =cut
326              
327             sub incr {
328 7     7 1 32 my $n_plus1 = Net::IPAM::Util::incr_n( $_[0]->bytes );
329              
330             # overflow?
331 7 100       26 return unless defined $n_plus1;
332              
333             # sort of cloning
334 4         11 $_[0]->new_from_bytes($n_plus1);
335             }
336              
337             =head2 decr
338              
339             Returns the previous IP address, returns undef on underflow.
340              
341             $prev_ip = Net::IPAM::IP->new('fe80::1')->decr // die 'underflow,';
342             say $prev_ip; # fe80::
343              
344             =cut
345              
346             sub decr {
347 6     6 1 28 my $n_minus1 = Net::IPAM::Util::decr_n( $_[0]->bytes );
348              
349             # underflow?
350 6 100       19 return unless defined $n_minus1;
351              
352             # sort of cloning
353 3         7 $_[0]->new_from_bytes($n_minus1);
354             }
355              
356             =head2 expand
357              
358             Expand IP address into canonical form, useful for C<< grep >>, aligned output and lexical C<< sort >>
359              
360             Net::IPAM::IP->new('1.2.3.4')->expand; # '001.002.003.004'
361             Net::IPAM::IP->new('fe80::1')->expand; # 'fe80:0000:0000:0000:0000:0000:0000:0001'
362              
363             =cut
364              
365             sub expand {
366 5 100   5 1 21 return $_[0]->{expand} if exists $_[0]->{expand};
367              
368 4         10 my $n = substr( $_[0]->{binary}, 1, );
369              
370 4 100       12 if ( $_[0]->{version} == 6 ) {
371 1         6 my @hextets = unpack( 'H4' x 8, $n );
372              
373             # cache it and return
374 1         9 return $_[0]->{expand} = join( ':', @hextets );
375             }
376              
377             # IPv4
378 3         15 my @octets = unpack( 'C4', $n );
379              
380             # cache it and return
381 3         31 return $_[0]->{expand} = sprintf( "%03d.%03d.%03d.%03d", @octets );
382             }
383              
384             =head2 reverse
385              
386             Reverse IP address, needed for PTR entries in DNS zone files.
387              
388             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'
389             Net::IPAM::IP->new('1.2.3.4')->reverse; # '4.3.2.1'
390              
391             =cut
392              
393             sub reverse {
394 5 100   5 1 19 return $_[0]->{reverse} if exists $_[0]->{reverse};
395              
396             # unpack to version and network byte order (from Socket::inet_pton)
397             # my ( $v, $n ) = unpack( 'C a*', $_[0]->{binary} );
398             # substr() ist faster
399 4         11 my $n = substr( $_[0]->{binary}, 1, );
400              
401 4 100       10 if ( $_[0]->{version} == 6 ) {
402 1         3 my $hex_str = unpack( 'H*', $n );
403 1         8 my @nibbles = unpack( 'A' x 32, $hex_str );
404              
405             # cache it and return
406 1         9 return $_[0]->{reverse} = join( '.', reverse @nibbles );
407             }
408              
409             # IPv4
410 3         12 my @octets = unpack( 'C4', $n );
411              
412             # cache it and return
413 3         37 return $_[0]->{reverse} = join( '.', reverse @octets );
414             }
415              
416             =head2 getname([$error_cb])
417              
418             Returns the DNS name for the ip object or undef if there is no PTR RR.
419              
420             say Net::IPAM::IP->new('2001:4860:4860::8888')->getname; # dns.google.
421              
422             L calls the L function C<< getnameinfo() >> under the hood.
423              
424             With no error callback L just calls C<< carp() >> with underlying Socket errors.
425              
426             =head3 LIMITATION:
427              
428             Returns just one name even if the IP has more than one PTR RR. This is a limitation
429             of Socket::getnameinfo. If you need all names for IPs with more than one PTR RR then you should
430             use L or similar modules.
431              
432             =cut
433              
434             sub getname {
435 1     1 1 526 my ( $self, $error_cb ) = @_;
436 1 50       8 $error_cb = \&Carp::carp unless defined $error_cb;
437              
438 1         2 my $sock_addr;
439 1 50       5 if ( $self->{version} == 4 ) {
440 1         5 $sock_addr = Socket::pack_sockaddr_in( 0, $self->bytes );
441             }
442             else {
443 0         0 $sock_addr = Socket::pack_sockaddr_in6( 0, $self->bytes );
444             }
445              
446 1         177058 my ( $err, $name ) = Socket::getnameinfo( $sock_addr, Socket::NI_NAMEREQD, Socket::NIx_NOSERV );
447              
448 1 50       17 if ($err) {
449              
450             # no error, just no resolveable name
451 0 0       0 return if $err == Socket::EAI_NONAME;
452              
453 0         0 $error_cb->("getnameinfo($self): $err");
454 0         0 return;
455             }
456              
457 1         17 $name;
458             }
459              
460             =head2 bytes
461              
462             $ip = Net::IPAM::IP->new('fe80::');
463             $bytes = $ip->bytes; # "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
464              
465             $ip = Net::IPAM::IP->new('10.0.0.1');
466             $bytes = $ip->bytes; # "\x0a\x00\x00\x01"
467              
468             Returns the packed IP address as byte-string. It's the opposite to L
469              
470             =cut
471              
472             # drop first byte (version) and return the packed IP address,
473             sub bytes {
474 20     20 1 87 substr( $_[0]->{binary}, 1 );
475             }
476              
477             =head1 FUNCTIONS
478              
479             =head2 sort_ip
480              
481             use Net::IPAM::IP 'sort_ip';
482              
483             @sorted_ips = sort_ip @unsorted_ips;
484              
485             Faster sort implemention (Schwartzian transform) as explcit sort function:
486              
487             @sorted_ips = sort { $a->cmp($b) } @unsorted_ips;
488              
489             =cut
490              
491             sub sort_ip {
492 11         18 return map { $_->[0] }
493 26         35 sort { $a->[1] cmp $b->[1] }
494 1     1 1 488 map { [ $_, $_->{binary} ] } @_;
  11         24  
495             }
496              
497             =head1 OPERATORS
498              
499             L overloads the following operators.
500              
501             =head2 bool
502              
503             my $bool = !!$ip;
504              
505             Always true.
506              
507             =head2 stringify
508              
509             my $str = "$ip";
510              
511             Alias for L.
512              
513             =cut
514              
515             use overload
516 55     55   3311 '""' => sub { shift->to_string },
517 32     32   2913 bool => sub { 1 },
518 10     10   10665 fallback => 1;
  10         8018  
  10         93  
519              
520             =head1 WARNING
521              
522             Some Socket::inet_XtoY implementations are hopelessly buggy.
523              
524             Tests are made during loading and in case of errors, these functions are redefined
525             with a (slower) pure-perl implementation.
526              
527             =cut
528              
529             # On some platforms, inet_pton accepts various forms of invalid input or discards valid input.
530             # In this case use a (slower) pure-perl implementation for Socket::inet_pton.
531             # and also for Socket::inet_ntop, I don't trust that too.
532             BEGIN {
533 10 100 66 10   740 if ( # wrong valid
      66        
      33        
      33        
      33        
      66        
      66        
      66        
534             defined Socket::inet_pton( Socket::AF_INET, '010.0.0.1' )
535             || defined Socket::inet_pton( Socket::AF_INET, '10.000.0.1' )
536             || defined Socket::inet_pton( Socket::AF_INET6, 'cafe:::' )
537             || defined Socket::inet_pton( Socket::AF_INET6, 'cafe::1::' )
538             || defined Socket::inet_pton( Socket::AF_INET6, 'cafe::1:' )
539             || defined Socket::inet_pton( Socket::AF_INET6, ':cafe::' )
540              
541             # wrong invalid
542             || !defined Socket::inet_pton( Socket::AF_INET6, 'caFe::' )
543             || !defined Socket::inet_pton( Socket::AF_INET6, '::' )
544             || !defined Socket::inet_pton( Socket::AF_INET, '0.0.0.0' )
545             )
546             {
547 10     10   1749 no warnings 'redefine';
  10         19  
  10         786  
548 2         51 *Socket::inet_pton = \&Net::IPAM::Util::inet_pton_pp;
549 2         77 *Socket::inet_ntop = \&Net::IPAM::Util::inet_ntop_pp;
550             }
551             }
552              
553             =head1 AUTHOR
554              
555             Karl Gaissmaier, C<< >>
556              
557             =head1 BUGS
558              
559             Please report any bugs or feature requests to C, or through
560             the web interface at L. I will be notified, and then you'll
561             automatically be notified of progress on your bug as I make changes.
562              
563             =head1 SUPPORT
564              
565             You can find documentation for this module with the perldoc command.
566              
567             perldoc Net::IPAM::IP
568              
569              
570             You can also look for information at:
571              
572             =over 4
573              
574             =item * on github
575              
576             TODO
577              
578             =back
579              
580             =head1 SEE ALSO
581              
582             L
583             L
584             L
585              
586             =head1 LICENSE AND COPYRIGHT
587              
588             This software is copyright (c) 2020-2021 by Karl Gaissmaier.
589              
590             This is free software; you can redistribute it and/or modify it under
591             the same terms as the Perl 5 programming language system itself.
592              
593              
594             =cut
595              
596             1; # End of Net::IPAM::IP