| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | #!/usr/bin/perl | 
| 2 |  |  |  |  |  |  | #=============================================================================== | 
| 3 |  |  |  |  |  |  | #      PODNAME:  Net::IP::Identifier | 
| 4 |  |  |  |  |  |  | #     ABSTRACT:  Identify IPs that fall within collections of network blocks | 
| 5 |  |  |  |  |  |  | # | 
| 6 |  |  |  |  |  |  | #       AUTHOR:  Reid Augustin (REID) | 
| 7 |  |  |  |  |  |  | #        EMAIL:  reid@hellosix.com | 
| 8 |  |  |  |  |  |  | #      CREATED:  Mon Oct  6 10:20:33 PDT 2014 | 
| 9 |  |  |  |  |  |  | #=============================================================================== | 
| 10 |  |  |  |  |  |  |  | 
| 11 | 3 |  |  | 3 |  | 4033 | use 5.002; | 
|  | 3 |  |  |  |  | 13 |  | 
|  | 3 |  |  |  |  | 152 |  | 
| 12 | 3 |  |  | 3 |  | 21 | use strict; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 143 |  | 
| 13 | 3 |  |  | 3 |  | 81 | use warnings; | 
|  | 3 |  |  |  |  | 4 |  | 
|  | 3 |  |  |  |  | 207 |  | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | { | 
| 16 |  |  |  |  |  |  | package Local::Payload; | 
| 17 | 3 |  |  | 3 |  | 2355 | use Moo; | 
|  | 3 |  |  |  |  | 42509 |  | 
|  | 3 |  |  |  |  | 22 |  | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | has entity => ( | 
| 20 |  |  |  |  |  |  | is => 'rw', | 
| 21 |  |  |  |  |  |  | isa => sub { die "Not a Net::IP::Identifier::Plugin\n" | 
| 22 |  |  |  |  |  |  | if (not $_[0]->does('Net::IP::Identifier_Role')); }, | 
| 23 |  |  |  |  |  |  | ); | 
| 24 |  |  |  |  |  |  | has ip => ( | 
| 25 |  |  |  |  |  |  | is => 'rw', | 
| 26 |  |  |  |  |  |  | isa => sub { die "Not a Net::IP::Identifier::Net\n" | 
| 27 |  |  |  |  |  |  | if (not $_[0]->isa('Net::IP::Identifier::Net')); }, | 
| 28 |  |  |  |  |  |  | ); | 
| 29 |  |  |  |  |  |  | } | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | package Net::IP::Identifier; | 
| 33 | 3 |  |  | 3 |  | 8312 | use Getopt::Long qw(:config pass_through); | 
|  | 3 |  |  |  |  | 36715 |  | 
|  | 3 |  |  |  |  | 35 |  | 
| 34 | 3 |  |  | 3 |  | 780 | use File::Spec; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 196 |  | 
| 35 | 3 |  |  | 3 |  | 624 | use Net::IP::Identifier::Net; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 161 |  | 
| 36 | 3 |  |  | 3 |  | 2004 | use Net::IP::Identifier::Binode; | 
|  | 3 |  |  |  |  | 13 |  | 
|  | 3 |  |  |  |  | 142 |  | 
| 37 | 3 |  |  | 3 |  | 2089 | use Net::IP::Identifier::Regex; | 
|  | 3 |  |  |  |  | 13 |  | 
|  | 3 |  |  |  |  | 156 |  | 
| 38 | 3 |  |  | 3 |  | 33 | use Carp; | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 262 |  | 
| 39 | 3 |  |  | 3 |  | 20 | use Moo; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 29 |  | 
| 40 | 3 |  |  | 3 |  | 1098 | use namespace::clean; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 29 |  | 
| 41 | 3 |  |  | 3 |  | 2795 | use Module::Pluggable; | 
|  | 3 |  |  |  |  | 24498 |  | 
|  | 3 |  |  |  |  | 21 |  | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | our $VERSION = '0.111'; # VERSION | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | has joiners => ( | 
| 46 |  |  |  |  |  |  | is => 'rw', | 
| 47 |  |  |  |  |  |  | default => sub { [ ':', '.' ] }, | 
| 48 |  |  |  |  |  |  | ); | 
| 49 |  |  |  |  |  |  | has cidr => ( | 
| 50 |  |  |  |  |  |  | is => 'rw', | 
| 51 |  |  |  |  |  |  | ); | 
| 52 |  |  |  |  |  |  | has parents => ( | 
| 53 |  |  |  |  |  |  | is => 'rw', | 
| 54 |  |  |  |  |  |  | ); | 
| 55 |  |  |  |  |  |  | has overlaps => ( | 
| 56 |  |  |  |  |  |  | is => 'rw', | 
| 57 |  |  |  |  |  |  | ); | 
| 58 |  |  |  |  |  |  | has re => ( # regular expressions for IP addresses | 
| 59 |  |  |  |  |  |  | is => 'lazy', | 
| 60 |  |  |  |  |  |  | default => sub { Net::IP::Identifier::Regex->new }, | 
| 61 |  |  |  |  |  |  | ); | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | my $imports; | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | my (undef, undef, $myName) = File::Spec->splitpath($0); | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | my $help_msg = < | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | $myName [ options ] IP [ IP... ] | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | If IP belongs to a known entity (a Net::IP::Identifier::Plugin), | 
| 72 |  |  |  |  |  |  | print the entity. | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | IP may be dotted decimal format: N.N.N.N, range format: N.N.N.N - N.N.N.N, | 
| 75 |  |  |  |  |  |  | CIDR format: N.N.N.N/W, or a filename from which IPs will be extracted.  If | 
| 76 |  |  |  |  |  |  | no IP or filename is found on the command line, STDIN is opened. | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | Options (may be abbreviated): | 
| 79 |  |  |  |  |  |  | parents   => prepend Net::IP::Identifier objects of parent entities | 
| 80 |  |  |  |  |  |  | cidr      => append Net::IP::Identifier::Net objects to entities | 
| 81 |  |  |  |  |  |  | filename  => read from file(s) instead of command line args | 
| 82 |  |  |  |  |  |  | overlaps  => show overlapping netblocks during binary tree construction | 
| 83 |  |  |  |  |  |  | help      => this message | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | EO_HELP | 
| 86 |  |  |  |  |  |  | ; | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | __PACKAGE__->run unless caller;     # modulino | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | sub run { | 
| 91 | 0 |  |  | 0 | 1 | 0 | my ($class) = @_; | 
| 92 |  |  |  |  |  |  |  | 
| 93 | 0 |  |  |  |  | 0 | my %opts; | 
| 94 |  |  |  |  |  |  | my $overlaps; | 
| 95 | 0 |  |  |  |  | 0 | my $filename; | 
| 96 | 0 |  |  |  |  | 0 | my $help; | 
| 97 |  |  |  |  |  |  |  | 
| 98 | 0 | 0 |  |  |  | 0 | exit 0 if (not | 
| 99 |  |  |  |  |  |  | GetOptions( | 
| 100 |  |  |  |  |  |  | 'parents'    => \$opts{parents}, | 
| 101 |  |  |  |  |  |  | 'cidr'       => \$opts{cidr}, | 
| 102 |  |  |  |  |  |  | 'overlaps'   => \$overlaps, | 
| 103 |  |  |  |  |  |  | 'filename=s' => \$filename, | 
| 104 |  |  |  |  |  |  | 'help'       => \$help, | 
| 105 |  |  |  |  |  |  | ) | 
| 106 |  |  |  |  |  |  | ); | 
| 107 |  |  |  |  |  |  |  | 
| 108 | 0 | 0 |  |  |  | 0 | if ($help) { | 
| 109 | 0 |  |  |  |  | 0 | print $help_msg; | 
| 110 | 0 |  |  |  |  | 0 | exit; | 
| 111 |  |  |  |  |  |  | } | 
| 112 |  |  |  |  |  |  |  | 
| 113 | 0 |  |  |  |  | 0 | my $identifier = __PACKAGE__->new(%opts); | 
| 114 |  |  |  |  |  |  |  | 
| 115 | 0 | 0 |  |  |  | 0 | unshift @ARGV, $filename if ($filename); | 
| 116 | 0 | 0 |  |  |  | 0 | if (not @ARGV) { | 
| 117 | 0 |  |  |  |  | 0 | $identifier->parse_fh(\*STDIN); | 
| 118 |  |  |  |  |  |  | } | 
| 119 |  |  |  |  |  |  |  | 
| 120 | 0 |  |  |  |  | 0 | while (@ARGV) { | 
| 121 | 0 |  |  |  |  | 0 | my $arg = shift @ARGV; | 
| 122 | 0 | 0 | 0 |  |  | 0 | if (-f $arg) { | 
|  |  | 0 | 0 |  |  |  |  | 
| 123 | 0 |  |  |  |  | 0 | open my $fh, '<', $arg; | 
| 124 | 0 | 0 |  |  |  | 0 | croak "Can't open $arg for reading\n" if not $fh; | 
| 125 | 0 |  |  |  |  | 0 | $identifier->parse_fh($fh); | 
| 126 | 0 |  |  |  |  | 0 | close $fh; | 
| 127 |  |  |  |  |  |  | next | 
| 128 | 0 |  |  |  |  | 0 | } | 
| 129 |  |  |  |  |  |  | elsif ($ARGV[0]        and  # accept N.N.N.N - N.N.N.N for network blocks too | 
| 130 |  |  |  |  |  |  | $ARGV[0] eq '-' and | 
| 131 |  |  |  |  |  |  | $ARGV[1]) { | 
| 132 | 0 |  |  |  |  | 0 | $arg .= shift(@ARGV) . shift(@ARGV); | 
| 133 |  |  |  |  |  |  | } | 
| 134 |  |  |  |  |  |  |  | 
| 135 | 0 |  | 0 |  |  | 0 | print $identifier->identify($arg) || $arg, "\n"; | 
| 136 |  |  |  |  |  |  | } | 
| 137 |  |  |  |  |  |  |  | 
| 138 | 0 | 0 |  |  |  | 0 | if ($overlaps) { | 
| 139 | 0 |  |  |  |  | 0 | for my $return (@{$identifier->tree_overlaps}) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 140 | 0 |  |  |  |  | 0 | my @r = map { $identifier->join($_->payload->entity, $_->payload->ip); } @{$return}; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 141 | 0 |  |  |  |  | 0 | warn join(' => ', @r), "\n"; | 
| 142 |  |  |  |  |  |  | } | 
| 143 |  |  |  |  |  |  | } | 
| 144 |  |  |  |  |  |  | } | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | sub import { | 
| 147 | 3 |  |  | 3 |  | 29 | my ($class, @imports) = @_; | 
| 148 |  |  |  |  |  |  |  | 
| 149 | 3 | 50 |  |  |  | 53 | $imports = \@imports if (@imports);   # save import list in class variable | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  |  | 
| 152 |  |  |  |  |  |  | sub parse_fh { | 
| 153 | 0 |  |  | 0 | 0 | 0 | my ($self, $fh) = @_; | 
| 154 |  |  |  |  |  |  |  | 
| 155 | 0 |  |  |  |  | 0 | my $ip_any = $self->re->IP_any; | 
| 156 | 0 |  |  |  |  | 0 | while(<$fh>) { | 
| 157 | 0 |  |  |  |  | 0 | my (@ips) = m/($ip_any)/; | 
| 158 | 0 |  |  |  |  | 0 | for my $ip (@ips) { | 
| 159 | 0 |  | 0 |  |  | 0 | print $self->identify($ip) || $ip, "\n"; | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  | } | 
| 162 |  |  |  |  |  |  | } | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | sub load_entities { | 
| 165 | 4 |  |  | 4 | 0 | 21795 | my ($self, @plugins) = @_; | 
| 166 |  |  |  |  |  |  |  | 
| 167 | 4 | 100 |  |  |  | 22 | my $plugins = ref $plugins[0] eq 'ARRAY'    # accept array or ref | 
| 168 |  |  |  |  |  |  | ? $plugins[0]       # a ref was passed in | 
| 169 |  |  |  |  |  |  | : \@plugins;        # convert array to ref | 
| 170 | 4 |  |  |  |  | 14 | delete $self->{parent_of}; | 
| 171 | 4 |  |  |  |  | 34 | delete $self->{entities}; | 
| 172 | 4 |  |  |  |  | 7 | for my $plugin (@{$plugins}) { | 
|  | 4 |  |  |  |  | 14 |  | 
| 173 |  |  |  |  |  |  | #print "requiring $plugin\n"; | 
| 174 | 37 | 100 |  |  |  | 200 | if (not $plugin =~ m/::/) { | 
| 175 | 2 |  |  |  |  | 7 | $plugin = __PACKAGE__ . "::Plugin::$plugin"; | 
| 176 |  |  |  |  |  |  | } | 
| 177 | 37 |  |  |  |  | 3305 | eval "CORE::require $plugin";   ## no critic # attempt to read in the plugin | 
| 178 | 37 | 50 |  |  |  | 227 | warn $@ if $@; | 
| 179 | 37 |  | 33 |  |  | 190 | my $p = $plugin && $plugin->new; | 
| 180 | 37 | 50 |  |  |  | 116 | next if not $p; | 
| 181 | 37 | 50 |  |  |  | 205 | if (not $p->does('Net::IP::Identifier_Role')) { | 
| 182 | 0 |  |  |  |  | 0 | print "$plugin doesn't satisfy the Net::IP::Identifier_Role - skipping\n"; | 
| 183 | 0 |  |  |  |  | 0 | next; | 
| 184 |  |  |  |  |  |  | } | 
| 185 | 37 |  |  |  |  | 872 | push @{$self->{entities}}, $p; | 
|  | 37 |  |  |  |  | 479 |  | 
| 186 | 37 |  |  |  |  | 159 | for my $child ($p->children) { | 
| 187 | 2 |  |  |  |  | 14 | $self->{parent_of}{$child} = $p; | 
| 188 |  |  |  |  |  |  | } | 
| 189 |  |  |  |  |  |  | } | 
| 190 | 4 | 50 | 33 |  |  | 36 | if (     @$plugins and | 
|  |  |  | 66 |  |  |  |  | 
| 191 |  |  |  |  |  |  | (not   $self->{entities} or | 
| 192 |  |  |  |  |  |  | not @{$self->{entities}})) { | 
| 193 | 0 |  |  |  |  | 0 | croak "No plugins installed\n"; | 
| 194 |  |  |  |  |  |  | } | 
| 195 | 4 |  |  |  |  | 11024 | delete $self->{ip_tree}; | 
| 196 |  |  |  |  |  |  | } | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | sub entities { | 
| 199 | 17 |  |  | 17 | 1 | 51 | my ($self, @plugins) = @_; | 
| 200 |  |  |  |  |  |  |  | 
| 201 | 17 | 100 |  |  |  | 77 | if (@_ > 1) { | 
| 202 | 2 |  |  |  |  | 6 | undef $imports;         # override imports with @plugins | 
| 203 |  |  |  |  |  |  | #print "load args: ", join(' ', @plugins), "\n"; | 
| 204 | 2 |  |  |  |  | 12 | $self->load_entities(@plugins); | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  |  | 
| 207 | 17 | 100 | 66 |  |  | 133 | if (not   $self->{entities} or | 
|  | 15 |  |  |  |  | 70 |  | 
| 208 |  |  |  |  |  |  | not @{$self->{entities}}) { | 
| 209 |  |  |  |  |  |  | # if no plugins yet loaded, check import list | 
| 210 |  |  |  |  |  |  | # no import list? load everything we can find | 
| 211 | 2 | 100 |  |  |  | 8 | if ($imports) { | 
| 212 |  |  |  |  |  |  | #print "load imports ", join(' ', @{$imports}), "\n"; | 
| 213 | 1 |  |  |  |  | 5 | $self->load_entities($imports); | 
| 214 | 1 |  |  |  |  | 2 | undef $imports;     # only the first time | 
| 215 |  |  |  |  |  |  | } | 
| 216 |  |  |  |  |  |  | else { | 
| 217 |  |  |  |  |  |  | #print "load imports ", join(' ', $self->plugins), "\n"; | 
| 218 | 1 |  |  |  |  | 10 | $self->load_entities([ $self->plugins ]); | 
| 219 |  |  |  |  |  |  | } | 
| 220 |  |  |  |  |  |  |  | 
| 221 | 2 | 50 | 33 |  |  | 30 | if (not   $self->{entities} or | 
|  | 2 |  |  |  |  | 8 |  | 
| 222 |  |  |  |  |  |  | not @{$self->{entities}}) { | 
| 223 | 0 |  |  |  |  | 0 | croak "No entity Plugins found\n"; | 
| 224 |  |  |  |  |  |  | } | 
| 225 |  |  |  |  |  |  | } | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | return wantarray | 
| 228 | 17 | 100 |  |  |  | 60 | ? @{$self->{entities}} | 
|  | 15 |  |  |  |  | 91 |  | 
| 229 |  |  |  |  |  |  | : $self->{entities}; | 
| 230 |  |  |  |  |  |  | } | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | sub ip_tree { | 
| 233 | 58 |  |  | 58 | 0 | 343 | my ($self, $version) = @_; | 
| 234 |  |  |  |  |  |  |  | 
| 235 | 58 | 50 |  |  |  | 179 | croak "ip_tree(\$version) error: no version\n" if not $version; | 
| 236 |  |  |  |  |  |  |  | 
| 237 | 58 | 100 |  |  |  | 200 | if (not $self->{ip_tree}) { | 
| 238 | 15 |  |  |  |  | 500 | my $root_v6 = Net::IP::Identifier::Binode->new; | 
| 239 |  |  |  |  |  |  | # Place the IPv4 block in the IPv6 tree (IPv4 mapped IPv6) | 
| 240 | 15 |  |  |  |  | 5561 | my $root_v4 = $root_v6->construct(Net::IP::Identifier::Net->new('::ffff:0:0/96')->masked_ip); | 
| 241 |  |  |  |  |  |  |  | 
| 242 | 15 |  |  |  |  | 317 | for my $entity ($self->entities) { | 
| 243 | 66 |  |  |  |  | 2823 | for my $ip ($entity->ips) { | 
| 244 | 1264 |  |  |  |  | 22490 | my @ips = ($ip); | 
| 245 | 1264 | 100 |  |  |  | 6310 | if (not defined $ip->prefixlen) { | 
| 246 | 141 |  |  |  |  | 1627 | @ips = $ip->range_to_cidrs; | 
| 247 |  |  |  |  |  |  | } | 
| 248 | 1264 |  |  |  |  | 9252 | for my $ip (@ips) { | 
| 249 | 1501 | 100 |  |  |  | 18679 | my $root = ($ip->version == 6) ? $root_v6 : $root_v4; | 
| 250 | 1501 |  |  |  |  | 16172 | $root->construct($ip->masked_ip)->payload( | 
| 251 |  |  |  |  |  |  | Local::Payload->new( | 
| 252 |  |  |  |  |  |  | entity => $entity, | 
| 253 |  |  |  |  |  |  | ip => $ip, | 
| 254 |  |  |  |  |  |  | ), | 
| 255 |  |  |  |  |  |  | ); | 
| 256 |  |  |  |  |  |  | } | 
| 257 |  |  |  |  |  |  | } | 
| 258 |  |  |  |  |  |  | } | 
| 259 | 15 |  |  |  |  | 374 | $self->{ip_tree}{6} = $root_v6; | 
| 260 | 15 |  |  |  |  | 64 | $self->{ip_tree}{4} = $root_v4; | 
| 261 |  |  |  |  |  |  | } | 
| 262 | 58 |  |  |  |  | 443 | return $self->{ip_tree}{$version}; | 
| 263 |  |  |  |  |  |  | } | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | sub identify { | 
| 266 | 47 |  |  | 47 | 1 | 18591 | my ($self, $ip) = @_; | 
| 267 |  |  |  |  |  |  |  | 
| 268 | 47 |  |  |  |  | 332 | $ip = Net::IP::Identifier::Net->new($ip); | 
| 269 | 47 |  |  |  |  | 119 | my @ips = ($ip); | 
| 270 | 47 | 50 |  |  |  | 150 | if (not defined $ip->prefixlen) { | 
| 271 | 0 |  |  |  |  | 0 | @ips = $ip->range_to_cidrs; | 
| 272 |  |  |  |  |  |  | } | 
| 273 |  |  |  |  |  |  |  | 
| 274 | 47 |  |  |  |  | 313 | my @return; | 
| 275 | 47 |  |  |  |  | 114 | for my $ip (@ips) { | 
| 276 |  |  |  |  |  |  | $self->ip_tree($ip->version)->follow($ip->masked_ip, sub { | 
| 277 | 2135 | 100 |  | 2135 |  | 5993 | push @return, $_[0] if ($_[0]->payload); | 
| 278 | 2135 |  |  |  |  | 4931 | return 0;  # always continue | 
| 279 |  |  |  |  |  |  | }, | 
| 280 | 47 |  |  |  |  | 134 | ); | 
| 281 |  |  |  |  |  |  | } | 
| 282 | 47 | 100 |  |  |  | 202 | if (not @return) { | 
| 283 | 21 |  |  |  |  | 265 | return; # not found. | 
| 284 |  |  |  |  |  |  | } | 
| 285 |  |  |  |  |  |  |  | 
| 286 | 26 | 50 |  |  |  | 134 | if (not $self->parents) { | 
| 287 | 26 |  |  |  |  | 74 | @return = ($return[-1]);    # just the last child | 
| 288 |  |  |  |  |  |  | } | 
| 289 |  |  |  |  |  |  |  | 
| 290 | 26 |  |  |  |  | 60 | @return = map { $_->payload } @return;   # remove the Binode layer | 
|  | 26 |  |  |  |  | 110 |  | 
| 291 |  |  |  |  |  |  |  | 
| 292 | 26 | 100 |  |  |  | 83 | if (wantarray) { | 
| 293 | 1 |  |  |  |  | 21 | return $self->cidr | 
| 294 | 1 | 50 |  |  |  | 5 | ? map { $_->entity, $_->ip } @return | 
| 295 |  |  |  |  |  |  | : @return; | 
| 296 |  |  |  |  |  |  | } | 
| 297 |  |  |  |  |  |  |  | 
| 298 | 25 | 100 |  |  |  | 165 | if ($self->cidr) { | 
| 299 | 9 |  |  |  |  | 24 | my @e = map { $self->join($_->entity, $_->ip) } @return; | 
|  | 9 |  |  |  |  | 254 |  | 
| 300 | 9 |  |  |  |  | 628 | return join ' => ', @e; | 
| 301 |  |  |  |  |  |  | } | 
| 302 | 16 |  |  |  |  | 437 | my $r = join (' => ', map { | 
| 303 | 16 |  |  |  |  | 37 | $_->entity->name | 
| 304 |  |  |  |  |  |  | } @return); | 
| 305 | 16 |  |  |  |  | 919 | return $r; | 
| 306 |  |  |  |  |  |  | } | 
| 307 |  |  |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | sub join { | 
| 309 | 35 |  |  | 35 | 1 | 4232 | my ($self, @parts) = @_; | 
| 310 |  |  |  |  |  |  |  | 
| 311 | 35 |  |  |  |  | 106 | my $joiners = $self->joiners; | 
| 312 | 35 |  |  |  |  | 72 | my $joiner = $joiners->[0];   # assume IPv4 | 
| 313 | 35 | 100 |  |  |  | 65 | if (grep { $_->can('version') and $_->version eq '6' } @parts) { | 
|  | 70 | 100 |  |  |  | 603 |  | 
| 314 | 3 |  |  |  |  | 38 | $joiner = $joiners->[1];  # use the IPv6 string | 
| 315 |  |  |  |  |  |  | } | 
| 316 | 35 | 50 |  |  |  | 349 | $joiner = ':' if (not defined $joiner);    # if all else fails... | 
| 317 | 35 |  |  |  |  | 188 | return join $joiner, @parts; | 
| 318 |  |  |  |  |  |  | } | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | sub tree_overlaps { | 
| 321 | 11 |  |  | 11 | 1 | 75 | my ($self) = @_; | 
| 322 |  |  |  |  |  |  |  | 
| 323 | 11 |  |  |  |  | 17 | my @overlaps;   # collect overlaps here.  each overlap is an array | 
| 324 |  |  |  |  |  |  | # starting with the parent, followed by children. | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | $self->ip_tree(6)->traverse_width_first( | 
| 327 |  |  |  |  |  |  | sub { | 
| 328 | 1179 |  |  | 1179 |  | 1213 | my ($node, $level) = @_; | 
| 329 |  |  |  |  |  |  |  | 
| 330 | 1179 |  |  |  |  | 951 | my @overlap;    # a single overlap array, parent then children | 
| 331 | 1179 | 100 | 66 |  |  | 3150 | if ($node->payload and | 
|  |  |  | 66 |  |  |  |  | 
| 332 |  |  |  |  |  |  | ($node->zero or $node->one)) { | 
| 333 |  |  |  |  |  |  | $node->traverse_width_first( | 
| 334 |  |  |  |  |  |  | sub { | 
| 335 | 138 | 100 |  |  |  | 346 | if ($_[0]->payload) { | 
| 336 | 26 |  |  |  |  | 45 | push @overlap, $_[0]; | 
| 337 |  |  |  |  |  |  | } | 
| 338 | 138 |  |  |  |  | 316 | return 0;   # always continue | 
| 339 |  |  |  |  |  |  | } | 
| 340 | 10 |  |  |  |  | 202 | ); | 
| 341 |  |  |  |  |  |  | } | 
| 342 | 1179 | 100 |  |  |  | 2184 | push @overlaps, \@overlap if (@overlap > 1); | 
| 343 | 1179 |  |  |  |  | 3716 | return @overlap > 1;    # stop if we found overlap | 
| 344 |  |  |  |  |  |  | }, | 
| 345 | 11 |  |  |  |  | 47 | ); | 
| 346 |  |  |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | return wantarray | 
| 348 |  |  |  |  |  |  | ? @overlaps | 
| 349 | 11 | 50 |  |  |  | 268 | : \@overlaps; | 
| 350 |  |  |  |  |  |  | } | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | 1; | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | __END__ |