Предисловие

Есть у нас сеть. Не вдаваясь в подробности, состоит она из нескольких десятков коммутаторов D-Link и Cisco. Последние выполняют роль агрегаторов трафика, а D-Link занимаются доступом. Соответственно много VLAN, много устройств. И очень часто, пользователи меняют свое расположение, а вместе с ними переезжают их компьютеры и МФУ. Всё это дело хоть и подключается к нумерованным розеткам, часто поиск порта на коммутаторе для замены VLAN довольно долог и не удобен. Кроме того, техники, которые занимаются непосредственно подключением оборудования, не имеют доступа на коммутаторы. Им приходится глазами искать кабель в стойке, а потом просить инженеров проверить VLAN. Хотелось как-то упростить процесс.

Я уже давно неспешно изучаю Python в качестве дополнительного образования. В основном для применения в автоматизации на работе и дома. Ну тут вот и сошлось: Python, проблема и статья на Хабре Python3. Автоматизация конфигурации мультивендорного сетевого оборудования / Хабр. Прямо брать код из ее не стал, такой функциональности не требуется. Да и хотелось самому разобраться, опираясь на идею.

Написание кода началось с запроса к GigaChat, заодно было интересно проверить насколько он хорошо пишет скрипты. Не буду утомлять приводя весь цикл нашего общения, остановлюсь кратко: сперва GigaChat выдал скрипт на основе библиотеки paramiko, после уточнения, что нужно для Telnet выдал и эту версию. После нескольких итераций, в том числе с асинхронными функциями, остановился на библиотеке telnetlib. В Python 3.12 есть ее замена telnetlib3, т.к. сама telnetlib вырезана в релизе - устарела. ИИ вполне можно использовать для написания небольших скриптов, как отработка идеи или небольших функций в проектах. Однако стоит проверять код, т.к. в части случаев он получается совсем не рабочий.

Разберем получившийся код, благо он небольшой:

import telnetlib
import sys
import argparse
import re
import ipaddress
from time import sleep

# Параметры подключения
username = 'логин'  # Имя пользователя
password = 'пароль'  # Пароль администратора
#mac_address = ''    #04-18-D6-9C-75-C4 

Подключение нужных библиотек и параметры подключений. Так же небольшая подсказка как должен выглядеть вводимый MAC- адрес.

def telnet_connect(ip, username, password):
    try:    # отслеживаем ошибку соединения
        tn = telnetlib.Telnet(ip,timeout=2)
        tn.read_until(b":")  # Username
        tn.write(username.encode('ascii') + b"\n")
        tn.read_until(b":")  # Password
        tn.write(password.encode('ascii') + b"\n")
        sleep(1)  # Ждем немного, пока коммутатор авторизует пользователя
        return tn
    except ConnectionRefusedError:
        print(f'Ошибка подключения: {ip}, соединение отклонено сервером.')
        return '0'
    except TimeoutError:
        print(f'Ошибка таймаута: {ip}, истекло время ожидания подключения.')
        return '0'
    except OSError:
        print(f'Общая ошибка ОС')
        return '0' 

Функция подключения к коммутатору с обработчиком трех типов ошибок, вполне хватает чтобы понять, что коммутатор например отключен.

def find_mac(tn, mac_address):
    # запрос таблицы адресов
    tn.write(f'show fdb mac_address {mac_address}\n'.encode('ascii'))
    response = tn.read_until(b'Total entries:', timeout=1).decode('utf-8')
    # ищем нужный мас
    lines = response.split('\n')
    for line in lines:
        parts = line.strip().split()
        if len(parts) < 6: continue # пропуск коротких строк
        if mac_address in parts[2]:  # Проверка совпадения MAC
            port_number = int(parts[3]) # берем порт
            if port_number in range(49,52): continue    # trunk нас не интересует
            print(f"MAC-адрес {mac_address} найден на порте {port_number} VLAN {parts[1]}")
            return True     # найден
    return False    # не найден   

Функция поиска MAC-адреса. Собственно всё крутиться вокруг команды show fdb mac_address и парсинга её вывода. Так же исключаются порты в режиме trunk, так как там тоже может светиться нужный нам MAC, но это не то, что мы ищем.

def find_port(ipaddr,mac_address): # проходим по списку коммутаторов
    if not ipaddr: # если не задан конкретный адрес загружаем весь список
        # загружаем файл со списком коммутаторов
        try:
            with open('ListComm.txt') as f:
                list_comm = f.readlines()
            f.close()
        except FileNotFoundError:
            print("Файл списка коммутаторов не найден или недоступен")
            sys.exit()
    else:
        list_comm = [ipaddr]    # грузим в список указанный ip

    for ip in list_comm:
        ip = ip.strip()
        print(f'Ищем на {ip}...')
        tn = telnet_connect(ip, username, password)
        if tn == '0':   # если выдана ошибка соединения продолжаем искать на следующем
            continue
        port_found = find_mac(tn, mac_address)
        tn.close()
        if port_found: sys.exit()

