2007-10-26 22:44:33 +00:00
#!/usr/bin/perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT::MacMap ;
use xCAT::Table ;
2008-01-21 19:49:59 +00:00
use xCAT::Utils ;
2008-08-22 16:22:58 +00:00
use xCAT::MsgUtils ;
2007-10-26 22:44:33 +00:00
use IO::Select ;
use IO::Handle ;
use Sys::Syslog ;
use Data::Dumper ;
2009-03-25 14:39:38 +00:00
use SNMP ;
2007-10-26 22:44:33 +00:00
my % cisco_vlans ; #Special hash structure to reflect discovered VLANS on Cisco equip
#use IF-MIB (1.3.6.1.2.1.2) for all switches
# 1.3.6.1.2.1.31.1.1 - ifXtable
# 1.3.6.1.2.1.31.1.1.1.1.N = name - ifName
#Using BRIDGE-MIB for most switches( 1.3.6.1.2.1.17 )
# 1.3.6.1.2.1.17.1.4 - dot1dBasePortTable
# 1.3.6.1.2.1.17.1.4.1.1.X = N - dot1dBasePort
# 1.3.6.1.2.1.17.4.3 - dot1dTpFdbTable #FAILS on FORCE10,
#
#If particular result fails, fallback to Q-BRIDGE-MIB for Force10 (1.3.6.1.2.1.17.7)
# 1.3.6.1.2.1.17.7.1.2.2 - dot1qTpFdbTable
sub namesmatch {
= pod
MacMap attempts to do it ' s best to determine whether or not a particular SNMP description of
a port matches the user specified value in the configuration . Generally , if the configuration
consists of non - stacked switches without line cards , the user should only need to specify the
port number without any characters or / characters . If the configuration contains line cards
or stacked switches , use of that particular switch ' s appropriate / syntax in generally called
for . The exception being stacked SMC 8848 switches , in which all ports are still single
numbers , and the ports on the second switch begin at 57 .
If difficulty is encountered , or a switch is attempted with a format that doesn ' t match any
existing rule , it is recommended to use snmpwalk on the switch with the .1 .3 .6 .1 .2 .1 .31 .1 .1 .1 .1
OID , and have the switch table port value match exactly the format suggested by that OID .
= cut
my $ namepercfg = shift ;
my $ namepersnmp = shift ;
if ( $ namepercfg eq $ namepersnmp ) {
return 1 ; # They matched perfectly
}
#Begin guessing, first off, all tested scenarios have likely correct guesses ending
#in the cfg string, with some non-numeric prefix before it.
2008-10-14 22:56:43 +00:00
#3com convention, contributed by Aaron Knister
if ( $ namepersnmp =~ /^RMON Port (0?)(\d+) on unit \d+/ ) {
if ( $ 2 =~ $ namepercfg ) {
return 1 ;
}
}
2010-07-21 14:48:47 +00:00
# dell 6248 convention
if ( $ namepersnmp =~ /^Unit \d Port (\d+)$/ ) {
if ( $ 1 eq $ namepercfg ) {
return 1 ;
}
}
2009-03-27 03:33:59 +00:00
unless ( $ namepersnmp =~ /[^0123456789]$namepercfg\z/ ) {
#Most common case, won't match at all
return 0 ;
}
2008-10-14 22:56:43 +00:00
2008-11-12 19:19:13 +00:00
#stop contemplating vlan, Nu, stacking ports, and console interfaces
2008-11-12 20:50:44 +00:00
if ( ( $ namepersnmp =~ /vl/i ) or ( $ namepersnmp =~ /Nu/ ) or ( $ namepersnmp =~ /onsole/ ) or ( $ namepersnmp =~ /Stack/ ) ) {
2007-10-26 22:44:33 +00:00
return 0 ;
}
#broken up for code readablitiy, don't check port channel numbers or CPU
#have to distinguish betweer Port and Po and PortChannel
2009-03-27 03:33:59 +00:00
if ( ( $ namepersnmp !~ /Port #/ ) and ( $ namepersnmp !~ /Port\d/ ) and ( $ namepersnmp =~ /Po/ ) or ( $ namepersnmp =~ /po\d/ ) or ( $ namepersnmp =~ /XGE/ ) or ( $ namepersnmp =~ /LAG/ ) or ( $ namepersnmp =~ /CPU/ ) ) {
2007-10-26 22:44:33 +00:00
return 0 ;
}
#don't contemplate ManagementEthernet
if ( ( $ namepersnmp =~ /Management/ ) ) {
return 0 ;
}
#The blacklist approach has been exhausted. For now, assuming that means good,
#if something ambiguous happens, the whitelist would have been:
#'Port','Port #','/' (if namepercfg has no /, then / would be...),
#'Gi','Te','GigabitEthernet','TenGigabitEthernet'
return 1 ;
}
sub new {
my $ self = { } ;
2009-03-25 14:39:38 +00:00
# Since switch.pm and lsslp.pm both create a MacMap object, SNMP is still required at xcatd start up.
# So we are going back to "use SNMP;" at the top of this file so RPM will automatically generate a prereq.
#eval { require SNMP; };
#if ($@) { die "SNMP support required to use MacMAP"; }
2007-10-26 22:44:33 +00:00
my $ proto = shift ;
my $ class = ref ( $ proto ) || $ proto ;
bless ( $ self , $ class ) ;
return $ self ;
}
sub find_mac {
# This function is given a mac address, checks for given mac address
# and returns undef if unable to find the node, and the nodename otherwise
my $ self = shift ;
my $ mac = shift ;
2008-01-11 20:55:28 +00:00
my $ cachedonly = shift ;
2007-10-26 22:44:33 +00:00
# For now HARDCODE (TODO, configurable?) a cache as stale after five minutes
# Also, if things are changed in the config, our cache could be wrong,
# invalidate on switch table write?
if ( $ self - > { mactable } - > { lc ( $ mac ) } and ( $ self - > { timestamp } > ( time ( ) - 300 ) ) ) {
my $ reftbl = 0 ;
foreach ( keys % { $ self - > { mactable } } ) {
if ( ( lc ( $ mac ) ne $ _ ) and ( $ self - > { mactable } - > { lc ( $ mac ) } eq $ self - > { mactable } - > { $ _ } ) ) {
2008-01-11 20:55:28 +00:00
#$reftbl = 1;
#Delete *possibly* stale data, without being heavy handed..
2009-02-24 11:02:34 +00:00
#But if this mac indicates multiple nodes, leave it there
if ( $ self - > { mactable } - > { lc ( $ mac ) } !~ /,/ )
{
delete $ self - > { mactable } - > { $ _ } ;
}
2007-10-26 22:44:33 +00:00
}
}
unless ( $ reftbl ) { return $ self - > { mactable } - > { lc ( $ mac ) } ; }
}
2008-01-11 20:55:28 +00:00
#If requesting a cache only check or the cache is a mere 20 seconds old
#don't bother querying switches
if ( $ cachedonly or ( $ self - > { timestamp } > ( time ( ) - 20 ) ) ) { return undef ; }
2007-10-26 22:44:33 +00:00
$ self - > refresh_table ; #not cached or stale cache, refresh
if ( $ self - > { mactable } - > { lc ( $ mac ) } ) {
return $ self - > { mactable } - > { lc ( $ mac ) } ;
}
return undef ;
}
sub refresh_table {
my $ self = shift ;
2008-10-29 03:08:34 +00:00
my $ curswitch ;
2008-02-01 16:23:20 +00:00
$ self - > { mactable } = { } ;
$ self - > { switchtab } = xCAT::Table - > new ( 'switch' , - create = > 1 ) ;
2008-10-29 03:08:34 +00:00
$ self - > { switchestab } = xCAT::Table - > new ( 'switches' , - create = > 1 ) ;
2010-09-13 18:54:26 +00:00
my @ switchentries = $ self - > { switchestab } - > getAllNodeAttribs ( [ qw( switch snmpversion username password privacy auth ) ] ) ;
2008-10-29 03:08:34 +00:00
$ self - > { switchparmhash } = { } ;
2007-10-26 22:44:33 +00:00
my $ community = "public" ;
2008-10-29 03:08:34 +00:00
$ self - > { sitetab } = xCAT::Table - > new ( 'site' ) ;
2007-10-26 22:44:33 +00:00
my $ tmp = $ self - > { sitetab } - > getAttribs ( { key = > 'snmpc' } , 'value' ) ;
if ( $ tmp and $ tmp - > { value } ) { $ community = $ tmp - > { value } }
else { #Would warn here..
}
2008-10-29 03:08:34 +00:00
foreach ( @ switchentries ) {
$ curswitch = $ _ - > { switch } ;
$ self - > { switchparmhash } - > { $ curswitch } = $ _ ;
if ( $ _ - > { snmpversion } ) {
if ( $ _ - > { snmpversion } =~ /3/ ) { #clean up to accept things like v3 or ver3 or 3, whatever.
$ self - > { switchparmhash } - > { $ curswitch } - > { snmpversion } = 3 ;
unless ( $ _ - > { auth } ) {
$ self - > { switchparmhash } - > { $ curswitch } - > { auth } = 'md5' ; #Default to md5 auth if not specified but using v3
}
} elsif ( $ _ - > { snmpversion } =~ /2/ ) {
$ self - > { switchparmhash } - > { $ curswitch } - > { snmpversion } = 2 ;
} else {
$ self - > { switchparmhash } - > { $ curswitch } - > { snmpversion } = 1 ; #Default to lowest common denominator, snmpv1
}
}
unless ( defined $ _ - > { password } ) { #if no password set, inherit the community
$ self - > { switchparmhash } - > { $ curswitch } - > { password } = $ community ;
}
}
2007-10-26 22:44:33 +00:00
my % checked_pairs ;
2008-01-14 22:19:17 +00:00
my @ entries = $ self - > { switchtab } - > getAllNodeAttribs ( [ 'node' , 'port' , 'switch' ] ) ;
2007-10-26 22:44:33 +00:00
#Build hash of switch port names per switch
$ self - > { switches } = { } ;
foreach $ entry ( @ entries ) {
2008-01-19 16:45:29 +00:00
if ( defined ( $ entry - > { switch } ) and $ entry - > { switch } ne "" and defined ( $ entry - > { port } ) and $ entry - > { port } ne "" ) {
2009-01-19 08:58:29 +00:00
if ( ! $ self - > { switches } - > { $ entry - > { switch } } - > { $ entry - > { port } } )
{
$ self - > { switches } - > { $ entry - > { switch } } - > { $ entry - > { port } } = $ entry - > { node } ;
}
else
{
$ self - > { switches } - > { $ entry - > { switch } } - > { $ entry - > { port } } . = ",$entry->{node}" ;
}
2008-01-19 16:45:29 +00:00
} else {
2008-08-22 16:22:58 +00:00
xCAT::MsgUtils - > message ( "S" , "xCAT Table error:" . $ entry - > { node } . "Has missing or invalid switch.switch and/or switch.port fields" ) ;
2008-01-19 16:45:29 +00:00
}
}
2007-10-26 22:44:33 +00:00
my $ children = 0 ;
my $ inputs = new IO:: Select ;
$ SIG { CHLD } = sub { while ( waitpid ( - 1 , WNOHANG ) > 0 ) { $ children - - } } ;
foreach $ entry ( @ entries ) {
if ( $ checked_pairs { $ entry - > { switch } } ) {
next ;
}
$ checked_pairs { $ entry - > { switch } } = 1 ;
pipe my $ child , my $ parent ;
$ child - > autoflush ( 1 ) ;
$ parent - > autoflush ( 1 ) ;
$ children + + ;
2008-01-21 19:49:59 +00:00
$ cpid = xCAT::Utils - > xfork ;
2007-10-26 22:44:33 +00:00
unless ( defined $ cpid ) { die "Cannot fork" } ;
if ( $ cpid == 0 ) {
close ( $ child ) ;
$ self - > refresh_switch ( $ parent , $ community , $ entry - > { switch } ) ;
exit ( 0 ) ;
}
close ( $ parent ) ;
$ inputs - > add ( $ child ) ;
}
while ( $ children ) {
$ self - > handle_output ( $ inputs ) ;
}
while ( $ self - > handle_output ( $ inputs ) ) { } ; #Drain the pipes
$ self - > { timestamp } = time ;
}
sub handle_output {
my $ self = shift ;
my $ inputs = shift ;
my @ readied = $ inputs - > can_read ( 1 ) ;
my $ rc = @ readied ;
my $ ready ;
foreach $ ready ( @ readied ) {
my $ line = <$ready> ;
unless ( $ line ) {
$ inputs - > remove ( $ ready ) ;
close ( $ ready ) ;
next ;
}
$ line =~ m/^([^|]*)\|(.*)/ ;
$ self - > { mactable } - > { $ 1 } = $ 2 ;
}
return $ rc ;
}
sub walkoid {
my $ session = shift ;
my $ oid = shift ;
2009-01-30 16:25:55 +00:00
my % namedargs = @ _ ;
2007-10-26 22:44:33 +00:00
my $ retmap = undef ;
my $ varbind = new SNMP:: Varbind ( [ $ oid , '' ] ) ;
$ session - > getnext ( $ varbind ) ;
if ( $ session - > { ErrorStr } ) {
2009-01-30 16:25:55 +00:00
unless ( $ namedargs { silentfail } ) {
if ( $ namedargs { warncisco } ) {
xCAT::MsgUtils - > message ( "S" , "Error communicating with " . $ session - > { DestHost } . " (First attempt at indexing by VLAN failed, ensure that the switch has the vlan configured such that it appears in 'show vlan'): " . $ session - > { ErrorStr } ) ;
} else {
xCAT::MsgUtils - > message ( "S" , "Error communicating with " . $ session - > { DestHost } . ": " . $ session - > { ErrorStr } ) ;
}
}
2007-10-26 22:44:33 +00:00
return undef ;
}
my $ count = 0 ;
while ( $ varbind - > [ 0 ] =~ /^$oid\.?(.*)/ ) {
$ count + + ;
if ( $ 1 ) {
$ retmap - > { $ 1 . "." . $ varbind - > [ 1 ] } = $ varbind - > [ 2 ] ; #If $1 is set, means key should
} else {
$ retmap - > { $ varbind - > [ 1 ] } = $ varbind - > [ 2 ] ; #If $1 is set, means key should
}
$ session - > getnext ( $ varbind ) ;
}
return $ retmap ;
}
sub refresh_switch {
my $ self = shift ;
my $ output = shift ;
my $ community = shift ;
my $ switch = shift ;
2008-10-29 03:08:34 +00:00
my $ snmpver = '1' ;
2008-09-22 20:03:02 +00:00
my $ swent ;
2008-10-29 03:08:34 +00:00
$ swent = $ self - > { switchparmhash } - > { $ switch } ;
if ( $ swent ) {
$ snmpver = $ swent - > { snmpversion } ;
$ community = $ swent - > { password } ;
2008-09-22 20:03:02 +00:00
}
2008-10-29 03:08:34 +00:00
if ( $ snmpver ne '3' ) {
$ session = new SNMP:: Session (
DestHost = > $ switch ,
Version = > $ snmpver ,
Community = > $ community ,
UseNumeric = > 1
) ;
} else { #we have snmp3
if ( $ swent - > { privacy } ) {
$ session = new SNMP:: Session (
DestHost = > $ switch ,
SecName = > $ swent - > { username } ,
AuthProto = > uc ( $ swent - > { auth } ) ,
AuthPass = > $ community ,
SecLevel = > 'authPriv' ,
PrivProto = > uc ( $ swent - > { privacy } ) ,
PrivPass = > $ community ,
Version = > $ snmpver ,
UseNumeric = > 1
) ;
} else {
$ session = new SNMP:: Session (
DestHost = > $ switch ,
SecName = > $ swent - > { username } ,
AuthProto = > uc ( $ swent - > { auth } ) ,
AuthPass = > $ community ,
SecLevel = > 'authNoPriv' ,
Version = > $ snmpver ,
UseNumeric = > 1
) ;
}
}
2007-10-26 22:44:33 +00:00
#if ($error) { die $error; }
2008-08-22 16:22:58 +00:00
unless ( $ session ) { xCAT::MsgUtils - > message ( "S" , "Failed to communicate with $switch" ) ; return ; }
2007-10-26 22:44:33 +00:00
my $ namemap = walkoid ( $ session , '.1.3.6.1.2.1.31.1.1.1.1' ) ;
2008-04-24 20:51:06 +00:00
if ( $ namemap ) {
my $ ifnamesupport = 0 ; #Assume broken ifnamesupport until proven good... (Nortel switch)
foreach ( keys % { $ namemap } ) {
if ( $ namemap - > { $ _ } ) {
$ ifnamesupport = 1 ;
last ;
}
}
unless ( $ ifnamesupport ) {
$ namemap = 0 ;
}
}
unless ( $ namemap ) { #Failback to ifDescr. ifDescr is close, but not perfect on some switches
$ namemap = walkoid ( $ session , '.1.3.6.1.2.1.2.2.1.2' ) ;
}
2007-10-26 22:44:33 +00:00
unless ( $ namemap ) {
return ;
}
#Above is valid without community string indexing, on cisco, we need it on the next one and onward
2009-01-30 16:25:55 +00:00
my $ iftovlanmap = walkoid ( $ session , '.1.3.6.1.4.1.9.9.68.1.2.2.1.2' , silentfail = > 1 ) ;
2010-02-25 20:18:17 +00:00
my $ trunktovlanmap = walkoid ( $ session , '.1.3.6.1.4.1.9.9.46.1.6.1.1.5' , silentfail = > 1 ) ; #for trunk ports, we are interested in the native vlan
2007-10-26 22:44:33 +00:00
my % vlans_to_check ;
2010-02-25 20:18:17 +00:00
if ( defined ( $ iftovlanmap ) or defined ( $ trunktovlanmap ) ) { #We have a cisco, the intelligent thing is to do SNMP gets on the ports
2007-10-26 22:44:33 +00:00
# that we can verify are populated per switch table
my $ portid ;
foreach $ portid ( keys % { $ namemap } ) {
my $ portname ;
my $ switchport = $ namemap - > { $ portid } ;
foreach $ portname ( keys % { $ self - > { switches } - > { $ switch } } ) {
2010-02-25 20:18:17 +00:00
unless ( namesmatch ( $ portname , $ switchport ) ) {
next ;
}
if ( not defined $ iftovlanmap - > { $ portid } and not defined $ trunktovlanmap - > { $ portid } ) {
xCAT::MsgUtils - > message ( "S" , "$portid missing from switch" ) ;
next ;
}
if ( defined $ iftovlanmap - > { $ portid } ) {
$ vlans_to_check { "" . $ iftovlanmap - > { $ portid } } = 1 ; #cast to string, may not be needed
} else { #given above if statement, brigetovlanmap *must* be defined*
$ vlans_to_check { "" . $ trunktovlanmap - > { $ portid } } = 1 ; #cast to string, may not be needed
}
2007-10-26 22:44:33 +00:00
}
}
} else {
$ vlans_to_check { 'NA' } = 1 ;
}
my $ vlan ;
2009-01-30 16:25:55 +00:00
my $ iscisco = 0 ;
2008-10-29 03:08:34 +00:00
foreach $ vlan ( sort keys % vlans_to_check ) { #Sort, because if numbers, we want 1 first
2008-11-12 19:19:13 +00:00
unless ( not $ vlan or $ vlan eq 'NA' or $ vlan eq '1' ) { #don't subject users to the context pain unless needed
2009-01-30 16:25:55 +00:00
$ iscisco = 1 ;
2008-10-29 03:08:34 +00:00
if ( $ snmpver ne '3' ) {
$ session = new SNMP:: Session (
2007-10-26 22:44:33 +00:00
DestHost = > $ switch ,
2008-10-29 03:08:34 +00:00
Version = > $ snmpver ,
2007-10-26 22:44:33 +00:00
Community = > $ community . "@" . $ vlan ,
UseNumeric = > 1
) ;
2008-10-29 03:08:34 +00:00
} else { #Cisco and snmpv3 with non-default vlan, user will have to do lots of configuration to grant context access
if ( $ swent - > { privacy } ) {
$ session = new SNMP:: Session (
DestHost = > $ switch ,
SecName = > $ swent - > { username } ,
AuthProto = > uc ( $ swent - > { auth } ) ,
AuthPass = > $ community ,
SecLevel = > 'authPriv' ,
PrivProto = > uc ( $ swent - > { privacy } ) ,
PrivPass = > $ community ,
Version = > $ snmpver ,
Context = > "vlan-" . $ vlan ,
UseNumeric = > 1
) ;
} else {
$ session = new SNMP:: Session (
DestHost = > $ switch ,
SecName = > $ swent - > { username } ,
AuthProto = > uc ( $ swent - > { auth } ) ,
AuthPass = > $ community ,
SecLevel = > 'authNoPriv' ,
Version = > $ snmpver ,
Context = > "vlan-" . $ vlan ,
UseNumeric = > 1
) ;
}
}
2007-10-26 22:44:33 +00:00
}
unless ( $ session ) { return ; }
2009-01-30 16:25:55 +00:00
my $ bridgetoifmap = walkoid ( $ session , '.1.3.6.1.2.1.17.1.4.1.2' , ciscowarn = > $ iscisco ) ; # Good for all switches
2007-10-26 22:44:33 +00:00
# my $mactoindexmap = walkoid($session,'.1.3.6.1.2.1.17.4.3.1.2');
2009-01-30 16:25:55 +00:00
my $ mactoindexmap = walkoid ( $ session , '.1.3.6.1.2.1.17.7.1.2.2.1.2' , silentfail = > 1 ) ;
2007-10-26 22:44:33 +00:00
unless ( defined ( $ mactoindexmap ) ) { #if no qbridge defined, try bridge mib, probably cisco
#$mactoindexmap = walkoid($session,'.1.3.6.1.2.1.17.7.1.2.2.1.2');
2009-01-30 16:25:55 +00:00
$ mactoindexmap = walkoid ( $ session , '.1.3.6.1.2.1.17.4.3.1.2' , ciscowarn = > $ iscisco ) ;
2007-10-26 22:44:33 +00:00
} #Ok, time to process the data
foreach my $ oid ( keys %$ namemap ) {
#$oid =~ m/1.3.6.1.2.1.31.1.1.1.1.(.*)/;
my $ ifindex = $ oid ;
my $ portname ;
my $ switchport = $ namemap - > { $ oid } ;
foreach $ portname ( keys % { $ self - > { switches } - > { $ switch } } ) { # a little redundant, but
# computationally trivial
unless ( namesmatch ( $ portname , $ switchport ) ) { next }
#if still running, we have match
foreach my $ boid ( keys %$ bridgetoifmap ) {
unless ( $ bridgetoifmap - > { $ boid } == $ ifindex ) { next ; }
my $ bridgeport = $ boid ;
foreach ( keys %$ mactoindexmap ) {
if ( $ mactoindexmap - > { $ _ } == $ bridgeport ) {
my @ tmp = split /\./ , $ _ ;
my @ mac = @ tmp [ - 6 .. - 1 ] ;
printf $ output "%02x:%02x:%02x:%02x:%02x:%02x|%s\n" , @ mac , $ self - > { switches } - > { $ switch } - > { $ portname } ;
}
}
}
}
}
}
}
1 ;