
Реализация TUN GSO в кастомном VPN-сервере на Java
easyJet только что Реализация TUN GSO в кастомном VPN-сервере на Java Средний 13 мин 0 Java * *nix * Сетевые технологии * Программирование * Серверная оптимизация * Как одна фича сократила число системных вызовов на...
<5 — 2026'da uzaya kaç SpaceX Starship fırlatması ulaşacak?
В сфере искусственного интеллекта произошло заметное событие. easyJet только что Реализация TUN GSO в кастомном VPN-сервере на Java Средний 13 мин 0 Java * *nix * Сетевые технологии * Программирование * Серверная оптимизация * Как одна фича сократила число системных вызовов на TUN-интерфейсе моего VPN почти вдвое — а на bulk-трафике до 44 раз. В этой статье пойдет речь о том как работает TUN GSO, зачем нужен virtio_net_hdr, какие подводные камни встретились во время реализации и почему эта технология способна заметно снизить нагрузку на VPN-сервер. Статья будет полезна разработчикам VPN серверов и клиентов.
Основной язык проекта — Java, поэтому здесь и далее все примеры кода приводятся на Java. Синтаксис у неё C-подобный, так что опытному разработчику не составит труда переложить идею на свой язык. Коротко об архитектуреМой VPN-сервер написан на Java.
Технические детали
Основа сетевого стека — Java NIO, которая по сути является тонкой обёрткой над платформенными механизмами мультиплексирования ввода-вывода (epoll в Linux и kqueue в BSD/macOS). Модель работы — классический Reactor: несколько I/O-потоков ожидают события OP_ACCEPT, OP_READ и OP_WRITE. Аналогичный подход, например, используется в NGINX.
После получения события реактор читает данные из сокета и помещает их в очередь. Далее виртуальный поток (sslWorker) извлекает пакеты, выполняет обработку TLS через SSLEngine и перекладывает уже расшифрованные данные в следующую очередь. Оттуда они отправляются в /dev/net/tun.
В обратном направлении путь практически симметричный: пакет читается из TUN-интерфейса, шифруется и отправляется клиенту. На первый взгляд архитектура выглядит вполне эффективно. Однако узкое место появляется именно во взаимодействии с TUN-интерфейсом.
Отраслевые последствия
Обычный IP-пакет имеет размер порядка 1500 байт (точнее, ограничен MTU канала). Это означает, что когда через TUN проходит около 64 КБ данных одного TCP-потока, они приходят уже нарезанными примерно на 44 сегмента — и пользовательскому приложению достаётся не один большой пакет, а несколько десятков маленьких. Для передачи этих 64 КБ потребуется примерно:44 вызова read() или write();44 перехода между пользовательским пространством и ядром (user → kernel → user);44 копирования данных между пространствами памяти;44 создания и обработки сетевых буферов (skb) внутри ядра;44 прохода через обработчики сетевого стека.
Одна и та же нагрузка без GSO и с TUN GSOРис. Одна и та же нагрузка без GSO и с TUN GSO: 44 системных вызова против одного. Сама операция чтения или записи не кажется дорогой.
Но когда счёт пакетов идёт на миллионы, накладные расходы начинают доминировать над полезной работой. Процессор всё больше времени тратит не на шифрование TLS или обработку сетевого трафика, а на обслуживание большого количества системных вызовов и переключений контекста. Именно здесь возникает закономерный вопрос: а обязательно ли передавать через TUN каждый TCP-сегмент по отдельности?
Этот прогресс даёт важные сигналы о будущем отрасли, и технологический мир внимательно наблюдает.