Функция поиска порта. Собственно тут и происходит перебор всех коммутаторов с вызовом предыдущих функций. Всё просто: список коммутаторов лежит в текстовом файле, он подгружается и идет перебор ip-адресов. Да, тут нет проверки корректности IP адреса, предполагается что список-то подготовить корректно можно. Но даже с некорректным просто выдается ошибка соединения.

def main():
    # работа с аргументами
    parser = argparse.ArgumentParser(description="Поиск порта и VLAN по MAC-адресу на коммутаторах DLink")
    parser.add_argument('mac',type=str, help="MAC-адрес в формате XX-XX-XX-XX-XX-XX")
    parser.add_argument('ip', type=str,  nargs='?', help="IP адрес коммутатора, не обязательно")
    args = parser.parse_args()

    mac_address = args.mac.upper()  # сразу перевод в заглавные буквы
    # Регулярное выражение для проверки формата MAC-адреса
    mac_pattern = r'^([0-9A-Fa-f]{2}[-]){5}([0-9A-Fa-f]{2})$'

    if not re.match(mac_pattern, mac_address):
        print('Не верный формат MAC-адреса')
        sys.exit()

    if args.ip: # если присутствует адрес коммутатора, считываем его как единственный для поиска
        ipaddr = args.ip
        try:    # проверяем корректность ip адреса
            ipaddress.IPv4Address(ipaddr)
        except ipaddress.AddressValueError:
            print('Не верный формат IP-адреса')
            sys.exit()
        find_port(ipaddr, mac_address)
    else:
        find_port('',mac_address)

if __name__ == "__main__":
    main()   

Основная функция. Здесь инструкции по запуску, аргументам, их считывание и проверка корректности. Есть варианты запуска: по-умолчанию, со списком IP-адресов из файла или прямым указанием одного IP-адреса с игнорированием списка.

Вот собственно и всё. Теперь поиск нужного порта занимает буквально пару минут при переборе всех коммутаторов. Самое главное: скрипт упаковывается в EXE файл через PyInstaller, тем самым скрывая логин и пароль от техников, которым запрещен вход на коммутаторы. Используя скрипт, они могут найти порт гораздо быстрее и сразу проверить правильный ли VLAN там стоит.

Да, я знаю, что PyInstaller делает просто упаковку скрипта и нужных библиотек в EXE файл, который при желании можно вскрыть. Но на сей момент ничего лучшего не знаю.

