Содержание статьи
Виновником появления этой статьи стал уже несколько месяцев сбоящий интернет, который мне предоставляет единственный в округе провайдер. Увы, в мою деревню ничего, кроме 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», тогда не нужно было бы городить костыли для регистрации нашего монитора как системной службы. Бонусом получили бы автозапуск. Жаль, что в случае «шаблонного» сервиса мы теряем ту гибкость и управляемость, что имеем при ручном управлении.
Готово. Теперь — алгоритм. Алгоритм работы программы будет прост. Во-первых, нужно прочитать настройки. Они у нас будут в файле 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
.
С этим кодом и так все понятно: парсим аргументы, если их нет — выводим справку. Программа знает четыре режима работы.
- При запуске без аргументов. Просто выводит справку и ждет, когда пользователь ее прочитает.
- Запуск с
-d
или--daemon
. Программа запускается и работает в фоновом режиме, никуда не устанавливаясь. - Запуск с
-m
или--measure-once
. Программа также не будет регистрировать сервис, но и прятать окно не будет, в отличие от второго режима. Просто для запуска портативной измерялки с флешки. - Режим установки. Войти в него можно с помощью параметров
-i
или--install
. В этом случае будет зарегистрирован сервис, а программа перезапустится как сервис в режиме 2.
Обработка этой несложной логики представлена на скриншоте выше. На этом подготовительная часть завершена, делаем логику измерений.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»