Привет.

Есть желание поделиться с сообществом идеей, которая реализована в компании провайдере для оперативного реагирования на повреждение медного кабеля. Речь пойдет о витой паре и Ethernet. Конечно не претендую на изящность решений, однако сервис показал хорошие результаты работы.

image

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

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

Оборудование в компании на 99% состоит из D-link, поэтому SNMP MIB приведены для этого вендора. Некоторые из них RFC и должны подходить и к другим производителям.

Немного истории о том, из чего всё это вытекло.

Все началось весной 2018. На группу техподдержки(ТП) возросла нагрузка. Помимо отработки звонков абонентов, ТП также координировала монтажников при подключении новых абонентов, а также при выезде на восстановление и дебаг уже существующих клиентов. Нужно было немного разгрузить ТП и дать некие инструменты в руки монтажникам. Было решено сочинить мессенджерного «бота», который на вход принимал бы логин/договор абонента и монтажник прямо в полях мог бы сам произвести минимальный дебаг.

Вставлять весь функционал в одно приложение не хотелось, т.к. по сути такой функционал пригодился бы и техподу в браузере в том же CRM при отработке звонка, поэтому было решено вынести механизмы взаимодействия с оборудованием сети, биллингом, радиусом в отдельный сервис, сделать ему API и подключить по API и бота, и CRM, и всё что угодно.

Теперь немного кода и перейдем к сути поста.

И так, что же может потребоваться монтажнику в полях:

  1. Тест кабеля конечно же
  2. Просмотр ошибок на порту
  3. Просмотр статус порта
  4. Посмотреть, есть ли МАК-адреса на порту. (вдруг абонент включил кабель в LAN порт вместо WAN)
  5. IPTV подписки
  6. Посмотреть логи авторизаций
  7. Баланс, статус

Взаимодействовать с коммутаторами будем по SNMP, и кое-где по telnet.

В качестве веб-фреймворка использовал Bottle.

И так,

импортируем нужные либы
#!/usr/bin/python
# -*- coding: utf_8 -*-
 
from bottle import route, run, template, auth_basic, request, error
from lib import crm, snmp, gis, billing
import time


Добавляем лист с API ключами и декораторы для проверки, не будем же мы отдавать данные всем подряд).

код
apikeys = ['RANDOM_KEY1', 'RANDOM_KEY2']

api_error = '{"error":"apikey invalid"}'
host_down_error = '{"error":"host down"}'


def apikey_checker(fn):
    def wrapper(*args, **kwargs):
        if not check_apikey():
            return api_error
        return fn(*args, **kwargs)
    return wrapper

def check_apikey():
    return 'apikey' in request.query and request.query['apikey'] in apikeys


Ну и собственно пара функций для взаимодействия с оборудованием.

код
@route('/port_status/<ip>/<port>')
@apikey_checker
def get_port_status(ip=' ', port=' '):
    return snmp.port_status(ip, port)

@route('/cable_test/<ip>/<port>')
@apikey_checker
def get_cable_test(ip, port):
    return snmp.cable_test(ip, port)


Внутри snmp либы у нас словарь с расшифровкой возвращаемых SNMP статусов пары на порту.

Словарь статусов

pair_status = {
    '0': 'ok',
    '1': 'open',
    '2': 'short',
    '3': 'open-short',
    '4': 'crosstalk',
    '5': 'unknown',
    '6': 'count',
    '7': 'no-cable',
    '8': 'other'
}


Заготовка словаря под результат измерений порта. Его мы будем копировать, чтоб не городить каждый раз новый.

Скрытый текст
pair_result = {
    'pairs': {
        1: {
            'status': '-',
            'length': '-'
        },
        2: {
            'status': '-',
            'length': '-'
        },
        3: {
            'status': '-',
            'length': '-'
        },
        4: {
            'status': '-',
            'length': '-'
        },
    }
}


Функция

