Files
utun2/src/tun_if_doc.md
T
evgeny 05b0927619 Fix TUN packet ownership, lifecycle and backend consistency
Unify TUN initialization and raw-IP writes, preserve socketpair directions, and bound reader delivery with queue backpressure. Make deferred packet ownership safe across close and uasync teardown, and apply per-interface MTU limits to lwIP MSS.

Validation: full check.sh passed 172 tests, 9 proxy integration checks, burst and load. TUN ASAN/UBSAN regressions passed 30 cases; TCP exchange passed with asymmetric and minimum MTUs. Sanitized NAT integration exposed an existing unaligned uint16_t read in eim_nat.c; that separate defect remains unchanged. Native Windows and FreeBSD validation remains outstanding.
2026-10-21 21:19:42 +02:00

8.3 KiB
Raw Blame History

TUN Interface (tun_if)

TUN связывает IP-трафик ОС с ядром. Все операции с интерфейсом, очередями, пулом и публичными счётчиками выполняются в потоке его UASYNC. tun_close() вызывается вне callbacks TUN, после остановки внешних пользователей и до уничтожения uasync.

Форматы и направления

API Направление Формат
output_queue ОС → ядро префикс(1) + IP
input_queue ядро → ОС префикс(1) + IP
tun_write(tun, buf, len) ядро → ОС чистый IP
tun_inject_packet(tun, buf, len) тест → output_queue чистый IP
tun_read_packet(tun, buf, capacity) тест читает доставленное ОС чистый IP

input_queue снимает префикс и вызывает общий tun_write(). NAT и TCP proxy пользуются тем же API. Платформенный tun_platform_write() вызывается только модулем TUN. tun_write() сохраняет владение buf у вызывающего и возвращает len только после полной записи, иначе -1 с errno. Неполная запись считается ошибкой. Пакет, отклонённый queue_data_put(), уже освобождён очередью.

Создание и MTU

tun_init(ua, config) создаёт основной IPv4 TUN с MTU 1500. tun_init_nat(ua, name, ip, mtu, test_mode) создаёт отдельный интерфейс для NAT или TCP proxy: ip — IPv4 без CIDR, MTU — 68..1500. Обе функции используют общий путь создания, регистрации чтения и отката; возвращают NULL при любой ошибке запуска. Платформенный init сам освобождает ресурсы при ошибке, успешный init передаёт cleanup общему коду. Длинные имена, неверный IPv4 и неподдерживаемый MTU отклоняются.

[global] mtu управляет ETCP, поэтому основной и NAT TUN используют TUN_MTU_DEFAULT. TCP proxy передаёт собственный настроенный MTU и до создания PCB вызывает lwip_tcp_set_mtu(): локальный предел MSS равен IPv4 MTU минус 40 байт. Он ограничивает начальный MSS, объявление в SYN и MSS, согласованный с пиром.

Чтение и backpressure

На Linux/FreeBSD fd зарегистрирован в uasync. Перед чтением регистрация приостанавливается; после передачи пакета reader ждёт пустую output_queue через queue_waiter_wait(). Затем чтение разрешается снова. Синхронный consumer допустим. tun_inject_packet() копирует пакет, возвращает 0 при успехе и -1 при ошибке; если output_queue занята, возвращает EAGAIN без передачи пакета.

Windows использует Wintun: session, заимствованный read event, stop event, auto-reset read permit и read thread. Оба конструктора проверяют read event и создают reader до сообщения об успешном запуске. Worker ожидает данные и разрешение отдельно; stop event прерывает оба ожидания. Следующий пакет разрешается после освобождения output_queue, поэтому поток не накапливает задачи в uasync.

Worker не обращается к LL_QUEUE и пулу. Один пакет передаётся через tun_post_packet() и uasync_post_reserved(). Пакет и оболочка принадлежат ожидающей передаче; posted_task — первый член оболочки, которую освобождает uasync. Указатель ожидающей передачи и ошибки worker защищены mutex. Callback снимает ожидающую передачу, переносит ошибки в общую статистику и передаёт пакет очереди.

Тестовый backend

test_mode=1 поддерживается только на POSIX, прав на устройство ОС не требует. Оба конца socketpair(AF_UNIX, SOCK_DGRAM) неблокирующие. Заимствованный tun_get_test_fd() живёт до tun_close(); закрывать его тестам нельзя.

  • send(test_fd, ip, len, 0) моделирует исходящий IP от ОС и запускает обычный reader.
  • Запись через tun_write()/input_queue доступна через recv(test_fd, ...) либо helper.
  • tun_read_packet() возвращает размер чистого IP, 0 при пустом сокете, -1 при ошибке. При недостаточной ёмкости возвращает EMSGSIZE и сохраняет пакет для следующего чтения. Helper и прямой recv требуют одного последовательного consumer.

Тестовые сокеты обходят платформенный TUN read/write. В частности, FreeBSD AF-заголовок в socketpair не добавляется и не снимается.

Закрытие, статистика и диагностика

Закрытие выставляет closing, останавливает и присоединяет Windows worker, освобождает пакет ожидающей передачи и обнуляет её ссылку на TUN. Её оболочку позже освобождает uasync: callback становится пустым, а destroy без callback также освобождает блок. Это безопасно и для задачи из уже снятой uasync порции; исполнение чужих задач ради очистки не требуется.

Затем отменяется read waiter и регистрация fd, снимаются callbacks и освобождаются элементы очередей, очереди и пул, закрывается backend. Для test_mode закрываются оба fd обычным close, без удаления интерфейса ОС. Wintun session закрывается после join; заимствованный read event отдельно не закрывается. Внешние владельцы waiters должны отменять их перед закрытием TUN.

Счётчики bytes учитывают чистый IP: read — пакеты, принятые output_queue, write — полностью записанные пакеты. Helper их не меняет. Ошибки worker переносятся при доставке следующего пакета либо при закрытии. Категория tun: INFO — запуск и итог закрытия, DEBUG — направление/размер/сводка IP, TRACE — разрешение чтения, WARN/ERROR — некорректные пакеты, отказы памяти/очереди и системных операций.

test_tun_if проверяет направления, границы helper, освобождение повреждённых элементов, очередь с отказом, паузу reader, fd, откаты init и post lifetime на реальном uasync. test_nat_transport проверяет полный socketpair roundtrip. test_lwip_tcp 12345 1200 и test_lwip_tcp 12345 68 проверяют TCP-обмен с разными MTU, объявления MSS и размеры каждого выходного IP-пакета. Нативный Wintun требует отдельной проверки на Windows.