eBPF sock_ops
- title
- eBPF sock_ops
- type
- concept
- summary
- BPF-хук ядра Linux на уровне cgroup для жизненного цикла TCP-сокетов: срабатывает при SYN-sent, established, ретрансмитах и других переходах
- tags
- linux, ebpf, kernel, networking, tcp
- created
- 2026-05-06
- updated
- 2026-07-29
- lang
- ru
- translation_of
- ebpf-sock-ops
- source_updated
- 2026-07-29
- translated
- 2026-09-01
- translator
- lllm/antigravity/gemini-3.7-flash-medium
sock_ops - это тип BPF-программ, который подключается к конечному автомату TCP-сокетов в ядре Linux. BPF-программа привязывается к cgroup, и ядро вызывает её в строго определённые моменты жизненного цикла каждого TCP-сокета. Программа выполняется прямо в ядре, а userspace получает уведомления через perf-события или BPF map'ы, если ему нужно знать о произошедшем.
Именно этот хук использует gecit, чтобы внедрить поддельный ClientHello до того, как приложение отправит какие-либо данные. В статье прямо подчёркивается: благодаря этому хуку реализация под Linux занимает всего пару сотен строк, тогда как версии под macOS и Windows превращаются, по сути, в миниатюрные VPN.
Коллбэки жизненного цикла
Ядро вызывает прикреплённую sock_ops-программу, передавая ей struct bpf_sock_ops, где поле op указывает на сработавшее событие. Вот те из них, что важны для обхода блокировок и управления соединениями:
BPF_SOCK_OPS_TCP_CONNECT_CB- исходящий SYN вот-вот будет отправлен.BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB- трёхстороннее рукопожатие для исходящего соединения только что завершилось. Именно за него цепляется gecit: соединение установлено, приложение ещё ничего не успело отправить.BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB- то же самое, но для входящих (принятых) соединений.BPF_SOCK_OPS_HDR_OPT_LEN_CB/BPF_SOCK_OPS_WRITE_HDR_OPT_CB- позволяют программе добавлять свои TCP-опции в исходящие пакеты.BPF_SOCK_OPS_RTO_CB,BPF_SOCK_OPS_RETRANS_CB,BPF_SOCK_OPS_STATE_CB- коллбэки на аномалии в работе TCP (используются для телеметрии внутри ядра, тюнинга контроля перегрузки и тому подобного).
Что умеет делать программа
Изнутри коллбэка программа имеет прямой доступ к полям сокета, которые из userspace читать либо слишком накладно, либо вовсе невозможно:
skops->snd_nxt,skops->rcv_nxt- номера sequence и ack в точности в том виде, в каком их видит ядро. gecit считывает их и передаёт в userspace через perf-событие, чтобы поддельный пакет содержал корректное состояние TCP.skops->local_ip4,skops->remote_ip4,skops->local_port,skops->remote_port- элементы 5-tuple.bpf_setsockopt(skops, IPPROTO_TCP, TCP_MAXSEG, ...)- изменение опций сокета для конкретного соединения прямо из ядра. gecit занижает MSS до 88, чтобы принудительно фрагментировать ClientHello; другие программы используют этот вызов для настройки ECN, алгоритмов контроля перегрузки или начального окна для каждого отдельного потока.
Почему этот хук настолько удобен
Здесь сходятся три свойства, которые не даёт ни один другой сетевой хук в Linux:
- Синхронный коллбэк на каждое соединение. XDP и TC срабатывают на пакеты, а не на соединения. BPF-хуки cgroup'ов на
connect(2)вызываются слишком рано (сокет ещё не установлен, номера ack ещё нет).sock_ops- единственный хук, который срабатывает ровно в тот момент, когда соединение становится готово к передаче данных. - Привязка к cgroup. Программа отрабатывает для любого процесса внутри прикреплённого cgroup'а, независимо от того, как у него устроена работа с сетью в userspace. Программе gecit не требуется, чтобы приложения учитывали настройки прокси: Discord, Steam или любой другой процесс в cgroup'е обрабатываются одинаково.
- Мутация состояния прямо в ядре. Установка MSS через
bpf_setsockoptиз коллбэка меняет поведение сегментации в ядре только для этого конкретного сокета. В userspace для этого пришлось бы перехватывать системный вызов, что хрупко и требует настройки под каждое приложение.
Сравнение с другими платформами
Ключевая мысль статьи: "разница здесь не плавный спуск, а резкая ступенька". На macOS и Windows инструменту gecit требуются TUN-устройство, userspace-стек TCP от gVisor, инъекция через raw-сокеты (или pcap на Windows) и парсинг ARP-таблицы - всё это в userspace и с отдельной реализацией под каждую ОС. Каждый пакет вынужден пересекать границу между ядром и пользователем. На Linux же userspace затрагивается только в момент самой поддельной инъекции, а весь основной трафик остаётся внутри ядра.
Это постоянный сюжет в низкоуровневой сетевой разработке: у BSD есть dtrace + pf + pcap, у macOS - Network Extensions (требующие специальных entitlement'ов), у Windows - WFP / NDIS / WinDivert. Но ни одна из них не даёт синхронного коллбэка на состояние TCP внутри ядра, который можно было бы безопасно привязать к пользовательскому cgroup'у. eBPF здесь меняет правила игры.
Связанные страницы
- tls-desync-fake-clienthello - техника обхода, для реализации которой gecit использует sock_ops
- bypassing-dpi-with-ebpf - статья, которая послужила поводом для этой страницы
- gecit - конкретный пример использования
sock_opsдля обхода блокировок по SNI - little-snitch-linux - другой сетевой инструмент на базе eBPF (фильтрация и мониторинг трафика приложений), использующий другие хуки
- profiling-ebpf-code - как измерить накладные расходы такой программы на горячем пути; метод с perf и kallsyms подходит для
sock_opsточно так же, как и для LSM-хуков