теста кабеля
def cable_test(ip, port):

    if not check_ip(ip):  # чекаем не прислали ли нам ерунду вместо IP
        return {'error': "IP %s invalid" % (ip)}

    host_status = check_host(ip)  # чекаем доступен ли свитч по управлению
    if host_status['status'] == 'down':
        return {'error': u"Свитч недоступен"}

    result = copy.deepcopy(pair_result)

    # не тестим кабель, если порт UP, т.к. есть оборудование которое теряет
    # линк на порту при тестировании.
    if port_status(ip, port)['status'] == 'down':
        try:

            mib = '.1.3.6.1.4.1.171.12.58.1.1.1.12.%s' % str(
                port)  # миб инициализации тестирования на порту

            # запускаем тест и ждем секунду пока он завершится
            snmp_int_set(ip, mib, 1)
            time.sleep(1)

            # забираем результаты измерений
            result['pairs'][1]['status'] = pair_status[
                snmp_get(ip, '.1.3.6.1.4.1.171.12.58.1.1.1.4.%s' % str(port))]
            result['pairs'][2]['status'] = pair_status[
                snmp_get(ip, '.1.3.6.1.4.1.171.12.58.1.1.1.5.%s' % str(port))]
            result['pairs'][3]['status'] = pair_status[
                snmp_get(ip, '.1.3.6.1.4.1.171.12.58.1.1.1.6.%s' % str(port))]
            result['pairs'][4]['status'] = pair_status[
                snmp_get(ip, '.1.3.6.1.4.1.171.12.58.1.1.1.7.%s' % str(port))]

            result['pairs'][1]['length'] = snmp_get(
                ip, '.1.3.6.1.4.1.171.12.58.1.1.1.8.%s' % str(port))
            result['pairs'][2]['length'] = snmp_get(
                ip, '.1.3.6.1.4.1.171.12.58.1.1.1.9.%s' % str(port))
            result['pairs'][3]['length'] = snmp_get(
                ip, '.1.3.6.1.4.1.171.12.58.1.1.1.10.%s' % str(port))
            result['pairs'][4]['length'] = snmp_get(
                ip, '.1.3.6.1.4.1.171.12.58.1.1.1.11.%s' % str(port))

            return result
        except Exception as e:
            print(e)
            return {'error': u'Возникла ошибка при тестировании кабеля'}
    else:
        return {'error': u'Порт не готов к тестированию. Возможно порт Link UP.'}


фунция вернёт

результат
{
    "pairs": {
        "1": {
            "status": "other",
            "length": "0"
        },
        "2": {
            "status": "open",
            "length": "4"
        },
        "3": {
            "status": "open",
            "length": "4"
        },
        "4": {
            "status": "other",
            "length": "0"
        }
    }
}


Позже добавил еще аналогичную функцию, исключительно для скрипта, она на вход принимает список портов, а не один, и не проверяет статус порта перед тестированием, это не нужно при массовом падении линков.

Вот так примерно стал выглядеть бот

image

Теперь к сути поста.

До реализации дебаг сервера использовалась технология аналогичная описанной в посте habr.com/post/188730. Петля на порту с включенным SNMP трапом. При падении «самолинка» на порту в мониторинг падало сообщение об этом.

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

Однако, таких физических ловушек было примерно только на 10% коммутаторов, а этого оказалось мало.

Позже придумали мониторить радиус. И это позволило увеличить процент покрытия мониторингом до 100%. И вот тут уже все разнится от инфраструктуры провайдера.

Периодически смотрим, сколько упало клиентских сессий с того или иного коммутатора. Сделать это легко, если на коммутаторах включены circuit_id, который имеет вид

D4:CA:6D:0A:66:C9::192.168.20.86::20

Тут у нас MAC абонента, IP коммутатора, номер порта абонента. Т.е. всё что нужно для дебага.
Группируем завершенные с��ссии по IP коммутатора, если таких сессий больше некоторого количества (у нас установлен триггер на 2 сессии в минуту), то скрипт обращается к дебаг серверу и тестит порты упавших сессий. Если порты всё также лежат и на них открыты или закорочены пары кабеля, и длинна хотя бы на двух портах одинакова (+- 2 метра), а именно так выглядит обрез кабеля глазами свитча, то считаем ситуацию подозрительной и отправляем сообщение оператору.

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

Вот реальное сообщение о подозрительном событии.

image

И результаты отработки таких сообщений

image

Был случай, когда скрипт прислал подобное сообщение, и через пару секунд свитч ушел в оффлайн, т.к. повредили оптику, и если бы не оперативность софта, то ситуацию приняли бы за типичное отключение электричества в доме.

В другой раз управляющая компания без предупреждения начала делать ремонт крыши и к ним прилетели ВОХР с автоматами, внезапный стресс для слесарей.

Так скрипт стал показывать неплохие результаты и за 4 месяца работы было успешно отработано ВОХР, полицией, и самими сотрудниками провайдера более 10 случаев вандализма. Поэтому я и решил поделиться концептом такого мониторинга.

Сейчас скрипт мониторит около 15000 коммутаторов без каких-либо физических «ловушек» и SNMP трапов.

Всем удачи в новом году!