本节摘要:XDP 在驱动刚收到帧、套接字缓冲区尚未分配时就能丢弃或转发;TC 工作在已有 sk_buff、能看到更多栈内元数据的位置。要线速过滤 DDoS 选前者,要按连接或 cgroup 做精细策略选后者。挂错层,不是慢一点,是对象根本不存在。
阅读完本节,你应当能够:
第 1.1 节说抓包太靠外。网络类 eBPF 把观测和干预点往左推。推多远,决定你手里拿的是原始帧还是已经长出身份的包。
帧进网卡,DMA 写入内存。XDP 可以在驱动里最早被唤醒,此时常常还没有完整的 sk_buff。你能读包头、改部分字节、决定通过、丢弃、重定向。省下的是分配、协议解析、iptables 遍历。这就是它能在攻击流量下活下来的原因:垃圾在进城之前被扔掉,而不是进了城再安检。
TC 挂在流量控制层,包已经是 sk_buff,有设备、协议、有时还有套接字关联。你可以做分类、改字段、与 cgroup 策略配合。延迟高于 XDP,表达力也高于 XDP。服务网格把重定向放在这一带,是因为需要的不是“看见以太网帧”,而是“看见这是谁的连接”。
套接字过滤更靠后,面向已经送到某个套接字的数据。适合按连接观测,不适合挡在网卡门口。
海关有三道。第一道在港口看集装箱外观,能把整船垃圾挡回去,但看不到箱内提单属于哪个公司。第二道在保税区,单据齐了,能按公司策略开箱。第三道在收货门口,只处理已经派送到你家的货。抱怨第一道“不够智能”,是在要求港口干保税区的活。
| 挂载 | 对象在不在 | 能做什么 | 看不见什么 | 典型题 |
|---|---|---|---|---|
| 原生 XDP | 往往还无完整 sk_buff | 丢、过、重定向、有限改写 | 套接字、cgroup、连接跟踪细节 | 线速过滤、DDoS 前置 |
| 通用 XDP | 有 sk_buff 回退路径 | 能力接近但更慢 | 相对原生多付一次代价 | 驱动不支持时的降级 |
| TC | 有 sk_buff | 分类、改写、配合策略 | 已经付了分配成本 | 东西向策略、网格劫持 |
| 套接字过滤 | 已到套接字 | 按连接过滤观测 | 到不了这里的包 | 应用侧精细抓取 |
把 XDP 程序写成“查连接表、看进程名、再决定”会让人失望:那一拍进程名往往不在。你要么把状态自己用 Map 从追踪类同步过来,要么把程序往后挪到 TC。
反过来,把本该在 XDP 丢掉的攻击包留到 TC,等于让分配器和协议栈给攻击者打工。CPU 在构造 sk_buff 上烧完,你的精细策略没有机会表现。
所以分界问题应这样问:决策所需的最晚对象,最早在哪一拍出现? 只需要五元组,XDP 够。需要“这个包属于哪个 Pod 的哪条已建立连接”,往 TC 或 sockops 走,并用 Map 把前面的计数接上。
/* 概念性:XDP 只根据包头做丢或过 */ SEC("xdp") int filter(struct xdp_md *ctx) { if (is_syn_flood_tuple(ctx)) return XDP_DROP; return XDP_PASS; }
⚠️ 常见坑:在不支持原生 XDP 的网卡上以为自己已经“线速”。通用 XDP 仍会走分配路径,数字会好看过 TC,但远不是驱动内那一档。
💡 关键直觉:先列出决策依赖的字段,再选渡口。字段不在那一拍,快没有意义。
网卡硬件卸载能把 XDP 程序放到 ASIC 上。这是真正的零主机 CPU,限制也更死:Helper 更少,调试更难。不要假设开发机的 XDP 语义等于卸载后的语义。
多网卡、多队列时,程序要无状态或 Map 自己分片。按 CPU 的计数还好说,按连接的状态机要考虑包打在不同队列。
与 iptables/nftables 共存时,明确谁先动手。XDP 丢了的包,后面规则看不见,排障会误判“规则没生效”。把 eBPF 策略和传统规则的责任写进同一张图,比再加一层“全镜像抓包”更有用。
Cilium 一类平台把大量 TC/XDP 程序当成数据平面。你在节点上手动挂一条 XDP 可能和它们抢挂钩点。生产上优先走平台提供的挂载,而不是 SSH 上去“先试一下”。

