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.
8.3 KiB
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.