Post content
把自己用过的网络编程方面的 bpf 梳理了一下(tx 方向的 tcp only),太多细节完全无文档,这就是💻。 [1] cgroup/sock_create 虽然是很好的 hook 用来关联用户态进程和 socket,但是它只能用在主动连接的 tcp socket,因为 passive establish tcp socket 创建时刻是三次握手完成时的内核态,强行读取 struct task 读到的可能是软中断侵入的用户态进程而非最终 accept 的用户态进程。 bpf programming 心智负担高,这是第一条:同一个 bpf prog 可能在不同上下文被触发。 [2] cgroup/connect4 是很好的 per-socket hook 点,此处可以修改 dst ip (LB DNAT 逻辑)、修改 SO_MARK 保证之后的网络包都有特殊的 mark、注入 setsockopt 强绑 ifindex 做路由强选……在之后还会有很多时刻可以用来“重定向流量”,比方用作透明代理流量劫持,但之后的做法都叫做 per-packet,性能优劣一目了然。 [3] skops 除了可以注入 tcp toa 等 optional header 之外,在高版本内核里的还有不少很好的观测性 hook 弥补了传统 tcp info (ss) 的局限,比如 BPF_SOCK_OPS_TSTAMP_SND_SW_CB 用来观测 tcp 在 kernel stack 的 latency。 [4] sk_msg bpf 的 bpf_msg_redirect_hash 虽然实现了 local -> local 的 tcp 流量在三次握手后直接像 pipe 一样发送字节流而完全 bypass tcp stack,看起来美好,但由于 sockmap 的海量恶性 bug,目前慎用,kernel panic。 [5] cgroup_skb/egress 肯定不是唯一可以同时拿到 sk 和 skb 的 bpf,比方说我们可以在 tc/egress 里读取 skb->sk,但它是唯一的单次 attach (cgroup v2 root) 就能全局触发的 bpf,而 tc/egress 需要逐 netdev attach,也要注意 net namespace;我们也可以在任何一个 skb context 的 tracepoint 里 CORE_READ(skb, sk),但是这些 hook 很难高性能读全 GSO-ed skb payload(非线性区),而 cgroup_skb 可以通过 bpf_load_skb_bytes 读到所有的非线性区。这是一个被低估的 bpf hook,有很多想象空间。 [6] lwt_xmit 是另一个可以用来做流量重定向的 hook,但它相比其他 hook 来说已经比较晚了,而且它是 attach 在 routing 上,很难用,我的看法是不要用。 关于顺序和 context 的额外注解: 1. 顺序隐含了很多信息,比方说 nf postrouting > cgroup_skb/egress > arp,这个顺序暗含了: 1.a. cgroup_skb bpf 看到的是 nf NAT 过的 skb,在此时收集观测性数据时用 sk 的 tuple 还是 skb 的 tuple,不同需求有不同设计。 1.b. 还没发生二层解析,skb->data 里不可能有 mac 地址。 1.c. 这个 hook 在 tcp retrans 之后,意味着可能看到重试的 tcp segment,如果要做 seg/ack 统计分析就要重组的准备。 1.d. 这个 hook 只是网络包在网络栈的一个中间步骤,在这里看到一个 tcp 请求不代表它发出去了。 2. user context vs kernel context 我们想要在 tc/egress 上抓 “指定 pid 进程的流量”,第一反应应该是在其中调用 bpf_get_current_task() 或者 bpf_get_current_pid_tgid(),但是这只有在 tc bpf 运行在用户态上下文时才成立,而 tc bpf 如果处理的是 tcp retrans 重试流量,一定处于软中断内核态上下文,那 bpf_get_current_pid_tgid 读到的就是被侵入的 pid。 3. irqsoft enable vs disable (local_bh_disable) 我们想要在在 BPF_PROG_TYPE_NETFILTER bpf 里做一些简单的统计,决定用 percpu_map 避免 CAS 原子操作的性能问题,这在 xdp 和 tc bpf 里很常见,因为 tc bpf 关闭了软中断 local_bh_disable,所以直接非原子地加减 percpu 变量不会被抢占和重入,但在 BPF_PROG_TYPE_NETFILTER 等大部分没有关闭软中断的 bpf 就不行了。 其实 rx 方向还要更复杂一些,因为有 xdp, sk_lookup, sk_reuseport 等更多的 bpf 类型,心智负担更高,但我已经打开了 Diablo2 继续我的专家模式圣骑士了。老板都辞职了我上个屁班 😀