Если тебе надоели постоянные обрывы связи и косяки провайдера, но субъективные оценки типа «подвисает» не внушают доверия, лучший выбор — записать состояние сети в автоматическом режиме. Причем для этого необязательно гонять Nagios, который к тому же не так прост в настройке. Сегодня мы напишем утилиту для мониторинга сети, которая легко настраивается и сохраняет в журнал RTT до заданных хостов, packet loss и скорость соединения (опционально), а логи летят прямо в Telegram.

Виновником появления этой статьи стал уже несколько месяцев сбоящий интернет, который мне предоставляет единственный в округе провайдер. Увы, в мою деревню ничего, кроме ADSL, не завезли, и, судя по качеству связи, и тот не дошел без многочисленных скруток. Packet loss порой доходит до 60–70%, что уже ни в какие ворота не лезет. Поэтому я решил сам измерить качество связи, дабы ткнуть провайдеру под нос логи вместе с заявлением о расторжении договора.

 

Задачи

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

  • Пинг для заданных хостов. Просто маст-хэв для любой диагностической утилиты. Измеряя пинг, можно узнать также и процент потерь пакетов (packet loss), и коды ошибок, позволяющие выяснить, что именно не так с сетью. Например, Destination Prohibited означает, что сеть вроде и есть, но администратор какого-то из промежуточных устройств не пропускает пакет. В общем, анализировать статус-коды ответов обязательно.
  • Реальная возможность подключений по TCP. Возможна ситуация, когда хосты вроде живы и откликаются на пинг, DNS работает, а доступ в интернет закрыт за неоплату. Этот тест потенциально позволит нам выявить недобросовестного провайдера, который подделывает ответы на пинги, но не обеспечивает реальный коннект.
  • Уведомления о времени даунтайма в Telegram. Они должны отправляться, как только соединение восстановится. Сообщение по-хорошему должно включать расширенную инфу о пинге и потерях пакетов после сбоя, а также состояние HTTP-клиента.
  • Доступ к роутеру. Для домашней сети с нестабильным Wi-Fi это особенно актуально. Роутер может просто упасть от перегрузки (например, очередной школохакер ломится на дырявый WPS, но вместо взлома получается DoS) или попросту не выдержать всех клиентов, которых в ином «умном доме» может быть и 15, и 20. Короче, роутер в любой момент может уйти в перезагрузку, а мы будем грешить на провайдера. Это нехорошо, поэтому при потере связи с роутером мы не будем тестировать дальше, а просто подождем, пока починят.

Цели обрисованы. Теперь детали реализации.

  • Программа предназначена для длительной работы в фоновом режиме. Оформим программу как системный сервис Windows.
  • Если мы работаем в фоновом режиме, ни консольный интерфейс, ни тем более GUI нам не нужен. Тем лучше — меньше кода.
  • Проверки не должны сильно нагружать канал, ведь будет некомфортно работать. Так что постоянно флудить пингами мы не станем. Отправим очередь из десятка пакетов раз в минуту-две, и хватит. Реже отправлять не имеет смысла — большинство неполадок устраняются в течение нескольких минут, а мы хотим знать о каждом сбое.
  • Возможность хранить отчет в JSON и выгружать CSV для изучения в Excel — с фильтрацией по дате создания.
  • Неплохо бы прикрутить возможность забирать логи по сети и скидывать статистику на центральный сервер, но в рамках демо я этого делать не буду.

Из этого следует, что нам понадобится работа с JSON. Писать я буду на C# и воспользуюсь модулем Json.NET.

WWW

Json.NET — популярная и простая библиотека для работы с JSON. Скачать ее можно с NuGet, а примеры использования лежат на сайте проекта.
 

Кодим

Для начала скачай Visual Studio с сайта Microsoft, если у тебя ее еще нет. Нужна поддержка языка C# и NuGet (с вкладки «Дополнительные компоненты»).

Первым делом создаем новый проект типа «Консольное приложение». Можно было, конечно, реализовать его в качестве «Службы Windows», тогда не нужно было бы городить костыли для регистрации нашего монитора как системной службы. Бонусом получили бы автозапуск. Жаль, что в случае «шаблонного» сервиса мы теряем ту гибкость и управляемость, что имеем при ручном управлении.

Тип проекта «Служба Windows», если решишь им воспользоваться
Тип проекта «Служба Windows», если решишь им воспользоваться

Готово. Теперь — алгоритм. Алгоритм работы программы будет прост. Во-первых, нужно прочитать настройки. Они у нас будут в файле JSON рядом с исполняемым файлом. Во-вторых, надо создать и запустить таймер, чтобы неожиданные задержки канала не мешали нам производить замеры через равные промежутки времени. И в-третьих, надо написать код сохранения результатов замеров. Поехали!

