В организации, где я работаю, используется довольно большое количество управляемых свичей, разнесенных по нескольким зданиям. Захотелось видеть MAC-адреса на их портах не только с помощью telnet или ssh, а прямо в веб-интерфейсе системы мониторинга Zabbix.
В поисках информации о том, как это сделать, был спрошен Гугл, и после непродолжительных поисков был найден чудесный документ за авторством Cisco, описывающий алгоритм получения искомых адресов и определения портов, на которых они фигурируют.
Беглое изучение выводов snmpwalk со свича 3Com 4200G показало, что в моем случае даже не требуется определять vlan, и достаточно всего трех шагов для определения MAC-адресов по портам.
Как вы можете понят из документации, нам всего-то надо:
1) получить список адресов командой
2) получить спискок портов командой
Если мы опрашиваем простой свич, то вот этот чудесный номер (13 в примере) — это и есть наш порт на свиче.
3) Осталось только найти соответствие между номерами OIDов, и мы увидим, что OIDы, оканчивающиеся на 12.7.172.8 дадут нам mac и номер порта, на котором этот mac висит.
Чтобы это все дело автоматизировать, я набросал простенький скрипт на перле (UPDATE0: код обновлен по совету gescheit; UPDATE1: код еще раз обновлен по совету FreeLSD)
Код очень бесхитростный, если нет файлика с уже полученными OIDами, то мы получаем OIDы с mac-адресами, и пишем в массив. А потом в такой же последовательности получаем OIDы с портами, что дает нам соответствие n-го значения в массиве n-му значению полученного OIDа с номером порта. Затем скидываем полученные данные в файлик.
Если же файлик при старте скрипта уже есть, и он не старше $interval, который в моем случае составляет 90 секунд — то берем данные из него. Это позволило использовать всего 2 snmp запроса на свич.
Скрипт принимает на вход два параметра, первый из которых это адрес устройства (такова специфика zabbix, для внешних скриптов всегда первым параметом будет адрес узла сети), а вторым параметром — интересующий нас номер порта. Если на этом порту адресов нет, скрипт вернет строку с текстом «null». В новой версии мы еще можем узнавать производителя устройства по mac-адресу. Для включения этой возможности существует переменная $show_vendor, при значении которой, равном единице, скрипт пытается получить данные о производителе от другого скрипта, указанного в переменной script. Этому скрипту передается mac-адрес утройства. Для себя, я реализовал довольной простой скрипт на sh, вся суть которого сводится к запуску одной строки:
Таинственный файлик, скрывающийся под переменной $filename, получается по ссылке, и содержит все нужные нам данные, заботливо вобранные ieee.
Я эту одну строчку слегка доработал, добавив даже режим «обновления», выкачивающий самую свежу версию списка производителей. Вот что получилось в итоге:
Запустим скрипт, выдающий нам производителя, для проверки на работоспособность:
Если все работает, двигаемся дальше.
Теперь надо в конфиге zabbixa настроить время выполнения скрипта, увеличив его до нужных значений. Если выбранного вами значения таймаута будет недостаточно, вы сразу это поймете, поскольку элементы данных, отражающие mac-адреса, будут один за одним переходить в категорию «неподдердживаемые» с описанием ошибки вроде «script execution timeout».
Правим конфиг:
#vim /etc/zabbix/zabbix-server.conf
Ищем там такие строки
и раскомментируем и меняем на нужное значение
Там же ищем
и при необходимости меняем на правильный путь к скриптам.
Вот по этому пути мы наш скрипт на перле и располагаем. Я его назвал бесхитростно — get_mac.pl
Теперь мы в Zabbix настроим в шаблоне для свича нужный тип данных:

И так для всех портов.
После этого, вы можете получать данные об адресах на портах свича прямо в zabbix.
У меня это выглядит так (обновленный скриншот с именами производителей выложить не могу, поскольку хабрасторадж не работает без флеша, а с гуглохромовским флешем не работает тоже):

