File Coverage

blib/lib/Net/Frame/Device.pm
Criterion Covered Total %
statement 18 20 90.0
branch n/a
condition 1 2 50.0
subroutine 7 7 100.0
pod n/a
total 26 29 89.6


line stmt bran cond sub pod time code
1             #
2             # $Id: Device.pm 357 2012-12-02 16:09:08Z gomor $
3             #
4             package Net::Frame::Device;
5 3     3   31217 use strict;
  3         8  
  3         151  
6 3     3   22 use warnings;
  3         6  
  3         175  
7              
8             our $VERSION = '1.10';
9              
10 3     3   17 use base qw(Class::Gomor::Array);
  3         21  
  3         4338  
11             our @AS = qw(
12             dev
13             mac
14             ip
15             ip6
16             subnet
17             subnet6
18             gatewayIp
19             gatewayIp6
20             gatewayMac
21             gatewayMac6
22             target
23             target6
24             _dnet
25             );
26             __PACKAGE__->cgBuildIndices;
27             __PACKAGE__->cgBuildAccessorsScalar(\@AS);
28              
29             BEGIN {
30 3     3   96206 my $osname = {
31             cygwin => [ \&_getDevWin32, ],
32             MSWin32 => [ \&_getDevWin32, ],
33             };
34              
35 3   50     106 *_getDev = $osname->{$^O}->[0] || \&_getDevOther;
36             }
37              
38 3     3   35 no strict 'vars';
  3         5  
  3         82  
39              
40 3     3   16 use Data::Dumper;
  3         6  
  3         127  
41 3     3   9101 use Net::Libdnet6;
  0            
  0            
