source: repository/lib/Metabrik/Network/Arp.pm

Last change on this file was 866:f6ad8c136b19, checked in by GomoR <gomor@…>, 4 months ago
  • update: copyright 2017
File size: 9.8 KB
Line 
1#
2# $Id$
3#
4# network::arp Brik
5#
6package Metabrik::Network::Arp;
7use strict;
8use warnings;
9
10use base qw(Metabrik::Network::Frame Metabrik::System::Package);
11
12sub brik_properties {
13   return {
14      revision => '$Revision$',
15      tags => [ qw(unstable cache poison eui64 discover scan eui-64) ],
16      author => 'GomoR <GomoR[at]metabrik.org>',
17      license => 'http://opensource.org/licenses/BSD-3-Clause',
18      attributes => {
19         try => [ qw(try_count) ],
20         rtimeout => [ qw(timeout_seconds) ],
21         count => [ qw(count) ],
22         max_runtime => [ qw(max_runtime) ],
23         device => [ qw(device) ],
24         _pidfile => [ qw(INTERNAL) ],
25      },
26      attributes_default => {
27         try => 2,
28         rtimeout => 2,
29      },
30      commands => {
31         install => [ ], # Inherited
32         cache => [ ],
33         half_poison => [ qw(gateway victim|OPTIONAL device|OPTIONAL) ],
34         full_poison => [ qw(gateway victim|OPTIONAL device|OPTIONAL) ],
35         mac2eui64 => [ qw(mac_address) ],
36         scan => [ qw(subnet|OPTIONAL device[OPTIONAL) ],
37         get_ipv4_neighbors => [ qw(subnet|OPTIONAL device|OPTIONAL) ],
38         get_ipv6_neighbors => [ qw(subnet|OPTIONAL device|OPTIONAL) ],
39         get_mac_neighbors => [ qw(subnet|OPTIONAL device|OPTIONAL) ],
40         stop_poison => [ ],
41      },
42      require_modules => {
43         'Net::Frame::Layer::ARP' => [ ],
44         'Net::Libdnet::Arp' => [ ],
45         'Metabrik::Network::Address' => [ ],
46         'Metabrik::Network::Arp' => [ ],
47         'Metabrik::Network::Read' => [ ],
48         'Metabrik::Network::Write' => [ ],
49         'Metabrik::Shell::Command' => [ ],
50         'Metabrik::System::Process' => [ ],
51      },
52      optional_binaries => {
53         arpspoof => [ ],
54      },
55      need_packages => {
56         ubuntu => [ qw(dsniff libnet-libdnet-perl) ],
57         debian => [ qw(dsniff libnet-libdnet-perl) ],
58         freebsd => [ qw(libdnet) ],
59      },
60   };
61}
62
63sub brik_use_properties {
64   my $self = shift;
65
66   return {
67      attributes_default => {
68         device => $self->global->device,
69      },
70   };
71}
72
73sub _loop {
74   my ($entry, $data) = @_;
75
76   $data->{ip}->{$entry->{arp_pa}} = $entry->{arp_ha};
77   $data->{mac}->{$entry->{arp_ha}} = $entry->{arp_pa};
78
79   return $data;
80}
81
82sub cache {
83   my $self = shift;
84
85   my $dnet = Net::Libdnet::Arp->new;
86
87   my %data = ();
88   $dnet->loop(\&_loop, \%data);
89
90   return \%data;
91}
92
93sub half_poison {
94   my $self = shift;
95   my ($gateway, $victim, $device) = @_;
96
97   if (! $self->brik_has_binary("arpspoof")) {
98      return $self->log->error("half_poison: you have to install dsniff package");
99   }
100
101   $device ||= $self->device;
102   $self->brik_help_run_undef_arg('half_poison', $gateway) or return;
103   $self->brik_help_run_undef_arg('half_poison', $device) or return;
104
105   my $cmd = "arpspoof -i $device -c both";
106   $cmd .= " -t $victim" if defined($victim);  # Or default to all LAN hosts
107   $cmd .= " $gateway";
108
109   my $sc = Metabrik::Shell::Command->new_from_brik_init($self) or return;
110   my $sp = Metabrik::System::Process->new_from_brik_init($self) or return;
111   my $pidfile = $sp->start(sub { $sc->system($cmd) });
112   $self->_pidfile($pidfile);
113
114   $self->log->info("half_poison: daemonized to pidfile[$pidfile]");
115
116   return 1;
117}
118
119sub full_poison {
120   my $self = shift;
121   my ($gateway, $victim, $device) = @_;
122
123   if (! $self->brik_has_binary("arpspoof")) {
124      return $self->log->error("full_poison: you have to install dsniff package");
125   }
126
127   $device ||= $self->device;
128   $self->brik_help_run_undef_arg('full_poison', $gateway) or return;
129   $self->brik_help_run_undef_arg('full_poison', $device) or return;
130
131   my $cmd = "arpspoof -i $device -c both -r";
132   $cmd .= " -t $victim" if defined($victim);  # Or default to all LAN hosts
133   $cmd .= " $gateway";
134
135   my $sc = Metabrik::Shell::Command->new_from_brik_init($self) or return;
136   my $sp = Metabrik::System::Process->new_from_brik_init($self) or return;
137   my $pidfile = $sp->start(sub { $sc->system($cmd) });
138   $self->_pidfile($pidfile);
139
140   $self->log->info("full_poison: daemonized to pidfile[$pidfile]");
141
142   return 1;
143}
144
145# Taken from Net::SinFP3
146sub mac2eui64 {
147   my $self = shift;
148   my ($mac) = @_;
149
150   $self->brik_help_run_undef_arg('mac2eui64', $mac) or return;
151
152   if ($mac !~ /^[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}$/i) {
153      return $self->log->error("mac2eui64: invalid MAC address [$mac]");
154   }
155
156   my @b  = split(':', $mac);
157   my $b0 = hex($b[0]) ^ 2;
158
159   return sprintf("fe80::%x%x:%xff:fe%x:%x%x", $b0, hex($b[1]), hex($b[2]),
160      hex($b[3]), hex($b[4]), hex($b[5]));
161}
162
163sub _get_arp_frame {
164   my $self = shift;
165   my ($dst_ip) = @_;
166
167   my $eth = $self->eth;
168   $eth->type(0x0806);  # ARP
169
170   my $arp = $self->arp($dst_ip);
171   my $frame = $self->frame([ $eth, $arp ]);
172
173   return $frame;
174}
175
176sub scan {
177   my $self = shift;
178   my ($subnet, $device) = @_;
179
180   $self->brik_help_run_must_be_root('scan') or return;
181
182   $device ||= $self->device;
183   $self->brik_help_run_undef_arg('scan', $device) or return;
184
185   my $interface = $self->get_device_info($device) or return;
186
187   $subnet ||= $interface->{subnet4};
188   $self->brik_help_run_undef_arg('scan', $subnet) or return;
189
190   my $arp_cache = $self->cache
191      or return $self->log->error("scan: cache failed");
192
193   my $scan_arp_cache = {};
194   for my $this (keys %{$arp_cache->{mac}}) {
195      my $mac = $this;
196      my $ip = $arp_cache->{mac}{$this};
197      $self->log->verbose("scan: found MAC [$mac] in cache for IPv4 [$ip]");
198      $scan_arp_cache->{$ip} = $mac;
199   }
200
201   my $na = Metabrik::Network::Address->new_from_brik_init($self) or return;
202
203   my $ip_list = $na->ipv4_list($subnet) or return;
204
205   my $reply_cache = {};
206   my @frame_list = ();
207   for my $ip (@$ip_list) {
208      # We scan ARP for everyone but our own IP
209      if (exists($interface->{ipv4}) && $ip eq $interface->{ipv4}) {
210         next;
211      }
212
213      if (exists($scan_arp_cache->{$ip})) {
214         my $mac = $scan_arp_cache->{$ip};
215         $reply_cache->{$ip} = $mac;
216      }
217      else {
218         # If it is not in ARP cache yet
219         push @frame_list, $self->_get_arp_frame($ip);
220      }
221   }
222
223   my $nw = Metabrik::Network::Write->new_from_brik_init($self) or return;
224
225   my $write = $nw->open(2, $interface->{device}) or return;
226
227   my $nr = Metabrik::Network::Read->new_from_brik_init($self) or return;
228   $nr->rtimeout($self->rtimeout);
229   $nr->count($self->count || 0);
230
231   my $filter = 'arp and src net '.$subnet.' and dst host '.$interface->{ipv4};
232   my $read = $nr->open(2, $interface->{device}, $filter) or return;
233
234   # We will send frames 3 times max
235   my $try = $self->try;
236   for my $t (1..$try) {
237      # We send all frames
238      for my $r (@frame_list) {
239         $self->debug && $self->log->debug($r->print);
240         my $dst_ip = $r->ref->{ARP}->dstIp;
241         if (! exists($reply_cache->{$dst_ip})) {
242            $nw->send($r->raw)
243               or $self->log->warning("scan: send failed");
244         }
245      }
246
247      # Then we wait for all replies until a timeout occurs
248      my $h_list = $nr->read_until_timeout;
249      for my $h (@$h_list) {
250         my $r = $self->from_read($h);
251         #$self->log->verbose("scan: read next returned some stuff".$r->print);
252
253         if ($r->ref->{ARP}->opCode != &Net::Frame::Layer::ARP::NF_ARP_OPCODE_REPLY) {
254            next;
255         }
256
257         my $src_ip = $r->ref->{ARP}->srcIp;
258         if (! exists($reply_cache->{$src_ip})) {
259            my $mac = $r->ref->{ARP}->src;
260            $self->log->info("scan: received MAC [$mac] for IPV4 [$src_ip]");
261            $reply_cache->{$src_ip} = $r->ref->{ARP}->src;
262
263            # Put it in ARP cache table for next round
264            $scan_arp_cache->{$src_ip} = $mac;
265         }
266      }
267
268      $nr->reset_timeout;
269   }
270
271   $nw->close;
272   $nr->close;
273
274   my %results = ();
275   for (keys %$reply_cache) {
276      my $mac = $reply_cache->{$_};
277      my $ip4 = $_;
278      my $ip6 = $self->mac2eui64($mac);
279      $self->log->verbose(sprintf("%-16s => %s  [%s]", $ip4, $mac, $ip6));
280      $results{by_ipv4}{$ip4} = { ipv6 => $ip6, mac => $mac, ipv4 => $ip4 };
281      $results{by_mac}{$mac} = { ipv6 => $ip6, mac => $mac, ipv4 => $ip4 };
282      $results{by_ipv6}{$ip6} = { ipv6 => $ip6, mac => $mac, ipv4 => $ip4 };
283   }
284
285   return \%results;
286}
287
288sub get_ipv4_neighbors {
289   my $self = shift;
290   my ($subnet, $device) = @_;
291
292   my $scan = $self->scan($subnet, $device) or return;
293   my $ipv4 = $scan->{by_ipv4};
294   if (! defined($ipv4)) {
295      return $self->log->info("get_ipv4_neighbors: no IPv4 neighbor found");
296   }
297
298   return $ipv4;
299}
300
301sub get_ipv6_neighbors {
302   my $self = shift;
303   my ($subnet, $device) = @_;
304
305   my $scan = $self->scan($subnet, $device) or return;
306   my $ipv6 = $scan->{by_ipv6};
307   if (! defined($ipv6)) {
308      return $self->log->info("get_ipv6_neighbors: no IPv6 neighbor found");
309   }
310
311   return $ipv6;
312}
313
314sub get_mac_neighbors {
315   my $self = shift;
316   my ($subnet, $device) = @_;
317
318   my $scan = $self->scan($subnet, $device) or return;
319   my $mac = $scan->{by_mac};
320   if (! defined($mac)) {
321      return $self->log->info("get_mac_neighbors: no MAC neighbor found");
322   }
323
324   return $mac;
325}
326
327sub stop_poison {
328   my $self = shift;
329
330   my $pidfile = $self->_pidfile;
331   if (defined($pidfile)) {
332      my $sp = Metabrik::System::Process->new_from_brik_init($self) or return;
333      $sp->force_kill(1);
334      $sp->kill_from_pidfile($pidfile);
335      $self->log->verbose("stop_poison: killing arpspoof process");
336      $self->_pidfile(undef);
337   }
338
339   return 1;
340}
341
342sub brik_fini {
343   my $self = shift;
344
345   return $self->stop_poison;
346}
347
3481;
349
350__END__
351
352=head1 NAME
353
354Metabrik::Network::Arp - network::arp Brik
355
356=head1 COPYRIGHT AND LICENSE
357
358Copyright (c) 2014-2017, Patrice E<lt>GomoRE<gt> Auffret
359
360You may distribute this module under the terms of The BSD 3-Clause License.
361See LICENSE file in the source distribution archive.
362
363=head1 AUTHOR
364
365Patrice E<lt>GomoRE<gt> Auffret
366
367=cut
Note: See TracBrowser for help on using the repository browser.