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
# 
3 rodolico 6
# requires snmp running on this machine. Also uses YAML::Tiny perl module
7
# under debian and derivatives, use the following command to install
8
# apt-get -y install snmp libyaml-tiny-perl
9
#
2 rodolico 10
# It then does an snmp walk through the arp table of one or
11
# more routers to determine IP and DNS entries for the MAC address
12
# 
13
# Information gathered is stored in persistent storage (a yaml formatted file
14
# in the same directory), then reloaded at the next start of the program.
15
# As new entries become available, they are added. A time stamp
16
# records the last time an entry was "seen"
17
 
18
# Data is stored in the hash %switchports with the following structure
19
# %switchports =
5 rodolico 20
#     {switch} (from $config{'switches'} key)
2 rodolico 21
#        {name} (scalar, from snmp)
22
#        {location} (scalar, from snmp)
23
#        {'ports'} (constant key for sub hash)
24
#           {port number} (from snmp)
25
#              {connection} (uniqe id from snmp, basically the MAC)
26
#                 {mac} (from snmp walk of switch)
27
#                 {ip}  (from snmp walk of router)
28
#                 {hostname} (from reverse dns query)
29
#                 {lastseen} (last time this was active as unix timestamp)
30
 
5 rodolico 31
# 20190407 RWR
32
# converted to use external config file in YAML format
33
# added the ability to ignore ports on the switches
25 rodolico 34
# 20190514 RWR
35
# Added port aliases
33 rodolico 36
# 20190526 RWR
37
# fixed mapSwitchCSV.pl to output in various delimited format (see README)
5 rodolico 38
 
2 rodolico 39
use strict;
40
use warnings;
5 rodolico 41
use Data::Dumper; # only used for debugging
2 rodolico 42
use Socket; # for reverse dns entries
43
use YAML::Tiny; # apt-get libyaml-tiny-perl under debian
44
use File::Spec; # find location of script so we can put storage in same directory
45
use File::Basename;
46
 
33 rodolico 47
our $VERSION = '1.2';
25 rodolico 48
 
5 rodolico 49
my %config;
2 rodolico 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';
5 rodolico 55
my $CONFIGFILE = $scriptDir . '/mapSwitches.config.yaml';
2 rodolico 56
# main hash that holds the data we collect
57
my %switchports;
58
# some OIDS we need for the program
59
my $SWITCHPORTMIB  = 'iso.3.6.1.2.1.17.4.3.1.2';
60
my $SWITCHMACMIB   = 'iso.3.6.1.2.1.17.4.3.1.1';
25 rodolico 61
my $SWITCHIFNAMEMIB = '1.3.6.1.2.1.31.1.1.1.18';
2 rodolico 62
my $ROUTERIPMACMIB = 'iso.3.6.1.2.1.4.22.1.2'; #'iso.3.6.1.2.1.3.1.1.2';
63
my $DEVICENAMEMIB  = '1.3.6.1.2.1.1.5';
64
my $DEVICELOCMIB   = '1.3.6.1.2.1.1.6';
65
 
66
sub makeMAC {
67
   my $string = shift;
68
   my @octets = split( '\.', $string );
69
   for ( my $i = 0; $i < @octets; $i++ ) {
70
      $octets[$i] = sprintf( "%02x", $octets[$i] );
71
   }
72
   return join( '', @octets );
73
}
74
 
75
sub updateValue {
76
   my ( $oldValue, $newValue ) = @_;
77
   $newValue = '' unless defined $newValue;
78
   if ( $newValue ) {
79
      $$oldValue = $newValue unless $newValue eq $$oldValue;
80
      return 1;
81
   }
82
   return 0;
83
}
84
 
85
sub getOneSNMPValue {
86
   my ( $oid, $community, $ip, $regex ) = @_;
87
   my $line = `snmpwalk -v1 -c $community $ip $oid`;
88
   $line =~ m/$regex/i;
89
   return $1;
90
}
91
 
92
sub initialize {
93
   my ( $hash, $name, @keys ) = @_;
94
   foreach my $key ( @keys ) {
95
      $hash->{$name}->{$key} = '' unless $hash->{$name}->{$key};
96
   }
97
}
98
 
99
sub updateIP {
100
   my ( $switchports, $ip, $mac ) = @_;
101
   foreach my $switch ( keys %$switchports ) {
102
      my $thisSwitch = $switchports->{$switch}->{'ports'};
103
      foreach my $port ( keys %$thisSwitch ) {
104
         my $thisPort = $thisSwitch->{$port};
105
         foreach my $connection ( keys %$thisPort ) {
25 rodolico 106
            # skip the alias information on a port
107
            next if $connection eq 'alias';
2 rodolico 108
            &initialize( $thisPort,$connection,'mac','ip','hostname','lastseen' );
109
            if ( $switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'mac'} eq $mac ) {
110
               my $modified = &updateValue( \$switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'ip'}, $ip );
111
               $modified |= &updateValue( \$switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'hostname'},gethostbyaddr( inet_aton( $ip ), AF_INET ));
112
               $switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'lastseen'} = time() if $modified;
113
               return;
114
            } # if we found it
