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 2003 2012-11-16 15:37:48Z gomor $
3             #
4             package Net::Libdnet6;
5 1     1   6058 use strict;
  1         2  
  1         46  
6 1     1   6 use warnings;
  1         2  
  1         54  
7              
8             our $VERSION = '0.26';
9              
10 1     1   6 use base qw(Exporter);
  1         5  
  1         184  
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   4179 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             if ($elts[0]) {
241             $elts[0] && $elts[0] =~ s/%[a-z]+[0-9]+//;
242             $elts[1] && $elts[1] =~ s/%[a-z]+[0-9]+//;
243             if (Net::IPv6Addr::is_ipv6($elts[0])) {
244             my $route = {
245             destination => $elts[0],
246             interface => $elts[-1],
247             };
248             if (Net::IPv6Addr::is_ipv6($elts[1])) {
249             $route->{nextHop} = $elts[1];
250             }
251             push @ifRoutes, $route;
252             }
253             elsif ($elts[0] eq 'default') {
254             my $route = {
255             destination => $elts[0],
256             interface => $elts[-1],
257             };
258             if (Net::IPv6Addr::is_ipv6($elts[1])) {
259             $route->{nextHop} = $elts[1];
260             }
261             push @ifRoutes, $route;
262             }
263             }
264             }
265              
266             if (@ifRoutes > 1) {
267             return \@ifRoutes;
268             }
269              
270             return;
271             }
272              
273             sub _is_in_network {
274             my ($src, $net, $mask) = @_;
275             my $net1 = addr_net6($src.'/'.$mask);
276             my $net2 = addr_net6($net.'/'.$mask);
277             return $net1 eq $net2;
278             }
279              
280             sub intf_get_dst6 {
281             my $dst = shift;
282              
283             confess('Usage: intf_get_dst6($targetIpv6Address)'."\n")
284             unless $dst;
285              
286             $dst = _to_string_preferred($dst);
287              
288             my $routes = _get_routes() or return;
289              
290             # Search network device list for target6
291             my @devList = ();
292             for my $r (@$routes) {
293             my ($net, $mask) = split('/', $r->{destination});
294              
295             # If the route is unicast, stop here
296             unless ($mask) {
297             if ($dst eq $r->{destination}) {
298             push @devList, $r->{interface};
299             last;
300             }
301             }
302             else {
303             $net = _to_string_preferred($net);
304             if (_is_in_network($dst, $net, $mask)) {
305             push @devList, $r->{interface};
306             }
307             }
308             }
309              
310             my @devs;
311             if (@devList > 0) {
312             @devs = map { intf_get6($_) } @devList;
313             }
314             else {
315             # Not on same network, should use default gw
316             for my $r (@$routes) {
317             if ($r->{destination} eq 'default') {
318             push @devs, intf_get6($r->{interface});
319             }
320             }
321             }
322              
323             return unless @devs > 0;
324              
325             # Now, search the correct source IP, if multiple found
326             my @finalDevs = ();
327             for (@devs) {
328             # Skip if interface has no IPv6 address
329             next unless exists $_->{addr6};
330              
331             # If it has multiple IPv6 address, choose the good one
332             if (exists $_->{aliases6}) {
333             my @ipList = ( $_->{addr6}, @{$_->{aliases6}} );
334             for my $i (@ipList) {
335             my ($net, $mask) = split('/', $i);
336              
337             if (_is_in_network($dst, $net, $mask)) {
338             my @ipNotMain = grep {!/^$i$/} @ipList;
339             $_->{addr6} = $i;
340             $_->{aliases6} = \@ipNotMain;
341             }
342             }
343             }
344              
345             if ($_->{name} =~ /^lo\d*$/ && $dst !~ /^0:0:0:0:0:0:0:1$/) {
346             next;
347             }
348              
349             push @finalDevs, $_;
350             }
351              
352             wantarray ? @finalDevs : $finalDevs[0];
353             }
354              
355             sub _search_next_hop {
356             my $dev = shift;
357             my ($dst, $hops) = @_;
358              
359             return unless exists $dev->{addr6};
360              
361             my ($net, $mask) = split('/', $dev->{addr6});
362             for my $h (@$hops) {
363             if (! _is_in_network($dst, $net, $mask)) {
364             for my $i ($dev->{addr6}, @{$dev->{aliases6}}) {
365             my ($iNet, $iMask) = split('/', $i);
366             if (_is_in_network($h, $iNet, $iMask)) {
367             return $h;
368             }
369             }
370             }
371             }
372             return;
373             }
374              
375             sub route_get6 {
376             my $dst = shift;
377              
378             confess('Usage: route_get6($targetIpv6Address)'."\n")
379             unless $dst;
380              
381             $dst = _to_string_preferred($dst);
382              
383             my @devs = intf_get_dst6($dst) or return;
384             return unless @devs > 0;
385              
386             my @nextHops = ();
387             my $routes = _get_routes() or return;
388             for my $r (@$routes) {
389             push @nextHops, $r->{nextHop} if $r->{nextHop};
390             }
391              
392             return unless @nextHops > 0;
393              
394             my $nextHop;
395             for my $d (@devs) {
396             $nextHop = _search_next_hop($d, $dst, \@nextHops);
397             }
398              
399             return $nextHop;
400             }
401              
402             1;
403              
404             __END__