42             use Net::IPv4Addr;
43             use Net::IPv6Addr;
44             use Net::Pcap;
45             use Net::Write::Layer2;
46             use Net::Frame::Dump qw(:consts);
47             use Net::Frame::Dump::Online2;
48             use Net::Frame::Layer qw(:subs);
49             use Net::Frame::Layer::ETH qw(:consts);
50             use Net::Frame::Layer::ARP qw(:consts);
51             use Net::Frame::Layer::IPv6 qw(:consts);
52             use Net::Frame::Layer::ICMPv6 qw(:consts);
53             use Net::Frame::Layer::ICMPv6::NeighborSolicitation;
54             use Net::Frame::Simple;
55              
56             sub new {
57             my $self = shift->SUPER::new(@_);
58              
59             $self->[$__target] && return $self->updateFromTarget;
60             $self->[$__target6] && return $self->updateFromTarget6;
61             $self->[$__dev] && return $self->updateFromDev;
62              
63             return $self->updateFromDefault or return;
64              
65             return $self;
66             }
67              
68             sub _update {
69             my $self = shift;
70             my ($dnet6) = @_;
71              
72             $self->[$__dev] = $self->_getDev;
73             $self->[$__mac] = $self->_getMac;
74             $self->[$__ip] = $self->_getIp;
75             $self->[$__subnet] = $self->_getSubnet;
76             $self->[$__gatewayIp] = $self->_getGatewayIp;
77             $self->[$__gatewayMac] = $self->_getGatewayMac;
78              
79             if ($dnet6) {
80             $self->[$___dnet] = $dnet6;
81             }
82              
83             $self->[$__ip6] = $self->_getIp6;
84             $self->[$__subnet6] = $self->_getSubnet6;
85             $self->[$__gatewayIp6] = $self->_getGatewayIp6;
86              
87             $self->[$___dnet] = undef;
88              
89             return $self;
90             }
91              
92             # By default, we take outgoing device to Internet
93             sub updateFromDefault {
94             my $self = shift;
95              
96             my $dnet = intf_get_dst('1.1.1.1');
97             if (! $dnet || keys %$dnet == 0) {
98             die("Net::Frame::Device: updateFromDefault: unable to get dnet\n");
99             }
100             $self->[$___dnet] = $dnet;
101              
102             my $dnet6 = intf_get6($dnet->{name});
103             if (! $dnet6 || keys %$dnet6 == 0) {
104             return $self;
105             }
106              
107             return $self->_update($dnet6);
108             }
109              
110             sub updateFromDev {
111             my $self = shift;
112             my ($dev) = @_;
113              
114             if (defined($dev)) {
115             $self->[$__dev] = $dev;
116             }
117             else {
118             $dev = $self->[$__dev];
119             }
120              
121             my $dnet = intf_get($dev);
122             if (! $dnet || keys %$dnet == 0) {
123             die("Net::Frame::Device: updateFromDev: unable to get dnet\n");
124             }
125             $self->[$___dnet] = $dnet;
126              
127             my $dnet6 = intf_get6($dev);
128             if (! $dnet6 || keys %$dnet6 == 0) {
129             return $self;
130             }
131              
132             return $self->_update($dnet6);
133             }
134              
135             sub updateFromTarget {
136             my $self = shift;
137             my ($target) = @_;
138              
139             if (defined($target)) {
140             $self->[$__target] = $target;
141             }
142             else {
143             $target = $self->[$__target];
144             }
145              
146             my $dnet = intf_get_dst($target);
147             if (! $dnet || keys %$dnet == 0) {
148             die("Net::Frame::Device: updateFromTarget: unable to get dnet\n");
149             }
150             $self->[$___dnet] = $dnet;
151              
152             my $dnet6 = intf_get6($dnet->{name});
153             if (! $dnet6 || keys %$dnet6 == 0) {
154             return $self;
155             }
156              
157             return $self->_update($dnet6);
158             }
159              
160             sub updateFromTarget6 {
161             my $self = shift;
162             my ($target6) = @_;
163             $self->[$__target6] = $target6 if $target6;
164             my @dnetList = intf_get_dst6($self->[$__target6]);
165             if (@dnetList > 1) {
166             if (! $self->[$__dev]) {
167             die("[-] ".__PACKAGE__.": Multiple possible network interface for ".
168             "target6, choose `dev' manually\n");
169             }
170             $self->[$___dnet] = intf_get6($self->[$__dev])
171             or die("Net::Frame::Device: updateFromTarget6: unable to get dnet\n");
172             }
173             elsif (@dnetList == 1) {
174             $self->[$___dnet] = $dnetList[0];
175             }
176             else {
177             die("Net::Frame::Device: updateFromTarget6: unable to get dnet\n");
178             }
179             return $self->_update;
180             }
181              
182             # Thanx to Maddingue
183             sub _toDotQuad {
184             my ($i) = @_;
185             ($i >> 24 & 255).'.'.($i >> 16 & 255).'.'.($i >> 8 & 255).'.'.($i & 255);
186             }
187              
188             sub _getDevWin32 {
189             my $self = shift;
190              
191             die("[-] ".__PACKAGE__.": unable to find a suitable device\n")
192             unless $self->[$___dnet]->{name};
193              
194             # Get dnet interface name and its subnet
195             my $dnet = $self->[$___dnet]->{name};
196             my $subnet = addr_net($self->[$___dnet]->{addr});
197             die("[-] ".__PACKAGE__.": Net::Libdnet::addr_net() error\n")
198             unless $subnet;
199              
200             my %dev;
201             my $err;
202             Net::Pcap::findalldevs(\%dev, \$err);
203             die("[-] ".__PACKAGE__.": Net::Pcap::findalldevs() error: $err\n")
204             if $err;
205              
206             # Search for corresponding WinPcap interface, via subnet value.
207             # I can't use IP address or MAC address, they are not available
208             # through Net::Pcap (as of version 0.15_01).
209             for my $d (keys %dev) {
210             my $net;
211             my $mask;
212             if (Net::Pcap::lookupnet($d, \$net, \$mask, \$err) < 0) {
213             die("[-] ".__PACKAGE__.": Net::Pcap::lookupnet(): $d: $err\n")
214             }
215             $net = _toDotQuad($net);
216             if ($net eq $subnet) {
217             return $d;
218             }
219             }
220             undef;
221             }
222              
223             sub _getDevOther { shift->[$___dnet]->{name} || undef }
224              
225             sub _getGatewayIp { route_get (shift()->[$__target] || '1.1.1.1') || undef }
226             sub _getGatewayIp6 { route_get6(shift()->[$__target6] || '2001::1') || undef }
227              
228             sub _getMacFromCache { shift; arp_get(shift()) }
229              
230             sub _getGatewayMac {
231             my $self = shift;
232             $self->[$__gatewayIp] && $self->_getMacFromCache($self->[$__gatewayIp])
233             || undef;
234             }
235              
236             sub _getSubnet {
237             my $addr = shift->[$___dnet]->{addr};
238             return unless $addr;
239             if ($addr !~ /\//) {
240             return; # No netmask associated here
241             }
242             my $subnet = addr_net($addr) or return;
243             (my $mask = $addr) =~ s/^.*(\/\d+)$/$1/;
244             $subnet.$mask;
245             }
246              
247             sub _getSubnet6 {
248             my $addr = shift->[$___dnet]->{addr6};
249             return unless $addr;
250             if ($addr !~ /\//) {
251             return; # No netmask associated here
252             }
253             my $subnet = addr_net6($addr) or return;
254             (my $mask = $addr) =~ s/^.*(\/\d+)$/$1/;
255             $subnet.$mask;
256             }
257              
258             sub _getMac { shift->[$___dnet]->{link_addr} || undef }
259              
260             sub _getIp {
261             my $ip = shift->[$___dnet]->{addr} || return undef;
262             $ip =~ s/\/\d+$//;
263             $ip;
264             }
265              
266             sub _getIp6 {
267             my $ip = shift->[$___dnet]->{addr6} || return undef;
268             $ip =~ s/\/\d+$//;
269             $ip;
270             }
271              
272             sub _lookupMac {
273             my $self = shift;
274             my ($ip, $retry, $timeout) = @_;
275              
276             my $oWrite = Net::Write::Layer2->new(dev => $self->[$__dev]);
277             my $oDump = Net::Frame::Dump::Online2->new(
278             dev => $self->[$__dev],
279             filter => 'arp',
280             timeoutOnNext => $timeout,
281             );
282              
283             $oDump->start;
284             if ($oDump->firstLayer ne 'ETH') {
285             $oDump->stop;
286             die("[-] ".__PACKAGE__.": lookupMac: can't do that on non-ethernet ".
287             "link layers\n");
288             }
289              
290             $oWrite->open;
291              
292             my $eth = Net::Frame::Layer::ETH->new(
293             src => $self->[$__mac],
294             dst => NF_ETH_ADDR_BROADCAST,
295             type => NF_ETH_TYPE_ARP,
296             );
297             my $arp = Net::Frame::Layer::ARP->new(
298             src => $self->[$__mac],
299             srcIp => $self->[$__ip],
300             dstIp => $ip,
301             );
302             $eth->pack;
303             $arp->pack;
304              
305             # We retry three times
306             my $mac;
307             for (1..$retry) {
308             $oWrite->send($eth->raw.$arp->raw);
309             until ($oDump->timeout) {
310             if (my $h = $oDump->next) {
311             if ($h->{firstLayer} eq 'ETH') {
312             my $raw = substr($h->{raw}, $eth->getLength);
313             my $rArp = Net::Frame::Layer::ARP->new(raw => $raw);
314             $rArp->unpack;
315             next unless $rArp->srcIp eq $ip;
316             $mac = $rArp->src;
317             last;
318             }
319             }
320             }
321             last if $mac;
322             $oDump->timeoutReset;
323             }
324              
325             $oWrite->close;
326             $oDump->stop;
327              
328             return $mac;
329             }
330              
331             sub lookupMac {
332             my $self = shift;
333             my ($ip, $retry, $timeout) = @_;
334              
335             $retry ||= 1;
336             $timeout ||= 1;
337              
338             # First, lookup the ARP cache table
339             my $mac = $self->_getMacFromCache($ip);
340             return $mac if $mac;
341              
342             # Then, is the target on same subnet, or not ?
343             if (Net::IPv4Addr::ipv4_in_network($self->[$__subnet], $ip)) {
344             return $self->_lookupMac($ip, $retry, $timeout);
345             }
346             # Get gateway MAC
347             else {
348             # If already retrieved
349             return $self->[$__gatewayMac] if $self->[$__gatewayMac];
350              
351             # Else, lookup it, and store it
352             my $gatewayMac = $self->_lookupMac(
353             $self->[$__gatewayIp], $retry, $timeout,
354             );
355             $self->[$__gatewayMac] = $gatewayMac;
356             return $gatewayMac;
357             }
358              
359             return;
360             }
361              
362             sub _lookupMac6 {
363             my $self = shift;
364             my ($ip6, $srcIp6, $retry, $timeout) = @_;
365              
366             my $oWrite = Net::Write::Layer2->new(dev => $self->[$__dev]);
367             my $oDump = Net::Frame::Dump::Online2->new(
368             dev => $self->[$__dev],
369             filter => 'icmp6',
370             timeoutOnNext => $timeout,
371             );
372              
373             $oDump->start;
374             if ($oDump->firstLayer ne 'ETH') {
375             $oDump->stop;
376             die("[-] ".__PACKAGE__.": lookupMac6: can't do that on non-ethernet ".
377             "link layers\n");
378             }
379              
380             $oWrite->open;
381              
382             my $srcMac = $self->[$__mac];
383              
384             # XXX: risky
385             my $target6 = Net::IPv6Addr->new($ip6)->to_string_preferred;
386             my @dst = split(':', $target6);
387             my $str = $dst[-2];
388             $str =~ s/^.*(..)$/$1/;
389             $target6 = 'ff02::1:ff'.$str.':'.$dst[-1];
390              
391             my $eth = Net::Frame::Layer::ETH->new(
392             src => $srcMac,
393             dst => NF_ETH_ADDR_BROADCAST,
394             type => NF_ETH_TYPE_IPv6,
395             );
396             my $ip = Net::Frame::Layer::IPv6->new(
397             src => $srcIp6,
398             dst => $target6,
399             nextHeader => NF_IPv6_PROTOCOL_ICMPv6,
400             );
401             my $icmp = Net::Frame::Layer::ICMPv6->new(
402             type => NF_ICMPv6_TYPE_NEIGHBORSOLICITATION,
403             );
404             my $icmpType = Net::Frame::Layer::ICMPv6::NeighborSolicitation->new(
405             targetAddress => $ip6,
406             options => [
407             Net::Frame::Layer::ICMPv6::Option->new(
408             type => NF_ICMPv6_OPTION_SOURCELINKLAYERADDRESS,
409             length => 1,
410             value => pack('H2H2H2H2H2H2', split(':', $srcMac)),
411             ),
412             ],
413             );
414              
415             my $oSimple = Net::Frame::Simple->new(
416             layers => [ $eth, $ip, $icmp, $icmpType ],
417             );
418              
419             # We retry three times
420             my $mac;
421             FIRST:
422             for (1..$retry) {
423             $oWrite->send($oSimple->raw);
424             until ($oDump->timeout) {
425             if (my $oReply = $oSimple->recv($oDump)) {
426             for ($oReply->ref->{'ICMPv6::NeighborAdvertisement'}->options) {
427             if ($_->type eq NF_ICMPv6_OPTION_TARGETLINKLAYERADDRESS) {
428             $mac = convertMac(unpack('H*', $_->value));
429             if ($mac !~
430             /^[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}$/i) {
431             die("[-] ".__PACKAGE__.": lookupMac6: ".
432             "MAC address format error: [$mac]\n");
433             }
434             last FIRST;
435             }
436             }
437             }
438             }
439             $oDump->timeoutReset;
440             }
441              
442             $oWrite->close;
443             $oDump->stop;
444              
445             return $mac;
446             }
447              
448             sub _searchSrcIp6 {
449             my $self = shift;
450             my ($ip6) = @_;
451             my @dnet6 = intf_get_dst6($ip6) or return undef;
452             my $dev = $self->[$__dev];
453             my $dnet6;
454             for (@dnet6) {
455             if ($_->{name} eq $dev) {
456             $dnet6 = $_;
457             last;
458             }
459             }
460             my ($srcIp6) = split('/', $dnet6->{addr6});
461             $srcIp6;
462             }
463              
464             sub lookupMac6 {
465             my $self = shift;
466             my ($ip6, $retry, $timeout) = @_;
467              
468             $retry ||= 1;
469             $timeout ||= 1;
470              
471             # XXX: No ARP6 cache support for now
472              
473             # If target IPv6 begins with fe80, we are on the same subnet,
474             # we lookup its MAC address
475             if ($ip6 =~ /^fe80/i) {
476             # We must change source IPv6 address to the one of same subnet
477             my $srcIp6 = $self->_searchSrcIp6($ip6);
478             return $self->_lookupMac6($ip6, $srcIp6, $retry, $timeout);
479             }
480             # Otherwise, we lookup the gateway MAC address, and store it
481             else {
482             # If already retrieved
483             return $self->[$__gatewayMac6] if $self->[$__gatewayMac6];
484              
485             # No IPv6 gateway?
486             if (! $self->[$__gatewayIp6]) {
487             print("[-] lookupMac6: no IPv6 gateway, no default route?\n");
488             return;
489             }
490              
491             # Else, lookup it, and store it
492             # We must change source IPv6 address to the one of same subnet
493             my $srcIp6 = $self->_searchSrcIp6($self->[$__gatewayIp6]);
494             my $gatewayMac6 = $self->_lookupMac6(
495             $self->[$__gatewayIp6], $srcIp6, $retry, $timeout,
496             );
497             $self->[$__gatewayMac6] = $gatewayMac6;
498             return $gatewayMac6;
499             }
500              
501             return;
502             }
503              
504             sub debugDeviceList {
505             my %dev;
506             my $err;
507             Net::Pcap::findalldevs(\%dev, \$err);
508             print STDERR "findalldevs: error: $err\n" if $err;
509              
510             # Net::Pcap stuff
511             for my $d (keys %dev) {
512             my ($net, $mask);
513             if (Net::Pcap::lookupnet($d, \$net, \$mask, \$err) < 0) {
514             print STDERR "lookupnet: error: $d: $err\n";
515             $err = undef; next;
516             }
517             print STDERR "[$d] => subnet: "._toDotQuad($net)."\n";
518             }
519              
520             # Net::Libdnet stuff
521             for my $i (0..5) {
522             my $eth = 'eth'.$i;
523             my $dnet = intf_get($eth);
524             last unless keys %$dnet > 0;
525             $dnet->{subnet} = addr_net($dnet->{addr})
526             if $dnet->{addr};
527             print STDERR Dumper($dnet)."\n";
528             }
529             }
530              
531             1;
532              
533             __END__