File Coverage

blib/lib/IP/Random.pm
Criterion Covered Total %
statement 80 90 88.8
branch 9 14 64.2
condition 9 11 81.8
subroutine 15 16 93.7
pod 3 3 100.0
total 116 134 86.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             #
4             # Copyright (C) 2016 Joelle Maslak
5             # All Rights Reserved - See License
6             #
7              
8             package IP::Random;
9             $IP::Random::VERSION = '1.004';
10             # ABSTRACT: Generate IP Addresses Randomly
11              
12              
13             # Some boilerplate
14 1     1   695 use v5.20;
  1         4  
15 1     1   6 use strict;
  1         3  
  1         34  
16 1     1   7 use warnings;
  1         3  
  1         56  
17              
18 1     1   7 use feature 'signatures';
  1         2  
  1         119  
19 1     1   6 no warnings 'experimental::signatures';
  1         2  
  1         50  
20              
21 1     1   7 use Carp;
  1         2  
  1         73  
22 1     1   6 use List::Util qw(none notall pairs);
  1         2  
  1         89  
23 1     1   433 use Socket qw(inet_aton);
  1         2769  
  1         909  
24              
25             my $DEFAULT_IPV4_EXCLUDE = {
26             '0.0.0.0/8' => 'rfc1122',
27             '10.0.0.0/8' => 'rfc1918',
28             '100.64.0.0/10' => 'rfc6598',
29             '127.0.0.0/8' => 'rfc1122',
30             '169.254.0.0/16' => 'rfc3927',
31             '172.16.0.0/12' => 'rfc1918',
32             '192.0.0.0/24' => 'rfc5736',
33             '192.0.2.0/24' => 'rfc5737',
34             '192.88.99.0/24' => 'rfc3068',
35             '192.168.0.0/16' => 'rfc1918',
36             '198.18.0.0/15' => 'rfc2544',
37             '198.51.100.0/24' => 'rfc5737',
38             '203.0.113.0/24' => 'rfc5737',
39             '224.0.0.0/4' => 'rfc3171',
40             '240.0.0.0/4' => 'rfc1112',
41             '255.255.255.255/32' => 'rfc919',
42             };
43              
44              
45 1007     1007 1 12419 sub random_ipv4 ( %args ) {
  1007         1213  
  1007         1108  
46 1007   100 7464   6198 $args{rand} //= sub { int( rand( shift() + 1 ) ) };
  7464         15553  
47              
48             # Can't have exclude and additional_types_allowed both existing
49 1007 50 66     2020 if ( exists( $args{exclude} ) && exists( $args{additional_types_allowed} ) )
50             {
51 0         0 croak( "Cannot define both 'exclude' and "
52             . "'additional_types_allowed' parameters" );
53             }
54              
55             # This saves us some later branches
56             # Define defaults
57 1007   100     3302 $args{additional_types_allowed} //= [];
58 1007   100     3084 $args{additional_exclude} //= [];
59              
60             # What are valid option names?
61 1007         2756 my $optre = qr/\A(?:rand|exclude|additional_(?:types_allowed|exclude))\z/;
62              
63             # Make sure all options are valid
64 1007 50   3023   4103 if ( notall { m/$optre/ } keys %args ) {
  3023         12340  
65 0         0 my (@bad) = grep { !m/$optre/ } keys %args;
  0         0  
66 0         0 croak( "unknown named argument passed to random_ipv4: " . $bad[0] );
67             }
68              
69             # Get default excludes
70 1007 100       3362 if ( !defined( $args{exclude} ) ) {
71             $args{exclude} =
72 1005         1935 _get_ipv4_excludes( $args{additional_types_allowed} );
73             }
74              
75             # Build a closure for checking to see if an address is excluded
76 1007         1306 my (@exclude_all) = ( @{ $args{exclude} }, @{ $args{additional_exclude} } );
  1007         1454  
  1007         2437  
77 1867     1867   1992 my $is_not_excluded = sub($addr) {
  1867         2576  
  1867         2643  
78 1867         6450 none { in_ipv4_subnet( $_, $addr ) } @exclude_all;
  23131         36919  
79 1007         2669 };
80              
81 1007         1357 my $addr;
82 1007         1148 do {
83 1867         2325 my @parts;
84 1867         3303 for my $octet ( 1 .. 4 ) {
85 7468         12000 push @parts, $args{rand}->( 255, $octet );
86             }
87 1867         8585 $addr = join '.', @parts;
88             } until $is_not_excluded->($addr);
89              
90 1007         9415 return $addr;
91             }
92              
93             # Private sub to build the default list of excludes, when passed a list
94             # of additional types allowed
95             #
96             # Returns a list ref
97 1005     1005   1110 sub _get_ipv4_excludes( $addl_types ) {
  1005         1240  
  1005         1100  
98             my @ret = grep {
99 16080         21475 my $k = $_;
100 16080     62   28745 none { $DEFAULT_IPV4_EXCLUDE->{$k} eq $_ } @{ $addl_types }
  62         130  
  16080         44899  
101 1005         1267 } keys %{ $DEFAULT_IPV4_EXCLUDE };
  1005         3518  
102              
103 1005         2746 return \@ret;
104             }
105              
106              
107 28138     28138 1 39312 sub in_ipv4_subnet ( $sub_cidr, $ip ) {
  28138         36461  
  28138         32204  
  28138         29881  
108 28138 50       44248 if ( !defined($sub_cidr) ) { confess("subnet_cidr is not defined"); }
  0         0  
109 28138 50       42837 if ( !defined($ip) ) { confess("ip is not defined"); }
  0         0  
110              
111 28138 50       96995 if ( $sub_cidr !~ m/\A(?:[\d\.]+)(?:\/(?:\d+))?\z/ ) {
112 0         0 confess("$sub_cidr is not in the format A.B.C.D/N");
113             }
114 28138         99114 my ( $sub_net, $sub_mask ) = $sub_cidr =~ m/\A([\d\.]+)(?:\/(\d+))?\z/ms;
115 28138   50     56636 $sub_mask //= 32;
116              
117 28138         77032 my $addr = unpack( 'N', inet_aton( $ip ) );
118 28138         67417 my $sub = unpack( 'N', inet_aton( $sub_net ) );
119              
120 28138         39575 my $mask = 0;
121 28138         61937 for ( 1 .. $sub_mask ) {
122 367896         398361 $mask = $mask >> 1;
123 367896         451191 $mask = $mask | ( 1 << 31 );
124             }
125              
126 28138 100       52205 if ( ( $addr & $mask ) == ( $sub & $mask ) ) {
127 864         3476 return 1;
128             }
129              
130 27274         56453 return undef;
131             }
132              
133              
134 0     0 1   sub default_ipv4_exclude() {
  0            
135 0           return _get_ipv4_excludes( [] );
136             }
137              
138             1;
139              
140             __END__