Subversion Repositories sysadmin_scripts

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 rodolico 1
#! /usr/bin/env perl
2
 
3
# Script does an snmp walk through the ports of one or more network
4
# switch, gathering MAC addresses assigned to each port.
5
# 
6
# It then does an snmp walk through the arp table of one or
7
# more routers to determine IP and DNS entries for the MAC address
8
# 
9
# Information gathered is stored in persistent storage (a yaml formatted file
10
# in the same directory), then reloaded at the next start of the program.
11
# As new entries become available, they are added. A time stamp
12
# records the last time an entry was "seen"
13
 
14
# Data is stored in the hash %switchports with the following structure
15
# %switchports =
16
#     {switch} (from %switches key)
17
#        {name} (scalar, from snmp)
18
#        {location} (scalar, from snmp)
19
#        {'ports'} (constant key for sub hash)
20
#           {port number} (from snmp)
21
#              {connection} (uniqe id from snmp, basically the MAC)
22
#                 {mac} (from snmp walk of switch)
23
#                 {ip}  (from snmp walk of router)
24
#                 {hostname} (from reverse dns query)
25
#                 {lastseen} (last time this was active as unix timestamp)
26
 
27
use strict;
28
use warnings;
29
#use Data::Dumper; # only used for debugging
30
use Socket; # for reverse dns entries
31
use YAML::Tiny; # apt-get libyaml-tiny-perl under debian
32
use File::Spec; # find location of script so we can put storage in same directory
33
use File::Basename;
34
 
35
# one or more switches and their corresponding
36
# community name. Use v1 snmp only
37
my %switches = (
38
                   'dns name or ip' => 'snmp v1 community name',
39
                   #'dns name or ip' => 'snmp v1 community name'
40
               );
41
 
42
# You must include at least one router.
43
# this will be queried to resolve IP's to MAC 
44
# using its arp table
45
# put IP and community name into the hash
46
my %routers = (
47
                   'dns name or ip' => 'snmp v1 read/only community name',
48
                   #'dns name or ip' => 'snmp v1 read/only community name',
49
              );
50
 
51
# where the script is located
52
my $scriptDir = dirname( File::Spec->rel2abs( __FILE__ ) );
53
# put the statefile in there
54
my $STATEFILE = $scriptDir . '/mapSwitches.yaml';
55
# main hash that holds the data we collect
56
my %switchports;
57
# some OIDS we need for the program
58
my $SWITCHPORTMIB  = 'iso.3.6.1.2.1.17.4.3.1.2';
59
my $SWITCHMACMIB   = 'iso.3.6.1.2.1.17.4.3.1.1';
60
my $ROUTERIPMACMIB = 'iso.3.6.1.2.1.4.22.1.2'; #'iso.3.6.1.2.1.3.1.1.2';
61
my $DEVICENAMEMIB  = '1.3.6.1.2.1.1.5';
62
my $DEVICELOCMIB   = '1.3.6.1.2.1.1.6';
63
 
64
sub makeMAC {
65
   my $string = shift;
66
   my @octets = split( '\.', $string );
67
   for ( my $i = 0; $i < @octets; $i++ ) {
68
      $octets[$i] = sprintf( "%02x", $octets[$i] );
69
   }
70
   return join( '', @octets );
71
}
72
 
73
sub updateValue {
74
   my ( $oldValue, $newValue ) = @_;
75
   $newValue = '' unless defined $newValue;
76
   if ( $newValue ) {
77
      $$oldValue = $newValue unless $newValue eq $$oldValue;
78
      return 1;
79
   }
80
   return 0;
81
}
82
 
83
sub getOneSNMPValue {
84
   my ( $oid, $community, $ip, $regex ) = @_;
85
   my $line = `snmpwalk -v1 -c $community $ip $oid`;
86
   $line =~ m/$regex/i;
87
   return $1;
88
}
89
 
90
sub initialize {
91
   my ( $hash, $name, @keys ) = @_;
92
   foreach my $key ( @keys ) {
93
      $hash->{$name}->{$key} = '' unless $hash->{$name}->{$key};
94
   }
95
}
96
 
97
sub updateIP {
98
   my ( $switchports, $ip, $mac ) = @_;
99
   foreach my $switch ( keys %$switchports ) {
100
      my $thisSwitch = $switchports->{$switch}->{'ports'};
101
      foreach my $port ( keys %$thisSwitch ) {
102
         my $thisPort = $thisSwitch->{$port};
103
         foreach my $connection ( keys %$thisPort ) {
104
            &initialize( $thisPort,$connection,'mac','ip','hostname','lastseen' );
105
            if ( $switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'mac'} eq $mac ) {
106
               my $modified = &updateValue( \$switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'ip'}, $ip );
107
               $modified |= &updateValue( \$switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'hostname'},gethostbyaddr( inet_aton( $ip ), AF_INET ));
108
               $switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'lastseen'} = time() if $modified;
109
               return;
110
            } # if we found it
111
         } # for connection
112
      } # for port
113
   } # for switch
114
} # updateIP
115
 
116
# read the saved state into memory if it exists
117
if ( -e $STATEFILE ) {
118
   my $yaml = YAML::Tiny->read( $STATEFILE );
119
   %switchports = %{ $yaml->[0] };
120
}
121
 
122
# first, get all of the MAC/Port assignments from the switches
123
foreach my $switch ( keys %switches ) {
124
   &initialize( \%switchports, $switch, 'name','location' );
125
   &updateValue(
126
      \$switchports{$switch}{'name'},
127
      &getOneSNMPValue( $DEVICENAMEMIB,$switches{$switch},$switch, '= STRING: "?([^"]*)"?' )
128
      );
129
 
130
   &updateValue(
131
      \$switchports{$switch}{'location'},
132
      &getOneSNMPValue( $DEVICELOCMIB,$switches{$switch},$switch, '= STRING: "?([^"]*)"?' )
133
      );
134
 
135
   my $values = `snmpwalk -v1 -c $switches{$switch} $switch $SWITCHPORTMIB`;
136
   my @lines = split( "\n", $values );
137
   foreach my $line ( @lines ) {
138
      $line =~ m/$SWITCHPORTMIB\.([0-9.]+)\s=\sINTEGER:\s+(\d+)/;
139
      next unless $2; # skip port 0, or any port which has nothing
140
      $switchports{$switch}{'ports'}{$2}{$1}{'mac'} = &makeMAC( $1 );
141
   }
142
}
143
 
144
# die Dumper( \%switchports );
145
 
146
 
147
# Now, try to match up the MAC address. Read the ARP table from the router(s)
148
foreach my $router ( keys %routers ) {
149
   my $values = `snmpwalk -v1 -c $routers{$router} $router $ROUTERIPMACMIB`;
150
   my @lines = split( "\n", $values );
151
   foreach my $line ( @lines ) {
152
      $line =~ m/$ROUTERIPMACMIB\.([0-9]+)\.([0-9.]+)\s=\sHex-STRING:\s([0-9a-z ]+)/i;
153
      my $interface = $1;
154
      my $ip = $2;
155
      my $mac = lc $3;
156
      $mac =~ s/ //g;
157
      &updateIP( \%switchports, $ip, $mac );
158
   }
159
}
160
 
161
#die Dumper( \%switchports );
162
 
163
# save the state file for later, so we can find things which disappear from the network
164
my $yaml = YAML::Tiny->new( \%switchports );
165
$yaml->write( $STATEFILE );
166
 
167
#print Dumper( \%switchports );
168
1;
169