Ни единого разрыва. Пишем на C# утилиту для мониторинга сети

Если тебе надоели постоянные обрывы связи и косяки провайдера, но субъективные оценки типа «подвисает» не внушают доверия, лучший выбор — записать состояние сети в автоматическом режиме. Причем для этого необязательно гонять 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», если решишь им воспользоваться

Готово. Теперь — алгоритм. Алгоритм работы программы будет прост. Во-первых, нужно прочитать настройки. Они у нас будут в файле 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.

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

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

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

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

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

    • Конечно, оно есть в репозитории к статье

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

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

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

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

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

Похожие материалы