115
         } # for connection
116
      } # for port
117
   } # for switch
118
} # updateIP
119
 
25 rodolico 120
sub getAliases {
121
   my ($ip, $community, $MIB ) = @_;
122
   my %return;
123
   # get the aliases
124
   my $values = `snmpwalk -v1 -c $community $ip $MIB`;
125
   my @aliases = split( "\n", $values );
126
   chomp @aliases;
127
   foreach my $entry ( @aliases ) {
128
      $entry =~ m/\.(\d+) = STRING: "?([^"]*)"?$/;
129
      $return{$1} = $2;
130
   }
131
   return \%return;
132
}
133
 
134
 
5 rodolico 135
if ( -e $CONFIGFILE ) {
136
   my $yaml = YAML::Tiny->read( $CONFIGFILE );
137
   %config = %{ $yaml->[0] };
138
} else {
139
   die "could not locate config file $CONFIGFILE\n";
140
}
141
 
2 rodolico 142
# read the saved state into memory if it exists
143
if ( -e $STATEFILE ) {
144
   my $yaml = YAML::Tiny->read( $STATEFILE );
145
   %switchports = %{ $yaml->[0] };
146
}
147
 
148
# first, get all of the MAC/Port assignments from the switches
5 rodolico 149
foreach my $switch ( keys %{$config{'switches'}} ) {
2 rodolico 150
   &initialize( \%switchports, $switch, 'name','location' );
151
   &updateValue(
152
      \$switchports{$switch}{'name'},
5 rodolico 153
      &getOneSNMPValue( $DEVICENAMEMIB,$config{'switches'}{$switch}{'community'},$switch, '= STRING: "?([^"]*)"?' )
2 rodolico 154
      );
155
 
156
   &updateValue(
157
      \$switchports{$switch}{'location'},
5 rodolico 158
      &getOneSNMPValue( $DEVICELOCMIB,$config{'switches'}{$switch}{'community'},$switch, '= STRING: "?([^"]*)"?' )
2 rodolico 159
      );
25 rodolico 160
   # get the MAC addresses
5 rodolico 161
   my $values = `snmpwalk -v1 -c $config{switches}{$switch}{'community'} $switch $SWITCHPORTMIB`;
2 rodolico 162
   my @lines = split( "\n", $values );
25 rodolico 163
   my $aliases = getAliases ( $switch, $config{switches}{$switch}{'community'}, $SWITCHIFNAMEMIB );
164
 
2 rodolico 165
   foreach my $line ( @lines ) {
166
      $line =~ m/$SWITCHPORTMIB\.([0-9.]+)\s=\sINTEGER:\s+(\d+)/;
5 rodolico 167
      my $uuid = $1; # this is the ID string of this MAC; normally the MAC itself in decimal form
168
      my $port = $2; # this is the port number
169
      next unless $port; # skip port 0, or any port which has nothing
170
      next if $config{'switches'}{$switch}{'portsToIgnore'} && grep( /^$port$/, @{$config{'switches'}{$switch}{'portsToIgnore'}} );
171
      $switchports{$switch}{'ports'}{$port}{$uuid}{'mac'} = &makeMAC( $1 );
25 rodolico 172
      $switchports{$switch}{'ports'}{$port}{'alias'} = $aliases->{$port} ? $aliases->{$port} : '';
2 rodolico 173
   }
174
}
175
 
176
# die Dumper( \%switchports );
177
 
178
 
179
# Now, try to match up the MAC address. Read the ARP table from the router(s)
5 rodolico 180
foreach my $router ( keys %{$config{'routers'}} ) {
181
   my $values = `snmpwalk -v1 -c $config{routers}{$router}{community} $router $ROUTERIPMACMIB`;
2 rodolico 182
   my @lines = split( "\n", $values );
183
   foreach my $line ( @lines ) {
184
      $line =~ m/$ROUTERIPMACMIB\.([0-9]+)\.([0-9.]+)\s=\sHex-STRING:\s([0-9a-z ]+)/i;
185
      my $interface = $1;
186
      my $ip = $2;
187
      my $mac = lc $3;
188
      $mac =~ s/ //g;
189
      &updateIP( \%switchports, $ip, $mac );
190
   }
191
}
192
 
193
#die Dumper( \%switchports );
194
 
195
# save the state file for later, so we can find things which disappear from the network
196
my $yaml = YAML::Tiny->new( \%switchports );
197
$yaml->write( $STATEFILE );
198
 
199
#print Dumper( \%switchports );
200
1;
201