⬆️ ⬇️

We get lists of mac-addresses on ports of managed switches in Zabbix

In the organization where I work, quite a large number of managed switches are used, spread over several buildings. I wanted to see the MAC addresses on their ports not only using telnet or ssh, but directly in the web interface of the Zabbix monitoring system.



Information gathering and theory


In search of information on how to do this, Google was asked, and after a brief search, a wonderful Cisco authorship document was found, describing the algorithm for obtaining the required addresses and identifying the ports on which they appear.

A cursory study of the findings of snmpwalk from the 3Com 4200G switch showed that in my case it is not even necessary to determine the vlan, and only three steps are sufficient to determine the MAC addresses by ports.



As you can see from the documentation, we just need to:

1) get the address list with the command

$snmpwalk -c public -v 2c hostname .1.3.6.1.2.1.17.4.3.1.1



17.4.3.1.1.0.0.12.7.172.8 = Hex: 00 00 0C 07 AC 08

17.4.3.1.1.0.1.2.27.80.145 = Hex: 00 01 02 1B 50 91

17.4.3.1.1.0.1.3.72.77.90 = Hex: 00 01 03 48 4D 5A

17.4.3.1.1.0.1.3.72.221.191 = Hex: 00 01 03 48 DD BF

...



2) get a list of ports with the command

$snmpwalk -c public -v 2c hostname .1.3.6.1.2.1.17.4.3.1.2

17.4.3.1.2.0.0.12.7.172.8 = 13

17.4.3.1.2.0.1.2.27.80.128 = 13

17.4.3.1.2.0.1.2.27.80.145 = 13

17.4.3.1.2.0.1.2.163.145.225 = 13

...



If we interview a simple switch, then this wonderful number (13 in the example) is our switch port.



3) It remains only to find a match between the OID numbers, and we will see that OIDs ending in 12.7.172.8 will give us the mac and the port number on which this mac hangs.

')

Moving from theory to practice


To automate this whole thing, I sketched a simple pearl script ( UPDATE0: the code was updated according to the advice of gescheit ; UPDATE1: the code was updated again according to the advice of FreeLSD )



#! / usr / bin / perl

# ================================================= ===================

#

# QUERY MAC ADDRESSES FROM 3COM SWITCH FROM SELECTED PORT

# ================================================= ===================





use strict;

use Net :: SNMP qw ( snmp_dispatcher oid_lex_sort ) ;



my $ show_vendor = 1 ;

my $ script = "/usr/share/zabbix/scripts/mac.sh -s" ;

my $ debug = 0 ;

my $ file_name = "/ tmp / $ ARGV [0] -getmac.tmp" ;

my $ interval = 90 ; #refresh rate

my $ new = 0 ;

my @ list;

my $ write_secs = ( stat ( $ file_name ) ) [ 9 ] ;

if ( $ debug == 1 ) {

print "file $ file_name updated at" , scalar ( localtime ( $ write_secs ) ) , " \ n " ;

} ;

if ( $ write_secs + $ interval < time ) { #file updated less then $ interval seconds ago

if ( $ debug == 1 ) { print "generating new mac table \ n " ; }

$ new = 1 ;

open FH, "> $ file_name " or die "can't open ' $ file_name ': $!" ;

} else {

if ( $ debug == 1 ) { print "using old mac table \ n " ; }

open FR, "< $ file_name " or die "can't open ' $ file_name ': $!" ;

while ( my $ line = < FR > ) {

chomp ( $ line ) ;

my ( $ p , $ m ) = split / ; / , $ line ;

@ list [ $ p ] = "@list [ $ p ] $ m ," ;

}

}

# =====================================

my $ session ;

my $ error ;

my $ port = $ ARGV [ 1 ] ;

if ( $ new == 1 ) {

# === Setup session to remote host ===

( $ session , $ error ) = Net :: SNMP- > session (

-hostname = > $ ARGV [ 0 ] || 'localhost' ,

-community = > 'public' ,

-version = > '2c' ,

-translate = > [ -octetstring = > 0 ] ,

-port = > 161

) ;

# === Was the session created? ===

if ( ! defined ( $ session ) ) {

printf ( "ERROR:% s \ n " , $ error ) ;

exit 1 ;

}

} ;

# ==================================



# === OIDs queried to retrieve information ====

my $ TpFdbAddress = '1.3.6.1.2.1.17.4.3.1.1' ;

my $ TpFdbPort = '1.3.6.1.2.1.17.4.3.1.2' ;

# =============================================

