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
5 rodolico 36
 
2 rodolico 37
use strict;
38
use warnings;
5 rodolico 39
use Data::Dumper; # only used for debugging
2 rodolico 40
use Socket; # for reverse dns entries
41
use YAML::Tiny; # apt-get libyaml-tiny-perl under debian
42
use File::Spec; # find location of script so we can put storage in same directory
43
use File::Basename;
44
 
25 rodolico 45
our $VERSION = '1.1';
46
 
5 rodolico 47
my %config;
2 rodolico 48
 
49
# where the script is located
50
my $scriptDir = dirname( File::Spec->rel2abs( __FILE__ ) );
51
# put the statefile in there
52
my $STATEFILE = $scriptDir . '/mapSwitches.yaml';
5 rodolico 53
my $CONFIGFILE = $scriptDir . '/mapSwitches.config.yaml';
2 rodolico 54
# main hash that holds the data we collect
55
my %switchports;
56
# some OIDS we need for the program
57
my $SWITCHPORTMIB  = 'iso.3.6.1.2.1.17.4.3.1.2';
58
my $SWITCHMACMIB   = 'iso.3.6.1.2.1.17.4.3.1.1';
25 rodolico 59
my $SWITCHIFNAMEMIB = '1.3.6.1.2.1.31.1.1.1.18';
2 rodolico 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 ) {
25 rodolico 104
            # skip the alias information on a port
105
            next if $connection eq 'alias';
2 rodolico 106
            &initialize( $thisPort,$connection,'mac','ip','hostname','lastseen' );
107
            if ( $switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'mac'} eq $mac ) {
108
               my $modified = &updateValue( \$switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'ip'}, $ip );
109
               $modified |= &updateValue( \$switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'hostname'},gethostbyaddr( inet_aton( $ip ), AF_INET ));
110
               $switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'lastseen'} = time() if $modified;
111
               return;
112
            } # if we found it
113
         } # for connection
114
      } # for port
115
   } # for switch
116
} # updateIP
117
 
25 rodolico 118
sub getAliases {
119
   my ($ip, $community, $MIB ) = @_;
120
   my %return;
121
   # get the aliases
122
   my $values = `snmpwalk -v1 -c $community $ip $MIB`;
123
   my @aliases = split( "\n", $values );
124
   chomp @aliases;
125
   foreach my $entry ( @aliases ) {
126
      $entry =~ m/\.(\d+) = STRING: "?([^"]*)"?$/;
127
      $return{$1} = $2;
128
   }
129
   return \%return;
130
}
131
 
132
 
5 rodolico 133
if ( -e $CONFIGFILE ) {
134
   my $yaml = YAML::Tiny->read( $CONFIGFILE );
135
   %config = %{ $yaml->[0] };
136
} else {
137
   die "could not locate config file $CONFIGFILE\n";
138
}
139
 
2 rodolico 140
# read the saved state into memory if it exists
141
if ( -e $STATEFILE ) {
142
   my $yaml = YAML::Tiny->read( $STATEFILE );
143
   %switchports = %{ $yaml->[0] };
144
}
145
 
146
# first, get all of the MAC/Port assignments from the switches
5 rodolico 147
foreach my $switch ( keys %{$config{'switches'}} ) {
2 rodolico 148
   &initialize( \%switchports, $switch, 'name','location' );
149
   &updateValue(
150
      \$switchports{$switch}{'name'},
5 rodolico 151
      &getOneSNMPValue( $DEVICENAMEMIB,$config{'switches'}{$switch}{'community'},$switch, '= STRING: "?([^"]*)"?' )
2 rodolico 152
      );
153
 
154
   &updateValue(
155
      \$switchports{$switch}{'location'},
5 rodolico 156
      &getOneSNMPValue( $DEVICELOCMIB,$config{'switches'}{$switch}{'community'},$switch, '= STRING: "?([^"]*)"?' )
2 rodolico 157
      );
25 rodolico 158
   # get the MAC addresses
5 rodolico 159
   my $values = `snmpwalk -v1 -c $config{switches}{$switch}{'community'} $switch $SWITCHPORTMIB`;
2 rodolico 160
   my @lines = split( "\n", $values );
25 rodolico 161
   my $aliases = getAliases ( $switch, $config{switches}{$switch}{'community'}, $SWITCHIFNAMEMIB );
162
 
2 rodolico 163
   foreach my $line ( @lines ) {
164
      $line =~ m/$SWITCHPORTMIB\.([0-9.]+)\s=\sINTEGER:\s+(\d+)/;
5 rodolico 165
      my $uuid = $1; # this is the ID string of this MAC; normally the MAC itself in decimal form
166
      my $port = $2; # this is the port number
167
      next unless $port; # skip port 0, or any port which has nothing
168
      next if $config{'switches'}{$switch}{'portsToIgnore'} && grep( /^$port$/, @{$config{'switches'}{$switch}{'portsToIgnore'}} );
169
      $switchports{$switch}{'ports'}{$port}{$uuid}{'mac'} = &makeMAC( $1 );
25 rodolico 170
      $switchports{$switch}{'ports'}{$port}{'alias'} = $aliases->{$port} ? $aliases->{$port} : '';
2 rodolico 171
   }
172
}
173
 
174
# die Dumper( \%switchports );
175
 
176
 
177
# Now, try to match up the MAC address. Read the ARP table from the router(s)
5 rodolico 178
foreach my $router ( keys %{$config{'routers'}} ) {
179
   my $values = `snmpwalk -v1 -c $config{routers}{$router}{community} $router $ROUTERIPMACMIB`;
2 rodolico 180
   my @lines = split( "\n", $values );
181
   foreach my $line ( @lines ) {
182
      $line =~ m/$ROUTERIPMACMIB\.([0-9]+)\.([0-9.]+)\s=\sHex-STRING:\s([0-9a-z ]+)/i;
183
      my $interface = $1;
184
      my $ip = $2;
185
      my $mac = lc $3;
186
      $mac =~ s/ //g;
187
      &updateIP( \%switchports, $ip, $mac );
188
   }
189
}
190
 
191
#die Dumper( \%switchports );
192
 
193
# save the state file for later, so we can find things which disappear from the network
194
my $yaml = YAML::Tiny->new( \%switchports );
195
$yaml->write( $STATEFILE );
196
 
197
#print Dumper( \%switchports );
198
1;
199