Не пугайтесь, глядя на 16 порт — это аплинк.
Данная методика проверена и успешно работает уже месяц на девяти свичах марки 3Com: 4200G, 4210, 2916, 2924. Версия Zabbix 1.8.5, недавно обновил на 1.8.7. Учитывая, что я писал все это по документации Cisco, для свичей Cisco проблем также быть не должно.
P.S. Я считаю, что скрипт можно (и нужно) доработать и переработать, но я не силен в perl, и потому прошу у хабрасообщества помощи в этом благородном деле, чтобы выложить в wiki такого чудесного проекта, как Zabbix, достойное и качественное решение. Так же планирую вместе с улучшенным скриптом выложить туда свои шаблоны для свичей 3Com 4200G, 4210, 2924.
UPD0 Код скрипта обновлен по совету gescheit
UPD1 Cтатья и код обновлены по совету FreeLSD
Сбор информации и теория
В поисках информации о том, как это сделать, был спрошен Гугл, и после непродолжительных поисков был найден чудесный документ за авторством Cisco, описывающий алгоритм получения искомых адресов и определения портов, на которых они фигурируют.
Беглое изучение выводов snmpwalk со свича 3Com 4200G показало, что в моем случае даже не требуется определять vlan, и достаточно всего трех шагов для определения MAC-адресов по портам.
Как вы можете понят из документации, нам всего-то надо:
1) получить список адресов командой
$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) получить спискок портов командой
$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
...
Если мы опрашиваем простой свич, то вот этот чудесный номер (13 в примере) — это и есть наш порт на свиче.
3) Осталось только найти соответствие между номерами OIDов, и мы увидим, что OIDы, оканчивающиеся на 12.7.172.8 дадут нам mac и номер порта, на котором этот mac висит.
Переходим от теории к практике
Чтобы это все дело автоматизировать, я набросал простенький скрипт на перле (UPDATE0: код обновлен по совету gescheit; UPDATE1: код еще раз обновлен по совету 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;
Код очень бесхитростный, если нет файлика с уже полученными OIDами, то мы получаем OIDы с mac-адресами, и пишем в массив. А потом в такой же последовательности получаем OIDы с портами, что дает нам соответствие n-го значения в массиве n-му значению полученного OIDа с номером порта. Затем скидываем полученные данные в файлик.
Если же файлик при старте скрипта уже есть, и он не старше $interval, который в моем случае составляет 90 секунд — то берем данные из него. Это позволило использовать всего 2 snmp запроса на свич.
Скрипт принимает на вход два параметра, первый из которых это адрес устройства (такова специфика zabbix, для внешних скриптов всегда первым параметом будет адрес узла сети), а вторым параметром — интересующий нас номер порта. Если на этом порту адресов нет, скрипт вернет строку с текстом «null». В новой версии мы еще можем узнавать производителя устройства по mac-адресу. Для включения этой возможности существует переменная $show_vendor, при значении которой, равном единице, скрипт пытается получить данные о производителе от другого скрипта, указанного в переменной script. Этому скрипту передается mac-адрес утройства. Для себя, я реализовал довольной простой скрипт на sh, вся суть которого сводится к запуску одной строки:
awk --assign IGNORECASE=1 '/hex/ && /'$mac'/ {for (x=3; x<=NF; x++) {printf("%s ",$x)}}' $filename
Таинственный файлик, скрывающийся под переменной $filename, получается по ссылке, и содержит все нужные нам данные, заботливо вобранные ieee.
Я эту одну строчку слегка доработал, добавив даже режим «обновления», выкачивающий самую свежу версию списка производителей. Вот что получилось в итоге:
#!/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 don't 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
Запустим скрипт, выдающий нам производителя, для проверки на работоспособность:
$./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
Если все работает, двигаемся дальше.
Теперь надо в конфиге zabbixa настроить время выполнения скрипта, увеличив его до нужных значений. Если выбранного вами значения таймаута будет недостаточно, вы сразу это поймете, поскольку элементы данных, отражающие mac-адреса, будут один за одним переходить в категорию «неподдердживаемые» с описанием ошибки вроде «script execution timeout».
Правим конфиг:
#vim /etc/zabbix/zabbix-server.conf
Ищем там такие строки
### Option: Timeout
# Specifies how long we wait for agent, SNMP device or external check (in seconds).
# Range: 1-30
и раскомментируем и меняем на нужное значение
Timeout=5
Там же ищем
### Option: ExternalScripts
# Location of external scripts
ExternalScripts=/usr/share/zabbix/scripts/
и при необходимости меняем на правильный путь к скриптам.
Вот по этому пути мы наш скрипт на перле и располагаем. Я его назвал бесхитростно — get_mac.pl
Теперь мы в Zabbix настроим в шаблоне для свича нужный тип данных:

И так для всех портов.
После этого, вы можете получать данные об адресах на портах свича прямо в zabbix.
У меня это выглядит так (обновленный скриншот с именами производителей выложить не могу, поскольку хабрасторадж не работает без флеша, а с гуглохромовским флешем не работает тоже):

Не пугайтесь, глядя на 16 порт — это аплинк.
Данная методика проверена и успешно работает уже месяц на девяти свичах марки 3Com: 4200G, 4210, 2916, 2924. Версия Zabbix 1.8.5, недавно обновил на 1.8.7. Учитывая, что я писал все это по документации Cisco, для свичей Cisco проблем также быть не должно.
P.S. Я считаю, что скрипт можно (и нужно) доработать и переработать, но я не силен в perl, и потому прошу у хабрасообщества помощи в этом благородном деле, чтобы выложить в wiki такого чудесного проекта, как Zabbix, достойное и качественное решение. Так же планирую вместе с улучшенным скриптом выложить туда свои шаблоны для свичей 3Com 4200G, 4210, 2924.
UPD0 Код скрипта обновлен по совету gescheit
UPD1 Cтатья и код обновлены по совету FreeLSD