EnglishРусский Map

Обход 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
created
2026-05-06
updated
2026-07-29
lang
ru
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-соединение завершает тройное рукопожатие: связь установлена, приложение ещё ничего не отправило. Внутри коллбэка происходят три вещи:

  1. bpf_setsockopt(skops, IPPROTO_TCP, TCP_MAXSEG, &mss, ...) зажимает MSS до 88 байт для конкретного сокета. Приложение об этом даже не знает.
  2. skops->snd_nxt и skops->rcv_nxt считываются напрямую из структуры сокета в ядре вместе с 5-tuple и упаковываются в perf-событие.
  3. Горутина на 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 выполняется на каждое исходящее соединение, и это ровно тот случай, когда нужно уметь измерять накладные расходы