yihong0618 和朋友们的频道 头像

消息来源频道

yihong0618 和朋友们的频道

@hyi0618

频道8,612 位成员公开可见0 人在线

yihong0618 和朋友们的频道

成员规模8,612 位成员
在线情况0 人在线
消息总数10,129 条消息
浏览量总数3,276,841 次浏览

在这个频道里搜索消息……

t.me/hyi0618

re-review https://dlee-libo.github.io/2020/05/31/bpftrace-kernel-journey/ 🤬
这是五年前我人生第一次排查内核网络栈连通性问题,完全不会,请教最爱的同事波波,他用一个周末钻木取火纯肉眼反汇编 + kprobe 找到了问题写下了这篇荡气回肠的青词。这五年里我每次进步一点都要来回头看看这个最初的问题,正好昨天评论区又提到 rp filter,这就是一个 rp_filter=2 造成的 arp 丢包问题没有丢,压根就没回。
在 ubuntu desktop 2404 kernel 6.14.0-29 依然可以按照博客脚本直接复现,症状是 ping 不通,pwru/tcpdump/ip-neigh 一下发现 arp 没回应,开始检查 arp。
arp 和一般的连通性问题不同,arp 的问题可能根本不是丢包。比如这一题尝试用 pwru 检查 arp 请求和返回最多只能看到:
$ pwru --filter-track-skb --output-caller 'arp and dst host 169.254.0.1'
arp_rcv __netif_receive_skb_one_core
arp_process arp_rcv
ip_route_input_noref arp_process
ip_route_input_slow ip_route_input_noref
__mkroute_input ip_route_input_slow
fib_validate_source __mkroute_input
__fib_validate_source fib_validate_source
ip_handle_martian_source __mkroute_input
consume_skb arp_process
完全没有丢包,看起来就像是正确无误地处理了 arp skb 一样。
用 retsnoop 摸奖也毫无结果,下面的命令检查所有包含 arp 的内核函数的返回 errno:
retsnoop -a 'arp' -e 'arp' -x any
完全没有任何 errno。
这时候只能根据 pwru 的 skb lifetime tracing 输出随缘读一下内核源代码,发现最可疑的似乎是 arp_process() 这个内核函数里的
if (...) // 一大堆嵌套 if,巨型 code block
arp_send_dst(ARPOP_REPLY, ETH_P_ARP,
sip, dev, tip, sha,
dev->dev_addr, sha,
reply_dst);
没有执行。
我在 PyCon CN 25 说过,查“某事为何没有发生”比“某事为何发生”更难,后者找到发生怪事的 hook 一波 bt 带走,前者连 attach point 都不知道从哪里下手。
波巨魔在五年前直接肉翻汇编找到 offset 打 kprobe,但现在我们有了 bpfsnoop lbr,来试试看。
注意 arp_send_dst() 所在的巨型 code block 之后是一个 neigh_lookup
if (...) { // 一大堆嵌套 if,巨型 code block
arp_send_dst(ARPOP_REPLY, ETH_P_ARP,
sip, dev, tip, sha,
dev->dev_addr, sha,
reply_dst);
}
n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
思路就来了,在 neigh_lookup 的入口检查 lbr 不就知道肛才运行的谜语代码是怎么运行了的吗
$ bpfsnoop -k neigh_lookup --output-lbr --filter-arg 'dev->ifindex == 9' -m entry --filter-pid $(pidof ping)
ip_route_input_noref+0x6e (net/ipv4/route.c:2489) -> __rcu_read_unlock+0x0 (kernel/rcu/tree_plugin.h:431)
arp_process+0x4a4 (net/ipv4/arp.c:839) -> arp_process+0x1a5 (include/net/neighbour.h:545)
arp_process+0x1be (include/net/neighbour.h:545) -> neigh_lookup+0x0 (net/core/neighbour.c:579)
最后三行输出已经很清楚了,看一眼 net/ipv4/arp.c:839 的代码
if (arp->ar_op == htons(ARPOP_REQUEST) &&
ip_route_input_noref(skb, tip, sip, 0, dev) == 0) {
是 ip_route_input_noref(skb, tip, sip, 0, dev) == 0 这个条件失败了。
检查 ip_route_input_noref 的 retval 也很简单
$ sudo bpfsnoop -k ip_route_input_noref --output-arg retval --filter-pkt 'arp and dst host 169.254.0.1'
← retval=(enum skb_drop_reason)SKB_DROP_REASON_IP_RPFILTER
已经出现了 SKB_DROP_REASON_IP_RPFILTER,结束。最终原因是 rp_filter 是 max(netdev, all) 的 sysctl,在复现脚本里只修改了 netdev.rp_filter=0 不足够,还要再改 all.rp_filter=0。
非常感谢 Leon Hwang (tg ch: https://t.me/eBPFTalker) 的工具 bpfsnoop,我已经安利了五六次,自己也撸起袖子打算做点微小的贡献。 这些微小的星光推动计算机工程的边界,make a better world.