Обход DPI с помощью eBPF sock_ops
- title
- Обход DPI с помощью eBPF sock_ops
- type
- summary
- summary
- Бора Танрыкулу создал gecit: eBPF sock_ops в Linux для рассинхронизации SNI в ядре и связка gVisor + TUN как подобие VPN на macOS и Windows
- tags
- networking, censorship-circumvention, dpi, ebpf, tls, linux, macos, windows
- sources
- bora-bypassing-dpi-with-ebpf
- created
- 2026-05-06
- updated
- 2026-07-29
- lang
- ru
- translation_of
- bypassing-dpi-with-ebpf
- source_updated
- 2026-07-29
- translated
- 2026-09-01
- translator
- lllm/antigravity/gemini-3.7-flash-medium
Заметка об архитектуре за конец апреля 2026 года на bora.sh про gecit - системную утилиту для обхода DPI, которая внедряет фальшивый TLS ClientHello перед настоящим, чтобы запутать проверяющие SNI мидлбоксы. Самая интересная часть статьи - не сама техника, а сравнение платформ. Для одной и той же задачи в Linux хватает пары сотен строк на eBPF, а на macOS и Windows приходится поднимать TCP-стек в userspace.
Принцип работы
При открытии TLS-соединения мидлбокс считывает SNI из ClientHello и сбрасывает сессии, если имя хоста есть в его списке. Обход в gecit устроен так: отправляются два пакета ClientHello - сначала фальшивый с SNI = www.google.com и TTL 8, затем настоящий. Фальшивка доходит до DPI-коробки на маршруте (та фиксирует "google.com" и пропускает соединение дальше), а затем сгорает по TTL, так и не дойдя до сервера. Настоящий ClientHello приходит на сервер, когда состояние соединения внутри DPI уже отравлено. Сопутствующий трюк: зажать TCP_MAXSEG до 88 байт, чтобы ядро фрагментировало настоящий ClientHello, разбив расширение SNI по сегментам - наивные парсеры DPI смотрят только на первый. См. tls-desync-fake-clienthello.
Это клиентский обход: IP-адрес назначения не меняется. Поэтому он обходит проверку SNI, но бессилен против блокировок по белым спискам CIDR - инструмент попадает в ту же категорию ортогональных утилит, что и zapret / GoodbyeDPI / ByeDPI в russia-vpn-bypass-state-2026-04.
Linux: всё решает eBPF
В Linux для обхода нужна BPF-программа типа sock_ops, прикреплённая к cgroup. Коллбэк BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB срабатывает в момент, когда исходящее TCP-соединение завершает тройное рукопожатие: связь установлена, приложение ещё ничего не отправило. Внутри коллбэка происходят три вещи:
bpf_setsockopt(skops, IPPROTO_TCP, TCP_MAXSEG, &mss, ...)зажимает MSS до 88 байт для конкретного сокета. Приложение об этом даже не знает.skops->snd_nxtиskops->rcv_nxtсчитываются напрямую из структуры сокета в ядре вместе с 5-tuple и упаковываются в perf-событие.- Горутина на Go в userspace считывает событие и отправляет фальшивый ClientHello в raw-сокет с флагом
IP_HDRINCL, тем же 5-tuple, теми же seq/ack и TTL 8.
Дальше соединение работает целиком в ядре. Основной трафик идёт без накладных расходов. Коллбэк sock_ops даёт gecit три свойства, которые ни один другой сетевой хук Linux не предоставляет одновременно: синхронный вызов на каждое соединение ровно в момент нужного перехода состояний TCP, привязку на уровне cgroup (благодаря чему перехватываются даже приложения, игнорирующие настройки прокси) и изменение состояния прямо в ядре через bpf_setsockopt. См. ebpf-sock-ops.
macOS и Windows: конструирование карманного VPN
В macOS нет eBPF. DTrace, Endpoint Security и MAC framework позволяют наблюдать, но не умеют вклинивать фиктивные TCP-пакеты в живое соединение. Network Extension подошёл бы, но требует одобренного Apple entitlement'а, что для небольшого open source проекта нереально. Первой попыткой автора был HTTP CONNECT прокси на 127.0.0.1:8443: браузеры его подхватили, Discord - нет, как и куча других программ, так что от идеи отказались.
Итоговое решение - устройство TUN со стеком TCP/IP gVisor в userspace через sing-tun. Трафик на 443 порт направляется в TUN, gVisor локально завершает рукопожатие, gecit открывает собственное соединение к реальному серверу, отправляет фальшивый ClientHello и затем пересылает настоящий. Номера последовательностей (sequence numbers) приходится вытаскивать через сниффинг SYN-ACK на физическом сетевом адаптере через pcap, поскольку macOS не отдаёт snd_nxt/rcv_nxt в userspace.
Windows использует ту же архитектуру TUN+gVisor, но с двумя дополнительными проблемами. Сырые сокеты TCP заблокированы со времён Vista, поэтому инъекция пакетов идёт через pcap_sendpacket из Npcap - а значит, gecit сам собирает полные Ethernet-фреймы, включая MAC-адрес шлюза, который приходится вытаскивать из таблицы ARP. А у стандартного для Windows драйвера обхода DPI, WinDivert, сертификат подписи кода истёк ещё в 2023 году (Defender на него ругается, некоторые системы вообще отказываются загружать), поэтому gecit взял WinTUN от WireGuard. Npcap нельзя распространять без OEM-лицензии, так что пользователям приходится ставить его вручную.
Цена на обеих не-Linux платформах одна и та же: каждый пакет теперь дважды пересекает границу ядра и userspace, тогда как в Linux через неё проходит только сама инъекция поддельного пакета. Накладные расходы точно такие же, как у VPN, только без удалённой точки выхода. Показательная цитата автора: "разница тут не плавный спуск, а ступенька. eBPF позволяет дотянуться до нужной точки в TCP-стеке ядра безо всяких лишних подпорок. На остальных платформах приходится городить целый карманный VPN ради того, с чем в ядре справляется короткая BPF-программа".
DNS, песочницы и ограничения твиков в userspace
gecit также запускает локальный DoH-сервер на 127.0.0.1:53 и перенаправляет системный резолвер на него, обеспечивая шифрованный DNS до выбранного апстрима. Проблема курицы и яйца (DoH-клиенту нужен DNS, чтобы найти адрес своего апстрима) решается жёсткой привязкой имён апстримов к IP при запуске.
Пример с Flatpak - небольшое философское отступление статьи. Каждое приложение Flatpak работает в собственной песочнице со своим DNS-резолвингом, игнорируя /etc/resolv.conf. Поэтому обход DNS от gecit не дотягивается внутрь Flatpak - однако обход DPI там всё равно работает, потому что eBPF выполняется в ядре глубже любых песочниц. Один инструмент, два механизма: работающий в ядре без проблем проходит сквозь пространства имён (namespaces), а userspace-решение так не умеет. Это наглядный контраст для размышлений о sandboxing-ai-agents и matryoshka-isolation: хуки на уровне ядра пронизывают стек контейнеров и пространств имён так, как твикам в userspace и не снилось.
Подводные камни из статьи
- Поведение DPI зависит от конкретной сети. Некоторые коробки молча сбрасывают пакеты, некоторые шлют RST. Значение TTL по умолчанию, равное 8, работает в большинстве сетей; если нет, для подстройки используют
traceroute. - Поддельные пакеты с фиктивными значениями seq/ack отбрасываются сразу. На macOS и Windows, если pcap не успевает перехватить SYN-ACK, фальшивка уходит с мусором в заголовках, и DPI её просто игнорирует.
- В macOS настройки DNS привязаны к сетевым службам (network services); gecit определяет активную службу по маршруту по умолчанию, чтобы корректно обрабатывать USB-модем (tethering).
Связанные страницы
- gecit - сама утилита
- bora-blog - блог автора
- tls-desync-fake-clienthello - концептуальное описание техники обхода
- ebpf-sock-ops - концептуальное описание хука ядра Linux
- tspu - российская система ТСПУ; рассинхронизация в стиле gecit обходит проверку SNI, но бессильна против белых списков CIDR
- russia-vpn-bypass-state-2026-04 - место этой техники в общей картине обхода блокировок (ниша ортогональных утилит, аналогично zapret / ByeDPI)
- domain-fronting - родственная техника, которая также подменяет назначение для DPI, но на уровне HTTP
- whitelist-internet-blocking - уровень блокировок, который эта техника преодолеть не способна
- little-snitch-linux - другой сетевой инструмент на базе eBPF (другие хуки, другая цель)
- profiling-ebpf-code - программа gecit выполняется на каждое исходящее соединение, и это ровно тот случай, когда нужно уметь измерять накладные расходы