путь пакета от провода до сокета, и три точки, где его можно перехватитьa packet's path from the wire to a socket, and three points where it can be intercepted
Каждый входящий пакет проходит одну и ту же последовательность шагов от сетевой карты до приложения — и на этом пути есть ровно три официальных места, где можно вмешаться: XDP на самом раннем этапе, netfilter/nftables на уровне IP, и обычный socket API на уровне приложения. Firewall-правило, eBPF-программа и io_uring-сокет — это не альтернативные технологии, а разные точки на одном и том же маршруте.Every incoming packet goes through the same sequence of steps from the network card to the application — and there are exactly three official points along that path where you can intervene: XDP at the earliest stage, netfilter/nftables at the IP layer, and the regular socket API at the application layer. A firewall rule, an eBPF program, and an io_uring socket aren't competing technologies — they're different points on the same route.
Сетевой стек ядра — не единый монолитный компонент, а последовательность уровней, через которые проходит каждый пакет: драйвер сетевой карты (принимает кадр из провода), NAPI (пакетный опрос вместо прерывания на каждый кадр под нагрузкой), выделение sk_buff (структура, представляющая пакет внутри ядра), демультиплексирование по Ethernet, точки перехвата netfilter, маршрутизация по IP, разбор TCP/UDP, и наконец очередь приёма конкретного сокета, откуда пакет читает приложение. Каждый уровень решает одну конкретную задачу и передаёт пакет дальше — либо отбрасывает его.The kernel's networking stack isn't one monolithic component — it's a sequence of layers every packet passes through: the network card driver (receives the frame off the wire), NAPI (packet polling instead of an interrupt per frame under load), sk_buff allocation (the struct representing a packet inside the kernel), Ethernet demultiplexing, netfilter interception points, IP routing, TCP/UDP parsing, and finally a specific socket's receive queue, where the application reads the packet from. Each layer does one specific job and either passes the packet along or drops it.
Путь входящего пакета и три точки, где его можно перехватить до того, как он дойдёт до приложения:An incoming packet's path, and the three points where it can be intercepted before reaching the application:
| точкаpoint | когда срабатываетwhen it fires | для чего используетсяwhat it's used for |
|---|---|---|
| XDP | до выделения sk_buff, в native-режиме — прямо в драйвере, до какой-либо обработки ядромbefore sk_buff allocation; in native mode — right in the driver, before any kernel processing | отбрасывание DDoS-трафика на скорости провода, балансировка нагрузки, кастомная маршрутизация — там, где важна минимальная задержка и цена системного вызова недопустимаdropping DDoS traffic at wire speed, load balancing, custom routing — anywhere minimal latency matters and the cost of a syscall is unacceptable |
| netfilter / nftables | после выделения sk_buff, на уровне IP — несколько именованных хуков (PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING)after sk_buff allocation, at the IP layer — several named hooks (PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING) | firewall-правила, NAT, connection tracking — обычная фильтрация и трансляция адресов на уровне пакета/соединенияfirewall rules, NAT, connection tracking — ordinary packet/connection-level filtering and address translation |
| socket API / io_uring | после полного разбора TCP/UDP, уже в контексте конкретного приложенияafter full TCP/UDP parsing, already in a specific application's context | обычное чтение/запись данных приложением; io_uring добавляет асинхронный путь без syscall на каждую операциюordinary data read/write by the application; io_uring adds an async path with no syscall per operation |
Ключевое архитектурное следствие: чем раньше точка перехвата, тем меньше накладных расходов ядра пройдено к моменту решения, но и тем меньше контекста доступно программе (в XDP ещё не существует концепции TCP-соединения — только сырые байты кадра). NAPI решает отдельную, более общую задачу — не о том, где перехватывать пакет, а о том, как не захлебнуться под нагрузкой: при высокой частоте кадров ядро переключается с обработки по прерыванию на каждый кадр на пакетный опрос (polling), иначе система тратила бы всё процессорное время на обработку самих прерываний, а не пакетов.The key architectural consequence: the earlier the interception point, the less kernel overhead has been spent by decision time, but also the less context is available to the program (XDP doesn't even have the concept of a TCP connection yet — just raw frame bytes). NAPI solves a separate, more general problem — not where to intercept a packet, but how not to choke under load: at high frame rates the kernel switches from per-frame interrupt handling to batched polling, otherwise the system would spend all its CPU time servicing the interrupts themselves rather than the packets.
| командаcommand | что происходитwhat happens |
|---|---|
| nft add rule inet filter input drop | добавляет правило в хук INPUT — сработает уже после выделения sk_buff и routing lookup, на уровне IPadds a rule to the INPUT hook — fires after sk_buff allocation and the routing lookup, at the IP layer |
| iptables -L -n -v | на современных дистрибутивах (Debian 10+/Ubuntu 20.10+/RHEL 8+/Fedora 32+) команда работает через слой совместимости iptables-nft, транслируясь в те же nftables-правила — физически на пакет действует один и тот же движокon modern distros (Debian 10+/Ubuntu 20.10+/RHEL 8+/Fedora 32+) the command runs through the iptables-nft compatibility layer, translating to the same nftables rules — physically the same engine acts on the packet |
| ip link set dev eth0 xdp obj prog.o | загружает eBPF-программу в XDP-хук конкретного интерфейса — с этого момента она видит каждый входящий кадр раньше netfilter, ещё до аллокации sk_buffloads an eBPF program into a specific interface's XDP hook — from that point it sees every incoming frame before netfilter, before sk_buff allocation even happens |
| bpftool net | показывает, какие eBPF-программы прицеплены к каким интерфейсам и хукам прямо сейчас — практический способ увидеть точки перехвата не в теории, а в конкретной системеshows which eBPF programs are attached to which interfaces and hooks right now — a practical way to see the interception points not in theory but on a specific system |
| ss -tulnp | точка наблюдения на противоположном конце стека от XDP — какие сокеты слушают, на каком порту, какой процесс за ними стоит; уровень socket API, самый конец пути пакетаa vantage point at the opposite end of the stack from XDP — which sockets are listening, on what port, which process owns them; the socket API layer, the very end of a packet's path |
Практическое следствие для выбора инструмента: если задача — «не пускать этот трафик вообще, максимально дёшево» под серьёзной нагрузкой (DDoS mitigation, load balancer перед тысячами бэкендов) — смотреть в сторону XDP. Если задача — «обычный firewall/NAT для сервера» — nftables (через nft напрямую или через привычный iptables, транслирующийся в те же правила). Если задача — «максимально быстрый ввод-вывод внутри приложения, которое уже получает нужные пакеты» — io_uring на уровне сокета. Все три решают разные проблемы на разных этажах одного и того же стека, а не конкурируют за одну и ту же роль.The practical consequence for choosing a tool: if the job is "don't let this traffic in at all, as cheaply as possible" under serious load (DDoS mitigation, a load balancer in front of thousands of backends) — look at XDP. If the job is "an ordinary firewall/NAT for a server" — nftables (via nft directly, or via familiar iptables, which translates to the same rules). If the job is "the fastest possible I/O inside an application that's already getting the packets it needs" — io_uring at the socket layer. All three solve different problems at different floors of the same stack, not compete for the same role.
Netfilter Project официально перевёл iptables в режим legacy-поддержки, а новые развёртывания рекомендует делать на nftables — но сама команда iptables никуда не исчезла: на большинстве современных дистрибутивов она работает как совместимый фронтенд поверх nftables-движка (iptables-nft). Скрипты и практики, написанные под классический синтаксис iptables, продолжают работать корректно — просто физически исполняются другим кодом ядра, чем десять лет назад.The Netfilter Project officially moved iptables into legacy-support mode and recommends nftables for new deployments — but the iptables command itself hasn't gone anywhere: on most modern distros it runs as a compatible frontend on top of the nftables engine (iptables-nft). Scripts and practices written against classic iptables syntax keep working correctly — they're just physically executed by different kernel code than a decade ago.
Пакет, отброшенный на этапе XDP, просто никогда не доходит до netfilter — с точки зрения nftables-правил его как будто не существовало. Отладка сетевых проблем, когда часть трафика фильтруется на уровне XDP, а часть — на уровне nftables, требует проверки обоих слоёв по отдельности (bpftool net для первого, nft list ruleset для второго) — единого места, где видно решения сразу обоих механизмов, нет.A packet dropped at the XDP stage simply never reaches netfilter — as far as nftables rules are concerned, it never existed. Debugging network issues where some traffic is filtered at the XDP layer and some at the nftables layer requires checking both layers separately (bpftool net for the first, nft list ruleset for the second) — there's no single place showing both mechanisms' decisions at once.
Native (driver) режим XDP требует явной поддержки со стороны драйвера конкретной сетевой карты — большинство популярных серверных драйверов её имеют, но не все. Если драйвер не поддерживает native-режим, ядро откатывается на generic-режим XDP, который выполняется уже после выделения sk_buff и, соответственно, не даёт того выигрыша в задержке, ради которого обычно и выбирают XDP.Native (driver) mode XDP requires explicit support from a specific network card's driver — most popular server drivers have it, but not all. If the driver doesn't support native mode, the kernel falls back to generic XDP mode, which runs after sk_buff allocation and, accordingly, doesn't deliver the latency win XDP is usually chosen for in the first place.