原生 XDP 看得到的是还没走到应用的字节。做完整七层要自己解析,复杂度和验证压力都高。多数七层策略放用户态或更靠后的钩子,XDP 只做明显的四层垃圾。
XDP 主要设计在收路径的最早点。出方向更常见的是 TC。出方向的“早”和入方向的“早”不是同一个驱动回调。
攻击流量和东西向业务流量的决策对象不同。攻击要在最早的渡口用最少字段丢掉;网格要在身份齐全的地方做重定向和策略。塞进同一个程序,验证器路径会膨胀,XDP 的热路径会被策略树拖死,TC 又挡不住线速洪水。拆开之后用 Map 共享“这五元组已识别为攻击”之类的小键即可,不必共享整份策略。
压测方法也不同。测 XDP 要用能打满网卡队列的发包器,看 CPU 是否仍接近空闲、看驱动是否走原生路径。测 TC 要用真实连接与 cgroup,看策略命中和错误重定向。用 TC 压测的数字去宣传 XDP 能力,或反过来,都会在真实攻击里穿帮。
多队列 RSS 会把同一连接的包打到不同 CPU。XDP 程序若在每 CPU 上维护未同步的限速桶,攻击者很容易把桶稀释。限速状态要么用全局原子并接受争用,要么按对称哈希保证同连接同 CPU。设计限速时把 RSS 写进假设,不要当实验机单队列的结论。
IPv6 扩展头、VLAN 叠层、封装协议会让“只读固定偏移的以太网+IP+TCP”失效。XDP 里解析要有界,超预算就交给后面的栈或丢掉。不要为了完整性在最早渡口写深度解析器。那是把 TC 甚至用户态的活抢到了最贵的地方。
和主机防火墙的责任表必须公开:哪类丢包发生在 XDP,哪类发生在 nftables,哪类发生在应用。排障时按表看计数。没有表时,每个人都会说“我这边放行了”,包却已经在更早的渡口消失。
网卡不支持原生路径,程序走了通用 XDP。压测数字好过 TC,于是对外宣称线速过滤。真攻击来时 CPU 照样打满,因为 sk_buff 仍在分配。后来在指标里显式标出驱动模式:原生、通用、卸载。模式不对,数字不许写进容量规划。宣传和路径必须同一张图。
RSS 把同连接打到多核,每 CPU 限速桶被稀释,攻击从所有队列进来,每个桶都觉得自己没满。改成按对称哈希保证同连接同核,或用全局原子并接受争用。设计假设里写明队列模型,实验机单队列的结论禁止直接上线。
和 nftables 的责任表没公开时,XDP 丢了的包在防火墙计数里消失,两边互相说放行了。公开责任表之后,排障先看哪一层的计数在涨。没有表,会议会变成信仰之争。有表,会议会变成读数。读数更快。
指标标明原生、通用还是卸载。模式不对,数字不许进容量规划。通用路径上的好看数字,挡不住真攻击时的分配成本。RSS 下每 CPU 限速桶会被稀释,同连接要能落到可同步的状态,或接受全局原子争用。假设里写队列模型,单队列实验机结论禁止直接上线。和主机防火墙的责任表公开:谁先丢,谁的计数该涨。没有表,会议变成信仰。有表,会议变成读数。IPv6 扩展头和封装让固定偏移失效,XDP 解析必须有界,超预算交给后面或丢掉。最早渡口不写深度解析器。深度是贵的,贵要花在对象已经长全的地方。对象还没长全时,贵等于把自己变成攻击者的帮凶,帮他消耗你的 CPU。帮凶角色要拒绝。
网络钩子选错,不是慢一点的问题,是你手里拿着的对象根本不是你以为的那个。在还没有套接字的地方问这是谁的 Pod,答案只能是未知或谎言。未知可以接受。谎言会变成错误策略。错误策略比慢更伤。所以把字段清单写在挂钩点前面:我要哪些字段,这些字段最早在哪一拍出现。清单会自然把人推向 XDP 或 TC。不写清单的人会被听说很快推着走。听说很快的人,常常站在通用路径上还以为自己在驱动里。
\n\n## 课堂补充\n\n字段清单决定渡口,听说很快不决定。原生通用卸载三种模式要标出来。垃圾在进城前扔掉。身份在保税区才齐全。RSS会稀释每核桶。责任表不公开就会信仰之争。七层别抢最早渡口。出方向常见是TC。平台挂钩是主权。压测方法按层选。扩展头有界。CPU该降不降就是挂错层。\n\n## 本章回顾
下一节离开包路径,看 cgroup、LSM 和调度:策略按谁生效,安全在哪一拍裁决。