Search
Write a publication
Pull to refresh

Поиск коммутатора и порта по мак адресу хоста

Всем доброго дня.

Прежде всего, моя статья адресована администраторам крупных корпоративных сетей (построенных исключительно на оборудовании 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;}
}
}
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.