File Coverage

blib/lib/Net/IPAM/Util.pm
Criterion Covered Total %
statement 82 82 100.0
branch 34 36 94.4
condition 9 11 81.8
subroutine 17 17 100.0
pod 4 4 100.0
total 146 150 97.3


line stmt bran cond sub pod time code
1             package Net::IPAM::Util;
2              
3 10     10   101 use 5.10.0;
  10         26  
4 10     10   43 use strict;
  10         14  
  10         161  
5 10     10   37 use warnings;
  10         14  
  10         187  
6 10     10   38 use utf8;
  10         14  
  10         35  
7              
8 10     10   161 use Carp ();
  10         13  
  10         96  
9 10     10   33 use Socket ();
  10         15  
  10         140  
10              
11 10     10   45 use Exporter 'import';
  10         23  
  10         4295  
12             our @EXPORT_OK = qw(incr_n decr_n inet_ntop_pp inet_pton_pp);
13              
14             =head1 NAME
15              
16             Net::IPAM::Util - A selection of general utility subroutines for Net::IPAM
17              
18             =head1 SYNOPSIS
19              
20             use Net::IPAM::Util qw(incr_n inet_ntop_pp inet_pton_pp);
21              
22             $n = incr_n("\x0a\x00\x00\x01"); # 10.0.0.2
23             $n = incr_n( pack( 'n8', 0x2001, 0xdb8, 0, 0, 0, 0, 0, 1 ) ); # 2001:db8::2
24              
25             $n = decr_n("\x0a\x00\x00\x01"); # 10.0.0.0
26             $n = decr_n( pack( 'n8', 0x2001, 0xdb8, 0, 0, 0, 0, 0, 1 ) ); # 2001:db8::
27              
28             $n = inet_pton_pp( AF_INET6, '2001:db8::fe1' );
29             say inet_ntop_pp( AF_INET, "\x0a\x00\x00\x01" ); # 10.0.0.1
30              
31             =cut
32              
33             =head1 FUNCTIONS
34              
35             =head2 $address_plusplus = incr_n( $address )
36              
37             Increment a packed IPv4 or IPv6 address in network byte order. Returns undef on overflow.
38              
39             This increment function is needed in L and L for transparent handling
40             of IPv4 and IPv6 addresses and blocks.
41              
42             No need for L, this pure perl algorithm works for all uint_n in network byte order,
43             where n is a multiple of 32: uint_32, uint_64, uint_96, uint_128, ...
44              
45             =cut
46              
47             sub incr_n {
48 13   66 13 1 1039 my $n = shift // Carp::croak("missing argument");
49              
50             # split in individual 32 bit unsigned ints in network byte order
51 12         38 my @N = unpack( 'N*', $n );
52              
53             # start at least significant N
54 12         18 my $i = $#N;
55              
56             # carry?
57 12         26 while ( $N[$i] == 0xffff_ffff ) {
58              
59             # OVERFLOW, it's already the most significant N
60 14 100       28 return if $i == 0;
61              
62             # set this N to zero: 0xffff_ffff + 1 = 0x0000_0000 + carry
63 10         12 $N[$i] = 0;
64              
65             # carry on to next more significant N
66 10         13 $i--;
67             }
68              
69             # incr this N
70 8         10 $N[$i]++;
71              
72             # pack again the individual 32 bit integers in network byte order to one byte string
73 8         34 return pack( 'N*', @N );
74             }
75              
76             =head2 $address_minusminus = decr_n( $address )
77              
78             Decrement a packed IPv4 or IPv6 address in network byte order. Returns undef on underflow.
79              
80             This decrement function is needed in L and L for transparent handling
81             of IPv4 and IPv6 addresses and blocks.
82              
83             No need for L, this pure perl algorithm works for all uint_n in network byte order,
84             where n is a multiple of 32: uint_32, uint_64, uint_96, uint_128, ...
85              
86             =cut
87              
88             sub decr_n {
89 12   66 12 1 1268 my $n = shift // Carp::croak("missing argument");
90              
91             # split in individual 32 bit unsigned ints in network byte order
92 11         26 my @N = unpack( 'N*', $n );
93              
94             # start at least significant N
95 11         17 my $i = $#N;
96              
97             # carry?
98 11         24 while ( $N[$i] == 0 ) {
99              
100             # UNDERFLOW, it's already the most significant N
101 14 100       23 return if $i == 0;
102              
103             # set this N to ffff_ffff: 0 - 1 = 0xffff_ffff + carry
104 10         11 $N[$i] = 0xffff_ffff;
105              
106             # carry on to next more significant N
107 10         16 $i--;
108             }
109              
110             # decr this N
111 7         7 $N[$i]--;
112              
113             # pack again the individual 32 bit integers in network byte order to one byte string
114 7         31 return pack( 'N*', @N );
115             }
116              
117             =head2 $string = inet_ntop_pp( $family, $address )
118              
119             A pure perl implementation for (buggy) Socket::inet_ntop.
120              
121             Takes an address family (C or C) and
122             a packed binary address structure and translates it
123             into a human-readable textual representation of the address.
124              
125             =cut
126              
127             sub inet_ntop_pp {
128              
129             # modify @_ = (AF_INETx, $ip) => @_ = ($ip)
130 19     19 1 24 my $v = shift;
131 19 100       46 goto &_inet_ntop_v4_pp if $v == Socket::AF_INET;
132 11         32 goto &_inet_ntop_v6_pp;
133             }
134              
135             =head2 $address = inet_pton_pp( $family, $string )
136              
137             A pure perl implementation for (buggy) Socket::inet_pton.
138              
139             Takes an address family (C or C) and a string
140             containing a textual representation of an address in that family and
141             translates that to an packed binary address structure.
142              
143             =cut
144              
145             sub inet_pton_pp {
146              
147             # modify @_ = (AF_INETx, $ip) => @_ = ($ip)
148 78     78 1 104 my $v = shift;
149 78 100       163 goto &_inet_pton_v4_pp if $v == Socket::AF_INET;
150 58 100       178 goto &_inet_pton_v4in6_pp if $_[0] =~ m/^::ffff:/i;
151 48         127 goto &_inet_pton_v6_pp;
152             }
153              
154             # easy peasy
155             sub _inet_ntop_v4_pp {
156 8 50   8   24 return if length( $_[0] ) != 4;
157 8         129 return join( '.', unpack( 'C4', $_[0] ) );
158             }
159              
160             # (1) Hexadecimal digits are expressed as lower-case letters.
161             # For example, 2001:db8::1 is preferred over 2001:DB8::1.
162             #
163             # (2) Leading zeros in each 16-bit field are suppressed.
164             # For example, 2001:0db8::0001 is rendered as 2001:db8::1,
165             # though any all-zero field that is explicitly presented is rendered as 0.
166             #
167             # (3) Representations are shortened as much as possible.
168             # The longest sequence of consecutive all-zero fields is replaced with double-colon.
169             # If there are multiple longest runs of all-zero fields, then it is the leftmost that is compressed.
170             # E.g., 2001:db8:0:0:1:0:0:1 is rendered as 2001:db8::1:0:0:1 rather than as 2001:db8:0:0:1::1.
171             #
172             # (4) "::" is not used to shorten just a single 0 field.
173             # For example, 2001:db8:0:0:0:0:2:1 is shortened to 2001:db8::2:1,
174             # but 2001:db8:0000:1:1:1:1:1 is rendered as 2001:db8:0:1:1:1:1:1.
175             #
176             sub _inet_ntop_v6_pp {
177 11     11   20 my $n = shift;
178 11 50       21 return if length($n) != 16;
179              
180             # expand binary to hex, lower case, rule (1), leading zeroes squashed
181             # add : at left and right for symmetric squashing algo, see below
182             # :2001:db8:85a3:0:0:8a2e:370:7334:
183 11         67 my $ip = sprintf( ':%x:%x:%x:%x:%x:%x:%x:%x:', unpack( 'n8', $n ) );
184              
185             # rule (3,4) # squash the longest sequence of consecutive all-zero fields
186             # e.g. :0:0: (?!not followed) :0\1
187 11         61 $ip =~ s/(:0[:0]+:) (?! .+ :0\1)/::/x;
188              
189 11 100       40 $ip =~ s/^:// unless $ip =~ /^::/; # trim additional left
190 11 100       40 $ip =~ s/:$// unless $ip =~ /::$/; # trim additional right
191 11         28 return $ip;
192             }
193              
194             sub _inet_pton_v4_pp {
195              
196             # 'C' may overflow for values > 255, check below
197 10     10   59 no warnings qw(pack numeric);
  10         18  
  10         3557  
198 30     30   155 my $n = pack( 'C4', split( /\./, $_[0] ) );
199              
200             # unpack(pack...) must be idempotent
201             # check for overflow errors or leading zeroes
202 30 100       158 return unless $_[0] eq join( '.', unpack( 'C4', $n ) );
203              
204 14         36 return $n;
205             }
206              
207             sub _inet_pton_v4in6_pp {
208             # skip ::ffff:
209 10   100 10   30 my $n = _inet_pton_v4_pp(substr($_[0], 7)) // return;
210             # back ::ffff:
211 4         14 return pack('n6', 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff) . $n;
212             }
213              
214             sub _inet_pton_v6_pp {
215 48     48   71 my $ip = shift;
216              
217 48 100       116 return if $ip =~ m/[^a-fA-F0-9:]/;
218 46 100       89 return if $ip =~ m/:::/;
219              
220             # starts with just one colon: :cafe...
221 42 100       80 return if $ip =~ m/^:[^:]/;
222              
223             # ends with just one colon: ..:cafe:affe:
224 40 100       70 return if $ip =~ m/[^:]:$/;
225              
226 38         60 my $col_count = $ip =~ tr/://;
227 38         112 my $dbl_col_count = $ip =~ s/::/::/g;
228              
229 38 100       69 return if $col_count > 7;
230 34 100       61 return if $dbl_col_count > 1;
231 28 100 100     62 return if $dbl_col_count == 0 && $col_count != 7;
232              
233             # normalize for splitting, prepend or append 0
234 24         42 $ip =~ s/^:: /0::/x;
235 24         40 $ip =~ s/ ::$/::0/x;
236              
237             # expand ::
238 24         58 my $expand_dbl_col = ':0' x ( 8 - $col_count ) . ':';
239 24         62 $ip =~ s/::/$expand_dbl_col/;
240              
241 24         77 my @hextets = split( /:/, $ip );
242 24 100       42 return if grep { length > 4 } @hextets;
  192         326  
243              
244 22         33 my $n = pack( 'n8', map { hex } @hextets );
  176         250  
245 22         62 return $n;
246             }
247              
248             =head1 AUTHOR
249              
250             Karl Gaissmaier, C<< >>
251              
252             =head1 BUGS
253              
254             Please report any bugs or feature requests to C, or through
255             the web interface at L.
256             I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
257              
258             =head1 SUPPORT
259              
260             You can find documentation for this module with the perldoc command.
261              
262             perldoc Net::IPAM::Util
263              
264             You can also look for information at:
265              
266             =over 4
267              
268             =item * on github
269              
270             TODO
271              
272             =back
273              
274             =head1 SEE ALSO
275              
276             L
277             L
278             L
279              
280             =head1 LICENSE AND COPYRIGHT
281              
282             This software is copyright (c) 2020-2022 by Karl Gaissmaier.
283              
284             This is free software; you can redistribute it and/or modify it under
285             the same terms as the Perl 5 programming language system itself.
286              
287             =cut
288              
289             1; # End of Net::IPAM::Util