Всем доброго дня.
Прежде всего, моя статья адресована администраторам крупных корпоративных сетей (построенных исключительно на оборудовании Cisco), в которых может насчитываться до 16000 ethernet портов. В таких сетях многие, казалось бы, элементарные вещи начинают со временем сильно утомлять.
К примеру, в крупных компаниях многим пользователям почему-то не сидится на местах и они начинают мигрировать с кабинета в кабинет, с этажа на этаж…, а это значит, что их хосты подключается в новые розетки и не факт, что эти пользователи после переезда окажутся в своем VLAN. Это в свою очередь влечет за собой следующее: почти каждый день в Service Desk приходят наряды такого содержания: «Просьба перевести такой-то MAC адрес в таком-то здании в такую-то сеть».
И вроде бы мелочь, но если она повторяется каждый день, то наступает момент, когда количество переходит в качество и подобные наряды ничего кроме раздражения не вызывают. Не знаю как остальные администраторы, но я терпеть не могу рутину в любом ее проявлении. Поэтому написал полезную утилиту на перле: fport.pl.
Пока в утилите минимальный функционал: она ищет порт и коммутатор по мак адресу и текущий VLAN порта. Утилита найденный порт не переводит в другой VLAN (пока я тестирую данную утилиту на предмет багов, т.к. в коде не всегда получается предусмотреть заранее все нюансы, а если случайно утилита переведет транковый порт… в общем не очень кошерно).
Формат задания аргументов для утилиты следующий:
./fport.pl “object1|mac1,mac2,mac3…” “object_2|mac1,mac2…” …
Формат задания MAC адреса: 2 последних байта либо все 6 байт, разделенных точками через каждые 2 байта; регистр не важен, между байтами могут быть дефисы или двоеточия.
Примеры:
0019.dbab.d5cf
00:19.db:ab.d5:cf
00-19.db-ab.d5-cf
001D.928A.9CD0
00:1D.92:8A.9C:D0
00-1D.92-8A.9C-D0
0c54
0c:54
0c-54
8EF3
8E:F3
8E-F3
Пример вывода (настоящие значения заменены псевдозначениями):
./fport.pl «XXX|zz:zz,nn:nn» «YYYY|mm-mm.mm-mm.mm-mm»
Searching MAC in the bulding block XXX:
MAC zzzz not found!
MAC nnnn found on device: x.x.x.x on port: Gix/y/z, now MAC in VLAN: xxx.
Searching MAC in the bulding block YYYY:
MAC mmmm.mmmm.mmmm found on device: y.y.y.y on port: Gix/y/z, now MAC in VLAN: yyy.
Напутствия:
Перед использованием данной утилиты должна быть создана база данных как минимум с тремя таблицами:
1. init содержит соответствие: id_объекта – ip_адрес_distribution:
init:
+———+———+
| obj_id | dev_ip |
+———+———+
| 1 | 3.3.3.3 |
| 2 | 2.2.2.2 |
+———+———+
2. aliases содержит список различных псевдонимов объекта, т.к. на предприятии коллеги часто могут использовать не только формально название объекта, но и всевозможные сокращения: id_объекта – псевдоним_объекта:
aliases:
+———+————-+
| obj_id | alias |
+———+————-+
| 1 | Mira |
| 2 | Vernadskogo |
| 2 | Vern |
+———+————-+
3. objects содержит соответствие: id_объекта – название_объекта:
objects:
+———+————————-+
| obj_id | object |
+———+————————-+
| 1 | Prospekt Mira, 101 |
| 2 | Prospekt Vernadskogo, 5 |
+———+————————-+
Также должен работать протокол CDP на всех транках.
Немного о принципе работы: fport.pl выделяет аргументы, по очереди выбирает из базы данных необходимые ip адреса соответствующих distribution устройств и, используя данные какой-либо учетный записи в TACAS, по очереди ищет все маки на данном объекте. Процесс поиска представляет собой рекурсивный парсинг вывода следующих команд: sh mac- mac, sh cdp neighbors port_name detail, sh etherchannel summary | i Po_number (вывод команды sh etherchannel summary используется для случая, когда на порту настроена агрегация).
Более подробно с алгоритмом работы можно ознакомиться из исходного кода под катом (в коде присутствуют основные комментарии).
Вот собственно сам код.
Ставьте perl, mysql создавайте базу и таблицы с описанным выше интерфейсом и все будет ok:
#!/usr/bin/perl use strict; use DBI; #Чтобы подключатся к устройствам Cisco необходим этот модуль. use Net::Telnet::Cisco; our ($acs_login, $acs_password, $db_name, $db_login, $db_password, $dbh); #Инициализируем логины и пароли. &init_accounts; #Создем подключение к нашей базе. $dbh = &db_connect ($db_name, $db_login, $db_password); #По очереди обрабатываем перечисленные объекты. foreach my $arg (@ARGV) { my @MAC = (); (my $object, my $MAC, my $dev_ip) = (undef, undef, undef); ($object, $MAC) = split(/\|/, $arg); @MAC = split(/,/, $MAC); #Извлекаем из базы ip дистрибьюшена. $dev_ip = &get_dev_ip($object); print "Searching MAC in the bulding block $object:\n"; foreach my $mac (@MAC) { #удаляем ненужные символы и преобразуем MAC к нижнему регистру. $mac =~ tr/ABCDEF/abcdef/; $mac =~ s/[-:]//g; #Начинаем рекурсивный поиск MAC адреса, передаем этой функции ip дистрибьюшена и искомый MAC адрес. (my $ip, my $port, my $current_vlan) = &find_port_by_mac ($dev_ip, $mac); if ($ip and $port and $current_vlan) { print " MAC $mac found on device: $ip on port: $port, now MAC in VLAN: $current_vlan.\n"; } else { print " MAC $mac not found!\n"; } } } print "\n\nCompleted!\n"; sub init_accounts { $acs_login = 'ram'; $acs_password = 'kexdhftuj'; $db_name = 'Lukoil'; $db_login = "root"; $db_password = "1234567890"; } sub db_connect { my ($db_name, $db_login, $db_password, $data_source, $dbh); ($db_name, $db_login, $db_password) = @_; $data_source = "DBI:mysql:$db_name:localhost"; $dbh = DBI->connect($data_source, $db_login, $db_password); return $dbh; } sub get_dev_ip { my ($object, $dev_ip, $subquery, $request, $sth, $ref); ($object) = @_; $subquery = "select obj_id from aliases where alias in ('$object')"; $request = "select obj_id,dev_ip from init where obj_id in ($subquery) group by obj_id;"; $sth = $dbh->prepare($request); $sth->execute(); $ref = $sth->fetchall_arrayref(); $dev_ip = ${${$ref}[0]}[1]; return $dev_ip; } sub find_port_by_mac { my ($session, $dev_ip, $ip, $int, $vl, $port, $vlan, $neighbor_ip, $sh_mac, $mac); ($dev_ip, $mac) = @_; #Подключаемся к дистрибьюшену if (eval {$session = Net::Telnet::Cisco->new(Host=>"$dev_ip", Timeout =>"25"); $session->login(Name => "$acs_login", Password => "$acs_password");}) { #получаем информацию о том, за каким портом виден данный MAC. ($port, $vlan) = &get_port_to_neighbor($session, $mac); #получаем информацию о том, за каким портом виден данный MAC. if ($port) { #Смотрим, виден ли коммутатор по CDP за этим портом или нет, если виден, то рекурсивно идем на него и т.д. $neighbor_ip = &get_neighbor_ip($session, $port); if ($neighbor_ip) { ($dev_ip, $port, $vlan) = &find_port_by_mac($neighbor_ip, $mac); } } $session->close; return ($dev_ip, $port, $vlan); } } sub get_port_to_neighbor { my ($session, $vlan, $mac, $sh_mac, $port, $Po, $etherchannel); ($session, $mac) = @_; if (eval {($sh_mac) = $session->cmd("sh mac- | i $mac");}) { $sh_mac =~ s/^(\n+)//; $sh_mac =~ s/(\n+)$//; $sh_mac =~ s/^(\s+)//; $sh_mac =~ s/(\s+)$//; if ($sh_mac =~ /(\d{1,3})\s+([a-f0-9]{4}\.){2}[a-f0-9]{4}.+([A-Za-z]{2})\s*((\d+\/)+\d+)/) { if ($1) {$vlan = $1;} if ($3 and $4) {$port = $3.$4;} } elsif ($sh_mac =~ /(\d{1,3})\s+([a-f0-9]{4}\.){2}[a-f0-9]{4}.+(Po\d+)/) { if ($1) {$vlan = $1;} if ($3) {$Po = $3;} if (eval {($etherchannel) = $session->cmd("sh etherchannel summary | i $Po");}) { $etherchannel =~ /([A-Za-z]{2}\s*(\d+\/)+\d+)/; if ($1) {$port = $1;} } } return ($port, $vlan); } } sub get_neighbor_ip { my (@sh_cdp_n_detail, $session, $sh_cdp_n_detail, $port, $neighbor_ip, $ip_phone); ($session, $port) = @_; $ip_phone = 1; if (eval {@sh_cdp_n_detail = $session->cmd("sh cdp neighbors $port detail");}) { foreach $sh_cdp_n_detail (@sh_cdp_n_detail) { $sh_cdp_n_detail =~ s/^(\n+)//; $sh_cdp_n_detail =~ s/(\n+)$//; $sh_cdp_n_detail =~ s/^(\s+)//; $sh_cdp_n_detail =~ s/(\s+)$//; if ($sh_cdp_n_detail =~ /[Ii][Pp]\s*address\s*:\s*((\d{1,3}\.){3}\d{1,3})/){ if ($1) {$neighbor_ip = $1;} } #Убеждаемся, что соседнее устройство не IP телефон elsif ($sh_cdp_n_detail =~ /SEP|SIP/){ $ip_phone = 0; } } if ($ip_phone) {return $neighbor_ip;} else {return 0;} } }
Источник: http://ajc.su/seti/poisk-kommutatora-i-porta-po-mak-adresu-xosta/