File Coverage

blib/lib/Net/Libdnet6.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             #
2             # $Id: Libdnet6.pm 2005 2015-01-28 18:59:42Z gomor $
3             #
4             package Net::Libdnet6;
5 1     1   4094 use strict;
  1         1  
  1         34  
6 1     1   3 use warnings;
  1         1  
  1         43  
7              
8             our $VERSION = '0.27';
9              
10 1     1   5 use base qw(Exporter);
  1         9  
  1         147  
11              
12             # We also export Net::Libdnet subs (those without 6 at the end)
13             our @EXPORT = qw(
14             addr_cmp6
15             addr_bcast6
16             addr_net6
17             arp_add6
18             arp_delete6
19             arp_get6
20             intf_get6
21             intf_get_src6
22             intf_get_dst6
23             intf_set6
24             route_add6
25             route_delete6
26             route_get6
27              
28             addr_cmp
29             addr_bcast
30             addr_net
31             arp_add
32             arp_delete
33             arp_get
34             intf_get
35             intf_get_src
36             intf_get_dst
37             intf_set
38             route_add
39             route_delete
40             route_get
41             );
42              
43 1     1   868 use Net::Libdnet;
  0            
  0            
44             use Net::IPv6Addr;
45              
46             my $_pathIfconfig;
47             my $_pathNetstat;
48              
49             BEGIN {
50             sub _getPathIfconfig {
51             my @pathList = qw(
52             /sbin/ifconfig /usr/sbin/ifconfig /bin/ifconfig /usr/bin/ifconfig
53             );
54             for (@pathList) {
55             (-f $_) && ($_pathIfconfig = $_) && return 1;
56             }
57             return;
58             }
59              
60             sub _getPathNetstat {
61             my @pathList = qw(
62             /bin/netstat /usr/bin/netstat /sbin/netstat /usr/sbin/netstat
63             );
64             for (@pathList) {
65             (-f $_) && ($_pathNetstat = $_) && return 1;
66             }
67             return;
68             }
69              
70             my $osname = {
71             linux => [ \&_get_routes_linux, ],
72             freebsd => [ \&_get_routes_bsd, ],
73             openbsd => [ \&_get_routes_bsd, ],
74             netbsd => [ \&_get_routes_bsd, ],
75             darwin => [ \&_get_routes_bsd, ],
76             };
77              
78             *_get_routes = $osname->{$^O}->[0] || \&_get_routes_other;
79              
80             # XXX: No support under Windows for now
81             unless ($^O =~ /mswin32|cygwin/i) {
82             _getPathIfconfig()
83             or die("[-] ".__PACKAGE__.": Unable to find ifconfig command\n");
84             _getPathNetstat()
85             or die("[-] ".__PACKAGE__.": Unable to find netstat command\n");
86             }
87             }
88              
89             sub arp_add6 { die("[-] ".__PACKAGE__.": arp_add6: Not supported\n") }
90             sub arp_delete6 { die("[-] ".__PACKAGE__.": arp_delete6: Not supported\n") }
91             sub arp_get6 { die("[-] ".__PACKAGE__.": arp_get6: Not supported\n") }
92              
93             sub intf_set6 { die("[-] ".__PACKAGE__.": intf_set6: Not supported\n") }
94             sub intf_get_src6 { die("[-] ".__PACKAGE__.": intf_get_src6: Not supported\n") }
95              
96             sub route_add6 { die("[-] ".__PACKAGE__.": route_add6: Not supported\n") }
97             sub route_delete6 { die("[-] ".__PACKAGE__.": route_delete6: Not supported\n") }
98              
99             sub addr_cmp6 { die("[-] ".__PACKAGE__.": addr_cmp6: Not supported\n") }
100             sub addr_bcast6 { die("[-] ".__PACKAGE__.": addr_bcast6: Not supported\n") }
101              
102             sub _to_string_preferred { Net::IPv6Addr->new(shift())->to_string_preferred }
103             sub _to_string_compressed { Net::IPv6Addr->new(shift())->to_string_compressed }
104              
105             sub addr_net6 {
106             my $ip6 = shift;
107              
108             confess('Usage: addr_net6("$ipv6Address/$prefixlen")'."\n")
109             if (! $ip6 || $ip6 !~ /\/\d+/);
110              
111             my ($ip, $mask) = split('/', $ip6);
112             $ip = _to_string_preferred($ip);
113             $mask /= 8; # Convert to number of bytes
114             my $subnet;
115             my $count = 0;
116             for (split(':', $ip)) {
117             if ($count < $mask) {
118             $subnet .= $_.':';
119             $count += 2; # Each element takes two bytes
120             }
121             else {
122             $subnet .= '0:';
123             }
124             }
125             $subnet =~ s/:$//;
126             return _to_string_compressed($subnet);
127             }
128              
129             sub _get_ip6 {
130             my $dev = shift;
131             return unless $_pathIfconfig;
132              
133             my $buf = `$_pathIfconfig $dev 2> /dev/null`;
134             return unless $buf;
135              
136             my @ip6 = ();
137             for (split('\n', $buf)) {
138             my $prefixLenFound;
139             my $lastIp6;
140             for (split(/\s+/)) {
141             s/(?:%[a-z0-9]+)$//; # This removes %lnc0 on BSD systems
142              
143             # Some Linux systems do not put the prefix with /number
144             if (/^[0-9a-f:]+$/i && Net::IPv6Addr::is_ipv6($_)) {
145             $lastIp6 = lc($_);
146             }
147             # Some newer Linux systems do it
148             elsif (/^[0-9a-f:]+\/(\d+)$/i && Net::IPv6Addr::is_ipv6($_)) {
149             $lastIp6 = lc($_);
150             }
151              
152             # Gather prefixlen on *BSD systems
153             if (/^\d+$/ && $prefixLenFound) {
154             $lastIp6 .= '/'.$_;
155             --$prefixLenFound;
156             }
157             ++$prefixLenFound if /^prefixlen$/i;
158             }
159             push @ip6, $lastIp6 if $lastIp6;
160             }
161              
162             # We return the first IP as the main address, others as aliases
163             if (@ip6 > 1) {
164             return $ip6[0], [ @ip6[1..$#ip6] ];
165             }
166             elsif (@ip6 == 1) {
167             return $ip6[0];
168             }
169             return;
170             }
171              
172             sub intf_get6 {
173             my $dev = shift;
174              
175             confess('Usage: intf_get6($networkInterface)'."\n")
176             unless $dev;
177              
178             my $dnet = intf_get($dev) or return;
179             my ($ip, $aliases) = _get_ip6($dev);
180             $dnet->{addr6} = $ip if $ip;
181             $dnet->{aliases6} = $aliases if $aliases;
182              
183             return $dnet;
184             }
185              
186             # XXX: not supported yet
187             sub _get_routes_other { return; }
188              
189             sub _get_routes_linux {
190             return unless $_pathNetstat;
191              
192             my $buf = `$_pathNetstat -rnA inet6 2> /dev/null`;
193             return unless $buf;
194              
195             my @ifRoutes = ();
196             my %devIps;
197             for (split('\n', $buf)) {
198             my @elts = split(/\s+/);
199             if ($elts[0]) {
200             if ($elts[0] eq '::/0') { # Default route
201             my $route = {
202             destination => 'default',
203             interface => $elts[-1],
204             };
205             if (Net::IPv6Addr::is_ipv6($elts[1])) {
206             $route->{nextHop} = $elts[1];
207             }
208             push @ifRoutes, $route;
209             }
210             elsif (Net::IPv6Addr::is_ipv6($elts[0])) {
211             my $route = {
212             destination => $elts[0],
213             interface => $elts[-1],
214             };
215             if (Net::IPv6Addr::is_ipv6($elts[1])) {
216             $route->{nextHop} = $elts[1];
217             }
218             push @ifRoutes, $route;
219             }
220             }
221             }
222              
223             if (@ifRoutes > 1) {
224             return \@ifRoutes;
225             }
226              
227             return;
228             }
229              
230             sub _get_routes_bsd {
231             return unless $_pathNetstat;
232              
233             my $buf = `$_pathNetstat -rnf inet6 2> /dev/null`;
234             return unless $buf;
235              
236             my @ifRoutes = ();
237             my %devIps;
238             for (split('\n', $buf)) {
239             my @elts = split(/\s+/);
240              
241             my $destination = $elts[0] || undef;
242             my $gateway = $elts[1] || undef;
243             my $flags = $elts[2] || undef;
244             my $if = $elts[3] || undef;
245              
246             if (defined($destination)) {
247             $destination =~ s/%[a-z]+[0-9]+//;
248             }
249             if (defined($gateway)) {
250             $gateway =~ s/%[a-z]+[0-9]+//;
251             }
252              
253             next if ! defined($destination);
254              
255             # FreeBSD > 9.x has a new scheme for default routes:
256             # it uses the MAC address of default interface
257             if ($gateway
258             && $gateway =~ /^[a-z0-9]{2}:[a-z0-9]{2}:[a-z0-9]{2}:[a-z0-9]{2}:[a-z0-9]{2}:[a-z0-9]{2}$/i) {
259             my $route = {
260             destination => 'default',
261             interface => $if,
262             };
263             if (Net::IPv6Addr::is_ipv6($elts[1])) {
264             $route->{nextHop} = $destination;
265             }
266             push @ifRoutes, $route;
267             }
268             elsif (Net::IPv6Addr::is_ipv6($destination)) {
269             my $route = {
270             destination => $destination,
271             interface => $if,
272             };
273             if (Net::IPv6Addr::is_ipv6($gateway)) {
274             $route->{nextHop} = $gateway;
275             }
276             push @ifRoutes, $route;
277             }
278             elsif ($destination eq 'default') {
279             my $route = {
280             destination => $destination,
281             interface => $if,
282             };
283             if (Net::IPv6Addr::is_ipv6($gateway)) {
284             $route->{nextHop} = $gateway;
285             }
286             push @ifRoutes, $route;
287             }
288             }
289              
290             if (@ifRoutes > 1) {
291             return \@ifRoutes;
292             }
293              
294             return;
295             }
296              
297             sub _is_in_network {
298             my ($src, $net, $mask) = @_;
299             my $net1 = addr_net6($src.'/'.$mask);
300             my $net2 = addr_net6($net.'/'.$mask);
301             return $net1 eq $net2;
302             }
303              
304             sub intf_get_dst6 {
305             my $dst = shift;
306              
307             confess('Usage: intf_get_dst6($targetIpv6Address)'."\n")
308             unless $dst;
309              
310             $dst = _to_string_preferred($dst);
311              
312             my $routes = _get_routes() or return;
313              
314             # Search network device list for target6
315             my @devList = ();
316             for my $r (@$routes) {
317             my ($net, $mask) = split('/', $r->{destination});
318              
319             # If the route is unicast, stop here
320             unless ($mask) {
321             if ($dst eq $r->{destination}) {
322             push @devList, $r->{interface};
323             last;
324             }
325             }
326             else {
327             $net = _to_string_preferred($net);
328             if (_is_in_network($dst, $net, $mask)) {
329             push @devList, $r->{interface};
330             }
331             }
332             }
333              
334             my @devs;
335             if (@devList > 0) {
336             @devs = map { intf_get6($_) } @devList;
337             }
338             else {
339             # Not on same network, should use default gw
340             for my $r (@$routes) {
341             if ($r->{destination} eq 'default') {
342             push @devs, intf_get6($r->{interface});
343             }
344             }
345             }
346              
347             return unless @devs > 0;
348              
349             # Now, search the correct source IP, if multiple found
350             my @finalDevs = ();
351             for (@devs) {
352             # Skip if interface has no IPv6 address
353             next unless exists $_->{addr6};
354              
355             # If it has multiple IPv6 address, choose the good one
356             if (exists $_->{aliases6}) {
357             my @ipList = ( $_->{addr6}, @{$_->{aliases6}} );
358             for my $i (@ipList) {
359             my ($net, $mask) = split('/', $i);
360              
361             if (_is_in_network($dst, $net, $mask)) {
362             my @ipNotMain = grep {!/^$i$/} @ipList;
363             $_->{addr6} = $i;
364             $_->{aliases6} = \@ipNotMain;
365             }
366             }
367             }
368              
369             if ($_->{name} =~ /^lo\d*$/ && $dst !~ /^0:0:0:0:0:0:0:1$/) {
370             next;
371             }
372              
373             push @finalDevs, $_;
374             }
375              
376             wantarray ? @finalDevs : $finalDevs[0];
377             }
378              
379             sub _search_next_hop {
380             my $dev = shift;
381             my ($dst, $hops) = @_;
382              
383             return unless exists $dev->{addr6};
384              
385             my ($net, $mask) = split('/', $dev->{addr6});
386             for my $h (@$hops) {
387             if (! _is_in_network($dst, $net, $mask)) {
388             for my $i ($dev->{addr6}, @{$dev->{aliases6}}) {
389             my ($iNet, $iMask) = split('/', $i);
390             if (_is_in_network($h, $iNet, $iMask)) {
391             return $h;
392             }
393             }
394             }
395             }
396             return;
397             }
398              
399             sub route_get6 {
400             my $dst = shift;
401              
402             confess('Usage: route_get6($targetIpv6Address)'."\n")
403             unless $dst;
404              
405             $dst = _to_string_preferred($dst);
406              
407             my @devs = intf_get_dst6($dst) or return;
408             return unless @devs > 0;
409              
410             my @nextHops = ();
411             my $routes = _get_routes() or return;
412             for my $r (@$routes) {
413             push @nextHops, $r->{nextHop} if $r->{nextHop};
414             }
415              
416             return unless @nextHops > 0;
417              
418             my $nextHop;
419             for my $d (@devs) {
420             $nextHop = _search_next_hop($d, $dst, \@nextHops);
421             }
422              
423             return $nextHop;
424             }
425              
426             1;
427              
428             __END__