From e996e09f6e2c9e1aa72d9765377bc1b77aaa1837 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 17:39:34 +0200 Subject: [PATCH] Specify dummynet datagram timing reconfiguration and filter ownership contracts --- src/transport_layer/dummynet.h | 61 ++++++++++++++++++++--------- src/transport_layer/dummynet_doc.md | 44 +++++++++++++++++---- 2 files changed, 80 insertions(+), 25 deletions(-) diff --git a/src/transport_layer/dummynet.h b/src/transport_layer/dummynet.h index b769595b..dc9972e9 100644 --- a/src/transport_layer/dummynet.h +++ b/src/transport_layer/dummynet.h @@ -1,22 +1,25 @@ /** * @file dummynet.h - * @brief UDP dummynet эмулятор с задержкой, bandwidth limiting и случайными потерями + * @brief Двунаправленный IPv4 UDP-эмулятор и inline-фильтр потерь для ETCP. * - * Эмулирует сетевые условия: задержку (fixed + random), ограничение пропускной способности - * и случайные потери пакетов. Работает как "человек посередине" - принимает пакеты на - * listen_port и пересылает между двумя явно настроенными endpoint с эмуляцией условий канала. + * FULL: create(ua, bind_ip, port), set_direction для обоих различных IP:порт, затем poll(ua). + * Эмулирует задержку fixed+random, пропускную способность и случайные потери. + * Принимает датаграммы 0..1500 байт; более длинные отбрасывает целиком. Только IPv4-литералы. + * Неизвестные источники не пересылаются и не входят в статистику направлений. * * Архитектура: - * [UDP recv] -> [check queue/drop] -> [delay timer] -> [ll_queue] -> [shaper timer] -> [UDP send] - * ^ - * loss check + * UDP recv -> size/DROP/loss/queue limit -> delay timer -> очередь -> shaper timer -> UDP send * * Поток данных: * - Слушаем на bind_ip:listen_port * - Пакеты от dest_backward -> отправляем на dest_forward (forward direction) * - Пакеты от dest_forward -> отправляем на dest_backward (backward direction) * - Оба направления настраиваются до начала трафика; IP и порты участников независимы - * - Каждое направление имеет свою очередь и параметры эмуляции + * - Каждое направление имеет свою очередь и параметры эмуляции; random delay может менять порядок пакетов. + * INLINE: filter_create/attach вставляет фильтр потерь и блокировок в один свободный UDP send_hook. + * Он не создаёт сокета, не добавляет задержки/BWlim; закрытие линка автоматически отвязывает фильтр. + * Все вызовы — в потоке ua. ua должен жить до destroy; статистика заимствована до destroy. + * Потери используют rand(): seed задаёт вызывающий. Подробнее — dummynet_doc.md. */ #ifndef DUMMYNET_H @@ -60,13 +63,13 @@ enum dummynet_mode { * @brief Статистика направления */ struct dummynet_stats { - uint64_t recv; // Принято пакетов + uint64_t recv; // FULL: пакеты распознанного направления, включая слишком большие; INLINE: вызовы hook uint64_t sent; // Отправлено пакетов - uint64_t dropped; // Переполнение очереди, отказ выделения/таймера или ошибка отправки + uint64_t dropped; // Слишком большой пакет, переполнение очереди, отказ выделения/таймера или ошибка отправки uint64_t lost; // Случайные потери и DROP uint64_t recv_bytes; // Принято UDP-байт, без IP/UDP заголовков - uint64_t sent_bytes; // Передано UDP-байт, включая протокольные накладные расходы ETCP - uint64_t blocked; // Пакетов отброшено режимом DROP (включены в lost) + uint64_t sent_bytes; // Передано UDP-байт, без IP/UDP заголовков; пустой пакет увеличивает sent, но не sent_bytes + uint64_t blocked; // FULL: режим DROP; INLINE: блокировка IP:порта; включены в lost uint64_t max_queue_delay_us; // максимум времени до отправки минус заданная propagation delay; включает сериализацию/HOLD uint32_t queue_size; // Все ожидающие пакеты, включая delay и HOLD uint32_t queue_max; // Максимальный размер очереди (пиковый) @@ -90,7 +93,8 @@ struct dummynet* dummynet_create(struct UASYNC* ua, const char* bind_ip, uint16_ /** * @brief Уничтожает dummynet контекст * - * Освобождает все ресурсы, закрывает сокет, отменяет таймеры. + * Освобождает пакеты, очереди и пул, закрывает сокет, отменяет все delay/shaper-таймеры. + * ua не освобождается. NULL допустим; после вызова контекст и указатели на статистику недействительны. * * @param dn Контекст dummynet */ @@ -99,6 +103,12 @@ void dummynet_destroy(struct dummynet* dn); /** * @brief Настраивает параметры направления * + * Ошибка сохраняет прежнюю конфигурацию целиком. Сумма fixed+random <= 10000 мс; + * max_queue_pkts <= INT_MAX. loss > 1000 ограничивается до 1000 с WARN. + * Одинаковые endpoints и dest_ip=0.0.0.0 запрещены. Смена endpoint требует пустых обоих направлений. + * Остальные параметры можно менять с очередью: delay/loss применяются к новым входящим пакетам, + * BW — к следующей сериализации (уже начатая завершается по прежнему сроку), лимит — к новым поступлениям. + * * @param dn Контекст dummynet * @param direction Направление: DUMMYNET_FORWARD или DUMMYNET_BACKWARD * @param delay_fixed_ms Фиксированная задержка (мс) @@ -134,7 +144,8 @@ const struct dummynet_stats* dummynet_get_stats(struct dummynet* dn, int directi * @brief Сбрасывает статистику направления * * @param dn Контекст dummynet - * @param direction Направление (-1 для всех направлений) + * @param direction 0/1 или -1 для обоих. Текущий queue_size сохраняется, queue_max становится равным ему; + * отладочные счётчики таймеров не сбрасываются. Остальные значения/NULL логируются без изменения состояния. */ void dummynet_reset_stats(struct dummynet* dn, int direction); @@ -167,7 +178,7 @@ struct UASYNC *dummynet_get_uasync(struct dummynet *dn); * * @param dn Контекст dummynet * @param direction Направление - * @return Размер очереди или -1 + * @return Число всех ожидающих пакетов (delay + очередь + сериализация/HOLD) или -1 */ int dummynet_get_queue_size(struct dummynet *dn, int direction); @@ -176,7 +187,8 @@ int dummynet_get_queue_size(struct dummynet *dn, int direction); * * @param dn Контекст dummynet * @param direction Направление - * @param delay_set Выход: число установок delay таймеров + * Выходы необязательны (NULL пропускается); неверный контекст/направление даёт нули с WARN. + * @param delay_set Выход: число успешных установок delay таймеров * @param delay_fire Выход: число срабатываний delay таймеров * @param shaper_set Выход: число установок shaper таймеров * @param shaper_fire Выход: число срабатываний shaper таймеров @@ -185,18 +197,31 @@ void dummynet_get_debug_counters(struct dummynet *dn, int direction, uint64_t *delay_set, uint64_t *delay_fire, uint64_t *shaper_set, uint64_t *shaper_fire); -// === Inline filter: встраивается в etcp_udp_send через link->send_hook === -// Не создаёт свой сокет — перехватывает пакеты напрямую из пути отправки. +/* INLINE: перехват native UDP ETCP_LINK. SOCKS5 и TCP не поддерживаются. + * Потеря возвращает len как успешную отправку; фактически socket_sendto не вызывается. + * Реальная ошибка отправки возвращается вызывающему и учитывается в dropped. + * Размер UDP здесь задаёт ETCP; лимит 1500 относится только к FULL. */ struct ETCP_LINK; struct dummynet_filter; +/* Создать фильтр для ua (обязателен); NULL при ошибке. ua заимствован, сокетов/таймеров у фильтра нет. */ struct dummynet_filter* dummynet_filter_create(struct UASYNC* ua); +/* Установить вероятность 0..1000; превышение ограничивается до 1000 с WARN. */ void dummynet_filter_set_loss(struct dummynet_filter* df, uint32_t loss_permille); +/* Присоединить к живому native UDP link того же ua; 0/-1. Занятый hook не заменяется. + * Повтор к своему link идемпотентен. При переходе к другому прежний отвязывается после проверки нового. + * Фильтр не владеет линком; link close отвязывает фильтр, сохраняя сам фильтр/статистику для повторного attach. */ int dummynet_filter_attach(struct dummynet_filter* df, struct ETCP_LINK* link); +/* Снять свой hook; NULL/уже отвязанный допустимы. При внешней подмене сохранить чужие fn/ctx и выдать WARN. */ void dummynet_filter_detach(struct dummynet_filter* df); +/* Отвязать и освободить фильтр; NULL допустим. Линк/ядро могли быть уже уничтожены. */ void dummynet_filter_destroy(struct dummynet_filter* df); +/* Заимствованная статистика или NULL. INLINE: queue_size/queue_max/max_queue_delay_us всегда 0. */ const struct dummynet_stats* dummynet_filter_get_stats(struct dummynet_filter* df); +/* Добавить/снять блокировку IPv4 IP:порта в network byte order, оба ненулевые. + * 0 — выполнено/повтор, -1 — неверный аргумент или заполнены 4 различных слота (только block). + * Block проверяется до случайной потери. Отсутствующий unblock идемпотентен. */ int dummynet_filter_block_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port); int dummynet_filter_unblock_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port); diff --git a/src/transport_layer/dummynet_doc.md b/src/transport_layer/dummynet_doc.md index b730fdde..f068d70b 100644 --- a/src/transport_layer/dummynet_doc.md +++ b/src/transport_layer/dummynet_doc.md @@ -10,6 +10,18 @@ 1. `dummynet_create(ua, bind_ip, listen_port)` — создаёт контекст, открывает UDP-сокет на `listen_port`. 2. `dummynet_set_direction(dn, DUMMYNET_FORWARD, ...)` и `DUMMYNET_BACKWARD` — настраивает параметры для каждого направления. 3. Источник FORWARD — endpoint, заданный как назначение BACKWARD, и наоборот. Порты независимы; `listen_port=0` выбирает ОС. +4. Вызовы выполняются в потоке `ua`; `ua` должен существовать до `dummynet_destroy`. Destroy освобождает только собственные ресурсы. + +FULL поддерживает IPv4-литералы и датаграммы **0..1500 байт**. Пустой UDP-пакет передаётся как пакет; +более длинный отбрасывается целиком с WARN, без пересылки усечённого содержимого. +Неизвестный source IP:порт не пересылается и не входит в статистику направлений. + +Настройка атомарна: ошибка сохраняет прежние параметры и адрес. Endpoints должны различаться, +`dest_ip=0.0.0.0` и нулевой порт запрещены. Сумма fixed+random ограничена 10000 мс, очередь — `INT_MAX` пакетов; +превышение даёт -1. Loss выше 1000 ограничивается до 1000 с WARN. +Смена endpoint разрешена при пустых обоих направлениях. При сохранении endpoint параметры можно менять с трафиком: +delay/loss применяются к новым поступлениям, BW — к следующей сериализации, лимит — к приёму новых пакетов. +Уже запущенный delay и начатая сериализация сохраняют свои сроки; уменьшение лимита не удаляет накопленное. ``` [dest_backward] → [dummynet: loss/queue limit→delay→serializer] → [dest_forward] @@ -19,9 +31,18 @@ ### Режим INLINE (send_hook в ETCP_LINK) 1. `dummynet_filter_create(ua)` — создаёт фильтр (нет своего сокета). -2. `dummynet_filter_set_loss(df, permille)` — настройка потерь. `set_delay` пока только сохраняет параметры. -3. `dummynet_filter_attach(df, link)` — встраивается в `link->send_hook`, перехватывая все вызовы `socket_sendto`. -4. Дополнительно: `dummynet_filter_block_addr/unblock_addr` — блокировка отправки на конкретные адреса. +2. `dummynet_filter_set_loss(df, permille)` — настройка потерь; 0..1000, превышение ограничивается с WARN. +3. `dummynet_filter_attach(df, link)` — 0/-1, присоединяет к одному живому native UDP-линку того же `ua`. + TCP/SOCKS5 отклоняются; занятый hook не заменяется. Повтор к своему линку идемпотентен. +4. `dummynet_filter_block_addr/unblock_addr` — 0/-1, IP и порт в **network byte order**, до 4 различных пар. + Повторное добавление и удаление отсутствующей пары дают 0. Переполнение списка логируется и даёт -1. +5. `dummynet_filter_detach/destroy` снимает только свой hook. Закрытие ETCP_LINK само отвязывает фильтр + через `send_hook_release`, очищая все поля hook до callback. Фильтр можно уничтожить после линка/ядра + или присоединить к новому линку. При ручной внешней замене hook сначала следует выполнить detach. + +INLINE не создаёт сокетов/таймеров и не эмулирует задержку/BW. Не работавший `filter_set_delay` удалён. +Фильтр не владеет `ua`/линком; заимствованная статистика действительна до destroy фильтра. +SOCKS5 имеет приоритет перед send_hook в общем пути UDP, поэтому attach к нему запрещён. ### Механизмы эмуляции @@ -39,6 +60,11 @@ `dropped` — переполнение/ошибка ресурсов или отправки, `lost` — случайные потери и DROP, `blocked` — только DROP. `queue_size`/`queue_max` учитывают все ожидающие пакеты, включая delay/HOLD. Сброс статистики сохраняет текущий размер очереди. Дополнительные отладочные счётчики: `delay_timer_set/fire`, `shaper_timer_set/fire`. +FULL учитывает в recv/recv_bytes также целиком прочитанные слишком большие датаграммы и относит их к dropped. +INLINE учитывает все вызовы hook и их байты; блок адреса входит в lost и blocked, ошибка socket_sendto — в dropped. +Пустой пакет увеличивает recv/sent, не увеличивая счётчики байтов. Queue/delay-поля у INLINE всегда нулевые. +Указатели на статистику заимствованы. Необязательные выходы get_debug_counters могут быть NULL; +для неверного контекста/направления запрошенные выходы обнуляются с WARN. ### Ключевые нюансы - Оба направления нужно настроить до начала трафика; источник проверяется по IP и порту. @@ -46,7 +72,12 @@ - Таймеры uasync имеют миллисекундную точность: несколько готовых пакетов могут выйти вместе; средняя скорость сохраняется. - При достижении `max_queue_pkts` новый пакет отбрасывается. Лимит включает ещё не сработавшие delay-таймеры. - Все операции — в потоке ua. Destroy отменяет также delay-таймеры, освобождает их аргументы и пакеты. -- INLINE-фильтр в режиме блокировки адреса не дропает пакет, а возвращает `len` (пакет «успешно отправлен», но фактически никуда не ушёл). +- Ошибка delay-таймера теряет текущий пакет; ошибка shaper-таймера теряет голову очереди и продолжает обработку остальных. + Отказы памяти/таймеров/отправки логируются и не завершают процесс. Частичный create откатывает ресурсы. +- INLINE при случайной потере/блокировке возвращает `len`: транспорт считает отправку успешной, фактической отправки нет. +- Потери и jitter используют `rand()`; `srand()` задаёт вызывающий. Случайная задержка может переставить пакеты. +- Категория диагностики — `etcp`. INFO — создание/настройка/режим/уничтожение, DEBUG — пакеты/потери/очередь, + WARN/ERROR — неверные параметры, переполненные блокировки и ошибки ресурсов/отправки. ## 3. API @@ -72,8 +103,7 @@ |---------|------------| | `dummynet_filter_create(ua)` | Создать фильтр | | `dummynet_filter_set_loss(df, permille)` | Установить вероятность потерь | -| `dummynet_filter_set_delay(df, delay_ms, jitter_ms)` | Установить задержку (NOTE: в текущей реализации delay не применяется в хуке) | -| `dummynet_filter_attach(df, link)` | Прикрепить хук к ETCP_LINK | +| `dummynet_filter_attach(df, link)` | Прикрепить хук к свободному native UDP ETCP_LINK, 0/-1 | | `dummynet_filter_detach(df)` | Открепить хук | | `dummynet_filter_destroy(df)` | Уничтожить фильтр | | `dummynet_filter_get_stats(df)` | Получить статистику | @@ -87,7 +117,7 @@ | `dummynet_stats` | Счётчики пакетов/байтов, потерь и размера очереди; точные поля — в dummynet.h | | `dummynet_dir` | Внутренняя: параметры направления, очередь, таймеры шейпера, отладочные счётчики | | `dummynet` | Контекст FULL-режима: ua, сокет, два `dummynet_dir`, пул пакетов | -| `dummynet_filter` | Контекст INLINE-режима: ua, loss_permille, delay, link, статистика, список блокировок | +| `dummynet_filter` | Контекст INLINE-режима: ua, loss_permille, link, статистика, список блокировок | ### Зависимости `u_async.h`, `ll_queue.h`, `memory_pool.h`, `socket_compat.h`, `etcp_connections.h` (для ETCP_LINK), `platform_compat.h`