my $ result ;

my @ tmp;

my $ x ;

if ( $ new == 1 ) {

if ( defined ( $ result = $ session - > get_table ( $ TpFdbAddress ) ) ) {

foreach ( oid_lex_sort ( keys ( % { $ result } ) ) ) {

$ x = unpack ( 'H *' , $ result - > { $ _ } ) ;

$ x = ~ s / ( .. ( ? ! \ Z ) ) / \ 1 : / g;

push ( @ tmp, $ x ) ;

}

} else {

if ( $ debug == 1 ) {

printf ( "ERROR:% s \ n \ n " , $ session - > error ( ) ) ;

}

}

# ==========================================

# === Print the returned MAC ports ===

$ result ;

if ( defined ( $ result = $ session - > get_table ( -baseoid = > $ TpFdbPort ) ) ) {

my $ i = 0 ;

my $ out = "" ;

my $ res = 0 ;

my $ tmp_port ;

my $ tmp_mac_list = "" ;

foreach ( oid_lex_sort ( keys ( % { $ result } ) ) ) {

if ( $ result - > { $ _ } == $ port ) {

$ res = 1 ;

if ( $ show_vendor == 1 ) {

$ out = ` $ script $ tmp [ $ i ] ` ;

printf ( "% s (% s)" , $ tmp [ $ i ] , $ out ) ;

} else {

printf ( "% s" , $ tmp [ $ i ] ) ;

} ;

print "," ;

} ;

if ( $ show_vendor == 1 ) {

$ out = ` $ script $ tmp [ $ i ] ` ;

printf FH ( "% s;% s (% s) \ n " , $ result - > { $ _ } , $ tmp [ $ i ] , $ out ) ;

} else {

printf FH ( "% s;% s \ n " , $ result - > { $ _ } , $ tmp [ $ i ] ) ;

} ;

$ i ++;

}

if ( $ res == 0 ) {

print "null" ;

} ;

} else {

if ( $ debug == 1 ) {

printf ( "ERROR:% s \ n \ n " , $ session - > error ( ) ) ;

} else {

print "null" ;

} ;

}

} else {

if ( @ list [ $ port ] ) {

print "@list [ $ port ]" ;

} else {

print "null" ;

} ;

}

print " \ n " ;

# =============================================

# === Close the session and exit the program ===

if ( $ new == 1 ) {

$ session - > close;

close FH;

} else {

close FR;

}

exit 0 ;







The code is very unsophisticated, if there is no file with already received OIDs, then we get OIDs with mac-addresses, and write to the array. And then in the same sequence we obtain OIDs with ports, which gives us the correspondence of the nth value in the array to the nth value of the received OID with the port number. Then we dump the data in the file.

If there is already a file at the start of the script, and it is not older than $ interval, which in my case is 90 seconds, then we take data from it. This allowed the use of only 2 snmp switch requests.

The script accepts two parameters as input, the first of which is the device address (this is the specificity of zabbix, for external scripts the first parameter is always the node's address), and the second parameter is the port number of interest to us. If there are no addresses on this port, the script will return a string with the text “null”. In the new version, we can still find out the device manufacturer by the mac-address. To enable this feature, there is a $ show_vendor variable, at the value of which is equal to one, the script tries to get data about the manufacturer from another script specified in the script variable. The mac-address of the device is transferred to this script. For myself, I implemented a fairly simple sh script, the whole essence of which comes down to running a single line:



awk --assign IGNORECASE = 1 '/ hex / && /' $ mac '/ {for (x = 3; x <= NF; x ++) {printf ("% s", $ x)}}' $ filename




The mysterious file hiding under the $ filename variable is obtained by reference , and contains all the data we need, carefully chosen ieee.



I slightly improved this one line, adding even the “update” mode, which extorts the most recent version of the list of manufacturers. Here's what happened in the end:



#! / bin / sh

#get vendor from mac-address

if [ -z "$ 1" ] ; then

echo "no args specified, exiting! Use $ 0 [-option] mac"

echo "where [-option] can be -u (update databse and exit) or -s (silent mode, just show vendor)"

exit 1

fi

# don't forget that Zabbix set $ PATH when running scripts

filename = / usr / share / zabbix / scripts / oui.txt

tmpfile = / tmp / oui.txt

link = http: // standards.ieee.org / develop / regauth / oui / oui.txt

sed = / bin / sed

awk = / bin / awk



case $ 1 in

-s )

silent = 1

mac = $ 2

if [ -z "$ 2" ] ; then