Комментарии (22)


  1. drWhy
    17.08.2025 14:51

    "Им приходится глазами искать кабель в стойке, а потом просить инженеров проверить VLAN. Хотелось как-то упростить процесс."
    Когда-то у кого-то из вендоров можно было помигать портами насквозь по пути линка.


  1. aeangel
    17.08.2025 14:51

    Идея вроде как хороша. Но по реализации много вопросов. 1) есть же dot1x для этих вещей, коммутатор сам поставит нужный vlan после авторизации. 2) хардкодить логин пароль, список устройств в скрипт обернутый в exe не очень хорошая идея, как минимум из-за необходимости постоянно его перекомпилировать в случаях добавления/удаления устройств, как максимум безопасность. Возможно стоило бы сделать web страницу на которую могли ваши техники заходить и проверять. Там же можно было бы и базу прикрутить, и поиск происходил бы в разы быстрее.


    1. Wallor Автор
      17.08.2025 14:51

      Ответ чуть ниже. , не туда нажал, когда писал.


  1. Wallor Автор
    17.08.2025 14:51

    Про dot1x и такую функцию не знал, почитаю конечно. Но коммутаторы у нас старые, они все равно таких букв не знают.

    Удаление\добавление устройств происходит в внешнем файле с списком их IP. Так что тут без проблем. А насчет пароля, ну да, есть небольшое неудобство.

    А насчет веб страницы идея хорошая, надо обдумать.


    1. Akina
      17.08.2025 14:51

      А насчет пароля, ну да, есть небольшое неудобство.

      Можно и без пароля. MAC-based VLAN.


  1. Cas_on
    17.08.2025 14:51

    Не ясно, что делать, когда MAC адрес одинаковый на нескольких конечных устройствах (такое бывает и нередко).

    а так скрипт отличный.

    P.S. Ну и dot1x это наше все конечно.


    1. Wallor Автор
      17.08.2025 14:51

      Найдет первое вхождение, т.к. после этого завершает работу. Дальше разбираться вручную и много думать. ))


      1. Cas_on
        17.08.2025 14:51

        Работа скрипта понятна.

        Интересно, как будут решать эту проблему потом. или как обычно - куча новых заявок, множество потерянного времени на диагностику и решение проблемы в течении недели ...


    1. aMster1
      17.08.2025 14:51

      Вот прям удивили - МАС адрес должен быть уникальным. И нормальный коммутатор начинает немного нервничать если с разных портов у него одинаковые маки прилетают ( мы не рассматриваем ситуацию с дублированием маков всякими роутерами - она то как раз и была разработана из-за уникальности маков.


    1. KyZZMI4
      17.08.2025 14:51

      Точно нередко? Я такое встретил один раз за лет 20. И то из-за того что монтажники заливали бездумно готовый конфиг в специфические устройства. Конфигурация перетирала родной мак и в сети оказалось с десяток одинаковых маков.


      1. Sna4es
        17.08.2025 14:51

        У нас была поставка 40 "отечественных" системных блоков, у пары оказались одинаковые маки.


    1. aik
      17.08.2025 14:51

      Я такое встречал на некоторых станках, куда производитель тупо прошивает одну и ту же прошивку с одинаковым МАС. И если у вас такой станок один, то ничего страшного. Если несколько, то начинаются приколы.


  1. IisNuINu
    17.08.2025 14:51

    уж больше 15 лет прошло, а задачи теже, когда то я писал скрипт на перле, который прохдил по десяткам свичей домовой сети. А суть задачи была следующая, предыдущий администратор наставил свичей в разных домах и уволился, а доокументации не оставил, где что не понятно. Мне предложили обойти эти дома и найти свичи, определить их адрес и связать с их IP. Я походил походил, а потом написал скрипт, который обходя свичи, списывал с них МАК адреса, потом шёл в NAS, и смотрел подключённых по PPPoE клиентах, затем обращался в биллинг, по которому идентифицировал клиентов с их адресами, и для каждого свича с его IP набиралась данные с адресами клиентов. Таким образом мне удалось геолоцировать все свичи, составить схему соединений сети. Перл рулит.


    1. drWhy
      17.08.2025 14:51

      Как же хорошо, когда есть добрый самаритянин.
      Ещё неплохо, когда оборудование - не зоопарк, а из линейки одного производителя.
      И у него есть симулятор всего этого счастья.
      И в нём кем-то добрым нарисована вся реальная сеть, и пакетики шаволятся.
      И можно сначала встраивать новые шелезяки, настраивать их, рушить сеть, поднимать, перенастраивать и опять. И всё в эмуляторе.
      А потом взять готовые скрипты и разослать по заинтересованным шелезякам.
      Но бывает и наоборот.


  1. magnificentbat
    17.08.2025 14:51

    Как альтернатива - можно выполнить поиск порта при помощи SNMP. Скорость работы с ним даже в однопотоке заметно выше. А если задать под скрипт snmp community с RO-правами, то безопасность скрипта значительно увеличится, так как техники, узнав его, не смогут изменить ни каких настроек.


    1. Bagatur
      17.08.2025 14:51

      Сдаётся, что не на все возможные коммутаторы легко найти .mib файлы. Хотя, если много свободного времени, можно, конечно, выгрузить всё, что коммутатор отдаёт по snmp, и сидеть это ручками разбирать, но это такое... Не быстрое...


      1. magnificentbat
        17.08.2025 14:51

        Ну почему не быстрое, когда есть Grep.

        К тому-же есть RFC MIB, которые, как правило, одинаковые для всех однотипных устройств. Например для описанной автором задачи половину данных можно взять из RFC'шного ifMib'a, остается только найти OID'ы таблицы MAC-адресов, которые обычно проприетарные.

        Касательно D-Link - у них есть MIB файлы практически для всех коммутаторов, даже довольно старых.


  1. Ava256
    17.08.2025 14:51

    Займитесь документированием вашего оборудования в netbox и жить станет проще.


  1. forajump
    17.08.2025 14:51

    Ходить телнетом каждый раз на все коммутаторы, передавая в открытом виде учетные данные — сомнительное удовольствие. Таблицы коммутации всех коммутаторов (а также таблица ARP маршрутизатора) собираются с определенной периодичностью по SNMP, записываются в БД с отметками времени, и в любой момент через фронтенд в виде веб-интерфейса по любому из параметров и временному фильтру ищется хоть текущий порт подключения, хоть история переключений конкретного клиента.


    1. Dorlas
      17.08.2025 14:51

      NetDisco ?


  1. net_racoon
    17.08.2025 14:51

    Во-первых, зачем это все если есть ансибл? Во-вторых, учетка в скрипте? В третьих, телнет?Серьезно?

    А все потому, что сначала надо было продумать архитектуру, а потом ее собирать.


    1. Wallor Автор
      17.08.2025 14:51

      Тех, кто собирал, уже нет давно на данном предприятии. Оборудование далеко не новое к тому же. Мне же было проще написать скрипт, чем разбираться в Ансибл.