1#!/usr/local/bin/perl
2#
3#
4use strict;
5use lib "<<Make:LIB>>";
6use Netdot::Model;
7use Getopt::Long qw(:config no_ignore_case bundling);
8use Log::Log4perl::Level;
9
10my %self;
11$self{ARP_LIMIT} = 0;
12$self{FWT_LIMIT} = 0;
13
14my $USAGE = <<EOF;
15
16    Locate a device given its name, MAC or IP address.
17
18    By default, this script uses information from the Netdot database.
19    The user also has the option of doing a "live" search by querying
20    relevant devices in the network.  In this case, providing a VLAN id
21    can significantly speed up the search.
22
23  Usage: $0 [options] <ether|ip|name>
24
25    Available options:
26
27    -A|--arp_limit <value>  Number of latest ARP cache entries to show (default: $self{ARP_LIMIT})
28    -F|--fwt_limit <valud>  Number of latest Forwarding Table entries to show (default: $self{FWT_LIMIT})
29    -v|--vlan      <vlanid> VLAN id to use when searching addresses "live"
30    -f|--forcelive          Force a "live" search
31    -d|--debug              Show debugging output
32    -h|--help               Show help
33
34EOF
35
36my $MAC  = Netdot->get_mac_regex();
37
38# handle cmdline args
39my $result = GetOptions( "A|arp_limit:s"  => \$self{ARP_LIMIT},
40			 "F|fwt_limit:s"  => \$self{FWT_LIMIT},
41                         "v|vlan:s"       => \$self{VLAN},
42			 "f|forcelive"    => \$self{FORCE_LIVE},
43			 "h|help"         => \$self{HELP},
44			 "d|debug"        => \$self{DEBUG},
45    );
46
47my $address = shift @ARGV;
48
49if ( !$result ) {
50    print $USAGE;
51    die "Error: Problem with cmdline args\n";
52}
53if ( $self{HELP} ) {
54    print $USAGE;
55    exit;
56}
57if ( !$address ) {
58    print $USAGE;
59    die "Error: Missing address or name\n";
60}
61
62my $logger = Netdot->log->get_logger('Netdot::Model::Device');
63my $logscr = Netdot::Util::Log->new_appender('Screen', stderr=>0);
64$logger->add_appender($logscr);
65
66# Notice that $DEBUG is imported from Log::Log4perl
67$logger->level($DEBUG) if ( $self{DEBUG} );
68
69print "--------------------\n";
70
71if ( $address =~ /$MAC/ ){
72    $address   = PhysAddr->format_address($address);
73    if ( $self{FORCE_LIVE} ){
74	&search_live(mac=>$address, vlan=>$self{VLAN});
75    }else{
76	&show_mac($address, 1);
77    }
78
79}elsif ( Ipblock->matches_ip($address) ){
80
81    if ( $self{FORCE_LIVE} ){
82	&search_live(ip=>$address, vlan=>$self{VLAN});
83    }else{
84	&show_ip($address, 1);
85    }
86}else{
87    # Try to resolve
88    if ( my @ips = Netdot->dns->resolve_name($address) ){
89	foreach my $ip ( @ips ){
90	    if ( $self{FORCE_LIVE} ){
91		&search_live(ip=>$ip, vlan=>$self{VLAN});
92	    }else{
93		&show_ip($ip, 1);
94	    }
95	}
96    }else{
97	die "$address not found\n"
98    }
99}
100
101###############################################################################
102#
103# Subroutine Section
104#
105###############################################################################
106
107###############################################################################
108sub show_ip {
109    my ($address, $show_arp) = @_;
110    my $ip = Ipblock->search(address=>$address)->first;
111    my $subnet;
112    if ( $ip ){
113	my $parent = $ip->parent;
114	if ( int($parent->status) && $parent->status->name eq "Subnet" ){
115	    $subnet = $parent;
116	}
117	print "\n";
118	print "IP Address : ", $address, "\n";
119	if ( $subnet ){
120	    print "Subnet     : ", $subnet->get_label, ", ", $subnet->description, "\n";
121	}
122	if ( my $name = Netdot->dns->resolve_ip($address) ){
123	    print "DNS        : ", $name, "\n";
124	}
125	if ( $show_arp ){
126	    my $last_n = $self{ARP_LIMIT} || 1;
127	    if ( my $arp = $ip->get_last_n_arp($last_n) ){
128		my @rows;
129		my %tstamps;
130		my $latest_mac;
131		foreach my $row ( @$arp ){
132		    my ($iid, $macid, $tstamp) = @$row;
133		    my $lbl   = Interface->retrieve($iid)->get_label;
134		    push @{$tstamps{$tstamp}{$macid}}, $lbl;
135		}
136		if ( $self{ARP_LIMIT} ){
137		    print "\nLatest ARP cache entries:\n\n";
138		}
139		foreach my $tstamp ( reverse sort keys %tstamps ){
140		    foreach my $macid ( keys %{$tstamps{$tstamp}} ){
141			my $mac   = PhysAddr->retrieve($macid)->address;
142			$latest_mac  = $mac unless defined $latest_mac;
143			if ( $self{ARP_LIMIT} ){
144			    print $tstamp, " ", $mac, " ", (join ', ', @{$tstamps{$tstamp}{$macid}}), "\n";
145			}
146		    }
147		}
148		&show_mac($latest_mac);
149	    }
150	}
151    }else{
152	warn "$address not found in DB.  Try searching live (--forcelive)\n";
153	exit 0;
154    }
155}
156
157
158###############################################################################
159sub show_mac {
160    my ($address, $show_arp) = @_;
161
162    my $mac = PhysAddr->search(address=>$address)->first;
163    if ( !$mac ){
164	warn "$address not found in DB.  Try searching live (--forcelive)\n";
165	exit 0;
166    }
167
168    print "\n";
169    print "MAC Address : ", $mac->address,    "\n";
170    print "Vendor      : ", $mac->vendor,     "\n";
171    print "First Seen  : ", $mac->first_seen, "\n";
172    print "Last Seen   : ", $mac->last_seen,  "\n";
173
174    my $last_n_fte = $self{FWT_LIMIT} || 1;
175    my $last_n_arp = $self{ARP_LIMIT} || 1;
176
177    my $fwt        = $mac->get_last_n_fte($last_n_fte);
178    my $arp        = $mac->get_last_n_arp($last_n_arp);
179    my @devices    = $mac->devices;
180    if ( @devices ){
181	print "\nDevices using this address: ";
182	print join(', ', map { $_->get_label } @devices), "\n";
183    }
184    my @interfaces = $mac->interfaces;
185    if ( @interfaces ){
186	print "\nInterfaces using this address: ";
187	print join(', ', map { $_->get_label } @interfaces), "\n";
188    }
189
190    if ( $self{FWT_LIMIT} ){
191	if ( $fwt && scalar @$fwt ){
192	    my %tstamps;
193	    foreach my $row ( @$fwt ){
194		my ($tstamp, $iid) = @$row;
195		my $iface = Interface->retrieve($iid);
196		my $lbl   = $iface->get_label;
197		push @{$tstamps{$tstamp}}, $lbl;
198	    }
199
200	    print "\nLatest forwarding table entries:\n\n";
201
202	    foreach my $tstamp ( reverse sort keys %tstamps ){
203		print $tstamp, ", ", join ', ', @{$tstamps{$tstamp}}, "\n";
204	    }
205	}
206    }
207
208    my ($latest_ip_id, $latest_ip);
209    if ( $show_arp ){
210	if ( $self{ARP_LIMIT} ){
211	    print "\nLatest ARP cache entries:\n\n";
212	}
213
214	if ( $arp && scalar @$arp ){
215	    my %tstamps;
216	    foreach my $row ( @$arp ){
217		my ($iid, $ipid, $tstamp) = @$row;
218		$latest_ip_id = $ipid unless $latest_ip_id;
219		my $lbl = Interface->retrieve($iid)->get_label;
220		push @{$tstamps{$tstamp}{$ipid}}, $lbl;
221	    }
222	    if ( $self{ARP_LIMIT} ){
223		foreach my $tstamp ( reverse sort keys %tstamps ){
224		    foreach my $ipid ( keys %{$tstamps{$tstamp}} ){
225			my $iplbl   = Ipblock->retrieve($ipid)->get_label;
226			print $tstamp, ", ", $iplbl, ", ", (join ', ', @{$tstamps{$tstamp}{$ipid}}), "\n";
227		    }
228		}
229	    }
230	    $latest_ip = Ipblock->retrieve($latest_ip_id)->address;
231	}
232    }
233
234    &show_ip($latest_ip) if $latest_ip;
235
236    if ( scalar(@interfaces) == 1 && int($interfaces[0]->neighbor) ){
237	print "\nNeighbor interface: ", $interfaces[0]->neighbor->get_label, "\n";
238    }else{
239	my $edge_port = $mac->find_edge_port();
240	&print_location($edge_port) if $edge_port;
241    }
242}
243
244###############################################################################
245sub search_live{
246    my (%argv) = @_;
247
248    my $info = Device->search_address_live(%argv);
249    if ( $info ){
250	my ($ipaddr, $macaddr);
251	$ipaddr  = $info->{ip};
252	$macaddr = $info->{mac};
253	if ( scalar keys %{$info->{routerports}} ){
254	    if ( $self{ARP_LIMIT} ){
255		print "\nARP entries: \n";
256	    }
257	    foreach my $id ( keys %{$info->{routerports}} ){
258		my $iface = Interface->retrieve($id);
259		my $ip    = (keys %{$info->{routerports}{$id}})[0];
260		$ipaddr   = $ip unless $ipaddr;
261		my $mac   = $info->{routerports}{$id}{$ip};
262		$macaddr  = $mac unless $macaddr;
263		if ( $self{ARP_LIMIT} ){
264		    print $iface->get_label, ", ", $ip, ", ", $mac, "\n";
265		}
266	    }
267	}
268	if ( scalar keys %{$info->{switchports}} && $self{FWT_LIMIT} ){
269	    print "\nFWT entries: \n";
270	    foreach my $id ( keys %{$info->{switchports}} ){
271		my $iface = Interface->retrieve($id);
272		print $iface->get_label;
273		print " ";
274	    }
275	    print "\n";
276	}
277	print "\n";
278	if ( $macaddr ){
279	    print "MAC Address : ", $macaddr, "\n";
280	    print "Vendor      : ", $info->{vendor}, "\n" if $info->{vendor};
281	}
282	if ( $ipaddr ){
283	    print "IP Address  : ", $ipaddr, "\n";
284	    print "DNS         : ", $info->{dns}, "\n" if $info->{dns};
285	}
286
287	print "\n";
288	&print_location($info->{edge}) if $info->{edge};
289
290    }else{
291	die "$address not found\n";
292    }
293}
294
295###############################################################################
296sub print_location{
297    my $port = shift;
298    return unless $port;
299    my $iface = Interface->retrieve($port);
300    return unless $iface;
301    my $info  = '';
302    $info    .= ', '.$iface->description if $iface->description;
303    my $jack  = ($iface->jack)? $iface->jack->get_label : $iface->jack_char;
304    $info    .= ', '.$jack if $jack;
305    print "\nLocation    : ", $iface->get_label, $info;
306    print "\nModel       : ", $iface->device->product->get_label;
307    print "\n\n";
308}
309
310=head1 AUTHOR
311
312Carlos Vicente, C<< <cvicente at ns.uoregon.edu> >>
313
314=head1 COPYRIGHT & LICENSE
315
316Copyright 2009 University of Oregon, all rights reserved.
317
318This program is free software; you can redistribute it and/or modify
319it under the terms of the GNU General Public License as published by
320the Free Software Foundation; either version 2 of the License, or
321(at your option) any later version.
322
323This program is distributed in the hope that it will be useful, but
324WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
325or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
326License for more details.
327
328You should have received a copy of the GNU General Public License
329along with this program; if not, write to the Free Software Foundation,
330Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
331
332=cut
333