Сперва определим, что именно мы сможем настраивать. Я выбрал следующие параметры:

  • хост и порт, до которых будет проходить проверка работоспособности HTTP;
  • количество пакетов пинга и их тайм-аут;
  • задержка перед отправлением следующего пакета пинга;
  • задержка между соседними измерениями (та, которая определяет, раз в сколько минут проверка);
  • включить или выключить вывод сообщений в консоль (для отладки);
  • хосты, которые будем пинговать;
  • IP роутера (чтобы узнавать, не завис ли он). Ты спросишь, зачем отдельно IP роутера, если его можно указать в общем списке адресов для проверки, и будешь прав. Разница в том, что, если программа не обнаружит связи с роутером, остальные хосты проверяться не будут, чтобы не тратить ресурсы;
  • тайм-аут для подключения по HTTP;
  • максимальный уровень packet loss, при котором подключение считается нормальным. Мне пришлось поставить себе 10%, так как 5–7% совсем не редкость для моей деревни;
  • выходной формат строки для CSV, если ты вдруг решишь отключить вывод ненужных столбцов. Признаюсь, я уже забыл, зачем мне это понадобилось;
  • выходной файл CSV, в который будут дописываться результаты;
  • возможность отключить запись.
static String       HTTP_TEST_HOST;
static int          HTTP_TEST_PORT;
static int          HTTP_TIMEOUT;
static int          PING_COUNT;
static int          PING_DELAY;
static int          PING_TIMEOUT;
static List<String> PING_HOSTS;
static int          MEASURE_DELAY;
static String       ROUTER_IP;
static bool         CUI_ENABLED;
static double       MAX_PKT_LOSS;
static String       OUT_FILE;
static bool         WRITE_CSV;
static String       CSV_PATTERN;
static String       TG_TOKEN;
static String       TG_CHAT_ID;
static bool         TG_NOTIFY;

Думаю, нет смысла расписывать, какая переменная за что отвечает, я постарался дать им понятные названия. Если что, можешь прочитать комментарии к коду (ссылка на GitHub — в конце статьи).

С настройками разобрались, теперь добавим их загрузку. Тут все просто: читаем файл, скармливаем его Json.NET, раскладываем настройки по переменным.

Загружаем настройки
Загружаем настройки

Теперь позаботимся о выводе CSV. Поскольку строка в конфиге задает только шаблон вывода, заголовки столбцов нам придется назначить самостоятельно. А так как мы хотим знать и результаты измерений по каждому хосту из списка, нужен цикл. Ниже — часть кода, которая отвечает за формирование заголовка таблицы.

String CSV_HEADER = CSV_PATTERN
    .Replace("FTIME", "Snapshot time")
    .Replace("IUP", "Internet up")
    .Replace("AVGRTT", "Average ping (ms)")
    .Replace("ROUTERRTT", "Ping to router (ms)")
    .Replace("LOSS", "Packet loss, %")
    .Replace("MID", "Measure ID")
    .Replace("SEQ", "SeqID")
    .Replace("HTTP", "HTTP OK")
    .Replace("STIME", "STime");
foreach (var host in PING_HOSTS) {
     CSV_HEADER = CSV_HEADER.Replace("RN", $"RTT to {host};RN");
}
CSV_HEADER = CSV_HEADER.Replace("RN", ";;\r\n");

Теперь небольшое пояснение, что тут происходит. Сначала мы заменяем почти все идентификаторы в строке формата на их человекочитаемые значения. Почти — потому что RN, обозначающий конец строки, остается. Далее в цикле мы вот таким нехитрым образом дописываем новые столбцы, а под конец закрываем строку с помощью ;;\r\n и убираем RN.

Парсим аргументы и выводим справку
Парсим аргументы и выводим справку

С этим кодом и так все понятно: парсим аргументы, если их нет — выводим справку. Программа знает четыре режима работы.

  1. При запуске без аргументов. Просто выводит справку и ждет, когда пользователь ее прочитает.
  2. Запуск с -d или --daemon. Программа запускается и работает в фоновом режиме, никуда не устанавливаясь.
  3. Запуск с -m или --measure-once. Программа также не будет регистрировать сервис, но и прятать окно не будет, в отличие от второго режима. Просто для запуска портативной измерялки с флешки.
  4. Режим установки. Войти в него можно с помощью параметров -i или --install. В этом случае будет зарегистрирован сервис, а программа перезапустится как сервис в режиме 2.

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

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


7 комментариев

  1. Аватар

    Illson

    08.04.2020 в 02:09

    а можно скачать готовое решение?

  2. Аватар

    Sergo

    09.04.2020 в 13:21

    Такой себе код, конечно. Почему не сделать класс Config и не зафигачить все настройки в него, а его уже сериализовать, десериализовать?

    • Аватар

      Hackcat

      14.04.2020 в 14:10

      Сначала эта программа писалась «для себя» и не имела даже такого функционала. Естественно, сделать всё «по правилам» мне было лень, так что делал так. А потом такой код и переехал в статью, исправлять и переделывать с нуля не было времени.
      Что касается сериализации и десериализации в класс — в таком случае пришлось бы при каждом обращении к настройкам обращаться через Config., а это лишние телодвижения и уходит наглядность и простота кода.
      В любом случае, все исправления приветствуются в виде Pull request на Github

  3. Аватар

    mitrofanzzz

    12.04.2020 в 11:46

    Неплохая статья, достаточно прозрачная для не программиста. Спасибо. Я мечтал написать что-то подобное для себя, но потом понял что WinMTR мне более чем достаточно.

  4. Аватар

    zmncsgo

    24.04.2020 в 20:45

    Трудности с запуском, можно чуть более подробней, спасибо

    • Аватар

      Hackcat

      26.04.2020 в 16:51

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

Оставить мнение