Как стать автором
Обновить

Получаем списки mac-адресов на портах управляемых свичей в Zabbix

Время на прочтение 19 мин
Количество просмотров 53K
В организации, где я работаю, используется довольно большое количество управляемых свичей, разнесенных по нескольким зданиям. Захотелось видеть MAC-адреса на их портах не только с помощью telnet или ssh, а прямо в веб-интерфейсе системы мониторинга Zabbix.

Сбор информации и теория

В поисках информации о том, как это сделать, был спрошен Гугл, и после непродолжительных поисков был найден чудесный документ за авторством 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
Теги:
Хабы:
+6
Комментарии 9
Комментарии Комментарии 9

Публикации

Истории

Работа

Ближайшие события

PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн
Weekend Offer в AliExpress
Дата 20 – 21 апреля
Время 10:00 – 20:00
Место
Онлайн