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
34
 
2 rodolico 35
use strict;
36
use warnings;
5 rodolico 37
use Data::Dumper; # only used for debugging
2 rodolico 38
use Socket; # for reverse dns entries
39
use YAML::Tiny; # apt-get libyaml-tiny-perl under debian
40
use File::Spec; # find location of script so we can put storage in same directory
41
use File::Basename;
42
 
5 rodolico 43
my %config;
2 rodolico 44
 
45
# where the script is located
46
my $scriptDir = dirname( File::Spec->rel2abs( __FILE__ ) );
47
# put the statefile in there
48
my $STATEFILE = $scriptDir . '/mapSwitches.yaml';
5 rodolico 49
my $CONFIGFILE = $scriptDir . '/mapSwitches.config.yaml';
2 rodolico 50
# main hash that holds the data we collect
51
my %switchports;
52
# some OIDS we need for the program
53
my $SWITCHPORTMIB  = 'iso.3.6.1.2.1.17.4.3.1.2';
54
my $SWITCHMACMIB   = 'iso.3.6.1.2.1.17.4.3.1.1';
55
my $ROUTERIPMACMIB = 'iso.3.6.1.2.1.4.22.1.2'; #'iso.3.6.1.2.1.3.1.1.2';
56
my $DEVICENAMEMIB  = '1.3.6.1.2.1.1.5';
57
my $DEVICELOCMIB   = '1.3.6.1.2.1.1.6';
58
 
59
sub makeMAC {
60
   my $string = shift;
61
   my @octets = split( '\.', $string );
62
   for ( my $i = 0; $i < @octets; $i++ ) {
63
      $octets[$i] = sprintf( "%02x", $octets[$i] );
64
   }
65
   return join( '', @octets );
66
}
67
 
68
sub updateValue {
69
   my ( $oldValue, $newValue ) = @_;
70
   $newValue = '' unless defined $newValue;
71
   if ( $newValue ) {
72
      $$oldValue = $newValue unless $newValue eq $$oldValue;
73
      return 1;
74
   }
75
   return 0;
76
}
77
 
78
sub getOneSNMPValue {
79
   my ( $oid, $community, $ip, $regex ) = @_;
80
   my $line = `snmpwalk -v1 -c $community $ip $oid`;
81
   $line =~ m/$regex/i;
82
   return $1;
83
}
84
 
85
sub initialize {
86
   my ( $hash, $name, @keys ) = @_;
87
   foreach my $key ( @keys ) {
88
      $hash->{$name}->{$key} = '' unless $hash->{$name}->{$key};
89
   }
90
}
91
 
92
sub updateIP {
93
   my ( $switchports, $ip, $mac ) = @_;
94
   foreach my $switch ( keys %$switchports ) {
95
      my $thisSwitch = $switchports->{$switch}->{'ports'};
96
      foreach my $port ( keys %$thisSwitch ) {
97
         my $thisPort = $thisSwitch->{$port};
98
         foreach my $connection ( keys %$thisPort ) {
99
            &initialize( $thisPort,$connection,'mac','ip','hostname','lastseen' );
100
            if ( $switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'mac'} eq $mac ) {
101
               my $modified = &updateValue( \$switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'ip'}, $ip );
102
               $modified |= &updateValue( \$switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'hostname'},gethostbyaddr( inet_aton( $ip ), AF_INET ));
103
               $switchports->{$switch}->{'ports'}->{$port}->{$connection}->{'lastseen'} = time() if $modified;
104
               return;
105
            } # if we found it
106
         } # for connection
107
      } # for port
108
   } # for switch
109
} # updateIP
110
 
5 rodolico 111
if ( -e $CONFIGFILE ) {
112
   my $yaml = YAML::Tiny->read( $CONFIGFILE );
113
   %config = %{ $yaml->[0] };
114
} else {
115
   die "could not locate config file $CONFIGFILE\n";
116
}
117
 
2 rodolico 118
# read the saved state into memory if it exists
119
if ( -e $STATEFILE ) {
120
   my $yaml = YAML::Tiny->read( $STATEFILE );
121
   %switchports = %{ $yaml->[0] };
122
}
123
 
124
# first, get all of the MAC/Port assignments from the switches
5 rodolico 125
foreach my $switch ( keys %{$config{'switches'}} ) {
2 rodolico 126
   &initialize( \%switchports, $switch, 'name','location' );
127
   &updateValue(
128
      \$switchports{$switch}{'name'},
5 rodolico 129
      &getOneSNMPValue( $DEVICENAMEMIB,$config{'switches'}{$switch}{'community'},$switch, '= STRING: "?([^"]*)"?' )
2 rodolico 130
      );
131
 
132
   &updateValue(
133
      \$switchports{$switch}{'location'},
5 rodolico 134
      &getOneSNMPValue( $DEVICELOCMIB,$config{'switches'}{$switch}{'community'},$switch, '= STRING: "?([^"]*)"?' )
2 rodolico 135
      );
136
 
5 rodolico 137
   my $values = `snmpwalk -v1 -c $config{switches}{$switch}{'community'} $switch $SWITCHPORTMIB`;
2 rodolico 138
   my @lines = split( "\n", $values );
139
   foreach my $line ( @lines ) {
140
      $line =~ m/$SWITCHPORTMIB\.([0-9.]+)\s=\sINTEGER:\s+(\d+)/;
5 rodolico 141
      my $uuid = $1; # this is the ID string of this MAC; normally the MAC itself in decimal form
142
      my $port = $2; # this is the port number
143
      next unless $port; # skip port 0, or any port which has nothing
144
      next if $config{'switches'}{$switch}{'portsToIgnore'} && grep( /^$port$/, @{$config{'switches'}{$switch}{'portsToIgnore'}} );
145
      $switchports{$switch}{'ports'}{$port}{$uuid}{'mac'} = &makeMAC( $1 );
2 rodolico 146
   }
147
}
148
 
149
# die Dumper( \%switchports );
150
 
151
 
152
# Now, try to match up the MAC address. Read the ARP table from the router(s)
5 rodolico 153
foreach my $router ( keys %{$config{'routers'}} ) {
154
   my $values = `snmpwalk -v1 -c $config{routers}{$router}{community} $router $ROUTERIPMACMIB`;
2 rodolico 155
   my @lines = split( "\n", $values );
156
   foreach my $line ( @lines ) {
157
      $line =~ m/$ROUTERIPMACMIB\.([0-9]+)\.([0-9.]+)\s=\sHex-STRING:\s([0-9a-z ]+)/i;
158
      my $interface = $1;
159
      my $ip = $2;
160
      my $mac = lc $3;
161
      $mac =~ s/ //g;
162
      &updateIP( \%switchports, $ip, $mac );
163
   }
164
}
165
 
166
#die Dumper( \%switchports );
167
 
168
# save the state file for later, so we can find things which disappear from the network
169
my $yaml = YAML::Tiny->new( \%switchports );
170
$yaml->write( $STATEFILE );
171
 
172
#print Dumper( \%switchports );
173
1;
174