echo "no mac specified, exiting!"

exit 1

fi

;;

-u )

wget $ link -O $ tmpfile

if [ $? -gt 0 ] ; then

echo "download error, exiting"

exit 1

else

echo "Download ok!"

echo "Moving $ tmpfile to $ filename ..."

mv -f $ tmpfile $ filename

if [ $? -gt 0 ] ; then

echo "Error!"

else

echo "Success!"

fi

exit 0

fi

;;

* )

mac = $ 1

;;

esac

if [ ! -f $ filename ] ; then

if [ -z $ silent ] ; then

echo "no mac list file, dowload it? [y / n]"

else

exit 1

fi

while :

do

read INPUT_STRING

case $ INPUT_STRING in

y )

echo "Trying to download from $ link "

wget $ link -O $ filename

if [ $? -gt 0 ] ; then

echo "download error, exiting"

exit 1

else

echo "Download ok!"

fi

break

;;

n )

echo "exiting!"

exit 0

;;

* )

echo "wrong input, use [y / n]"

;;

esac

done

fi

if [ $ {# mac} -lt 8 ] ; then

mac = ` echo " $ mac " | $ sed 's / ^ \ (.. \) \ (.. \) \ (.. \) / \ 1- \ 2- \ 3 /' `

else

mac = ` echo " $ mac " | $ sed -e 's /: / - / g' `

fi

mac = $ {mac: 0: 8}

if [ -z $ silent ] ; then

echo "Searching for $ mac ..."

fi

result = ` $ awk --assign IGNORECASE = 1 '/ hex / && /' $ mac '/ {for (x = 3; x <= NF; x ++) {printf ("% s ", $ x)}}' $ filename `

if [ -z " $ result " ] ; then

result = "no info"

fi

echo -n $ result





Run the script that gives us the manufacturer to check for performance:



$./mac.sh "000000"

no mac list file, dowload it? [y/n]

y

Trying to download from standards.ieee.org/develop/regauth/oui/oui.txt

--2011-10-06 14:20:16-- standards.ieee.org/develop/regauth/oui/oui.txt

proxy.organization.ltd... 192.168.0.1

proxy.organization.ltd|192.168.0.1|:3128... .

Proxy , ... 200 OK

: 2493060 (2,4M) [text/plain]

Saving to: «oui.txt»



100%[==========================================================================================================================================================================>] 2 493 060 784K/s 3,1s



2011-10-06 14:20:26 (784 KB/s) - «oui.txt» saved [2493060/2493060]



Download ok!

Searching for 00-00-00...

XEROX CORPORATION



If everything works, move on.



Now you need to configure the script execution time in the zabbixa config, increasing it to the desired values. If the timeout value you select is not enough, you will immediately understand it, because the data elements reflecting mac-addresses will go one by one into the category of “unsupported” with a description of an error like “script execution timeout”.

Rule the config:

#vim /etc/zabbix/zabbix-server.conf



We are looking for such lines there

### Option: Timeout

# Specifies how long we wait for agent, SNMP device or external check (in seconds).

# Range: 1-30




and uncomment and change to the desired value

Timeout=5



We are also looking for

### Option: ExternalScripts

# Location of external scripts

ExternalScripts=/usr/share/zabbix/scripts/




and if necessary, change to the correct path to the scripts.

Here is the way we have our script on the pearl and place. I called it ingenuously - get_mac.pl



Now in Zabbix we will configure the necessary data type in the template for the switch:







And so for all ports.



After that, you can get the data on the addresses on the ports of the switch directly in zabbix.

It looks like this to me (I cannot post the updated screenshot with the names of the manufacturers, since habrastoradj does not work without a flash, but it doesn’t work with a Google flash drive either):





Do not worry, looking at port 16 - it is uplink.



This technique has been tested and has been successfully working for a month on nine 3Com brand switches: 4200G, 4210, 2916, 2924. Zabbix version 1.8.5, recently updated to 1.8.7. Considering that I wrote all this on the Cisco documentation, there should be no problems for Cisco switches.



PS I believe that the script can (and should) be refined and reworked, but I am not strong in perl, and therefore I ask the habrasoobshchestvo help in this noble cause to post on such a wonderful project like Zabbix in the wiki. I also plan, together with the improved script, to lay out my 3Com 4200G, 4210, 2924 switch templates.

UPD0 Script code updated by gescheit advice

UPD1 Article and code updated on the advice of FreeLSD

Source: https://habr.com/ru/post/128439/



All Articles