👋 动态记录 & 转发分享 ✨ https://tg.okhk.net/ ✌️
终于懂了懂了,这个 bug 查了整整两天。
症状是这样的,考虑 K8s 上在不同节点的两个 pods 通信,本来好好的,但是一旦启用了七层透明代理(envoy),流量的路径会变成 src pod -> envoy(src host) -> envoy(dst host) -> dst pod,然后 curl 就不通了。最奇怪的是必须两个 envoy 同时代理才会出问题,单 envoy 没事。
先确认丢包发生在哪一步,这一步在普通场景很简单,但是在透明代理的场景下会看到相同 tuple 的 skb 飞来飞去但又并不是同一 tcp 会话,需要格外小心仔细。此外我追了几小时自己 ctrl-c 打断 curl 导致的 SKB_DROP_REASON_NOT_SPECIFIED,很绝望。
确认 MTU 丢包之后开始仔细摸查内核处理 MTU 的代码。一定要放弃胡乱猜测,老老实实对照着汇编和源码人肉逆向。这一步异常辛苦,虽然还挺好玩的,但是如果有自动化的工具我会更加感动。
一个简单的例子,我想看那个被丢弃的 skb 执行到下面代码时的 mtu 变量是多少(我知道可以直接抓 icmp):
我的笨办法是 gdb -ex disas 检查 ip_forward 的汇编,先过一遍 call 指令看看都有哪些函数调用,立刻能发现一个指令是
就这样反复排查所有可疑的地方,最终我找到了两个根因:
1. xfrm 会导致 mtu 隐式下降,没有任何命令行工具能够检查出来 mtu 居然从 1500 降到了 1446;
2. envoy 透明代理导致 TCP 握手时交换的 MSS 变大了,pod->pod 的时候 MSS 是 1383,envoy->pod/envoy 的 MSS 是 1460。内核会把 TCP MSS 设为 skb gso_size,这里变大的 gso_size 导致 MTU 检查过不去导致内核丢包。MTU 检查很多,并不是包长大于 MTU 就直接扔,这里也花了很多精力。
快解是把相关 netdev mtu 减小到 1446,但是会影响 node2node 流量,下周再想办法吧。。
症状是这样的,考虑 K8s 上在不同节点的两个 pods 通信,本来好好的,但是一旦启用了七层透明代理(envoy),流量的路径会变成 src pod -> envoy(src host) -> envoy(dst host) -> dst pod,然后 curl 就不通了。最奇怪的是必须两个 envoy 同时代理才会出问题,单 envoy 没事。
先确认丢包发生在哪一步,这一步在普通场景很简单,但是在透明代理的场景下会看到相同 tuple 的 skb 飞来飞去但又并不是同一 tcp 会话,需要格外小心仔细。此外我追了几小时自己 ctrl-c 打断 curl 导致的 SKB_DROP_REASON_NOT_SPECIFIED,很绝望。
确认 MTU 丢包之后开始仔细摸查内核处理 MTU 的代码。一定要放弃胡乱猜测,老老实实对照着汇编和源码人肉逆向。这一步异常辛苦,虽然还挺好玩的,但是如果有自动化的工具我会更加感动。
一个简单的例子,我想看那个被丢弃的 skb 执行到下面代码时的 mtu 变量是多少(我知道可以直接抓 icmp):
IPCB(skb)->flags |= IPSKB_FORWARDED;
mtu = ip_dst_mtu_maybe_forward(&rt->dst, true);
if (ip_exceeds_mtu(skb, mtu)) {
IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS);
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
htonl(mtu));
SKB_DR_SET(reason, PKT_TOO_BIG);
goto drop;
}
我的笨办法是 gdb -ex disas 检查 ip_forward 的汇编,先过一遍 call 指令看看都有哪些函数调用,立刻能发现一个指令是
call __icmp_send
,对应上面的 icmp_send() 函数,立刻能知道 mtu 作为第四参数在 rcx 寄存器,所以往前看 rcx 变量怎么来的,一点点就能分析出每行代码对应哪些汇编,每个变量在哪个寄存器、内存,然后可以用 bpftrace 仔细检查可疑的变量,如检查 ip_exceeds_mtu 函数调用时的各变量:// if (ip_exceeds_mtu(skb, mtu)) {
k:ip_forward+492
{
$skb = (struct sk_buff*)@tid2skb[tid];
$ip = (struct iphdr*)($skb->network_header+$skb->head);
if ($skb != 0) {
$len = *(uint32*)(reg("bx") + 0x70);
$mtu = reg("r13");
$df = $ip->frag_off ;
$fms = ((struct inet_skb_parm*)(((uint8*)$skb)+40))->frag_max_size;
printf("mtu=%ld len=%lld df=%d fms=%ld\n", $mtu, $len, $df, $fms);
}
}
就这样反复排查所有可疑的地方,最终我找到了两个根因:
1. xfrm 会导致 mtu 隐式下降,没有任何命令行工具能够检查出来 mtu 居然从 1500 降到了 1446;
2. envoy 透明代理导致 TCP 握手时交换的 MSS 变大了,pod->pod 的时候 MSS 是 1383,envoy->pod/envoy 的 MSS 是 1460。内核会把 TCP MSS 设为 skb gso_size,这里变大的 gso_size 导致 MTU 检查过不去导致内核丢包。MTU 检查很多,并不是包长大于 MTU 就直接扔,这里也花了很多精力。
快解是把相关 netdev mtu 减小到 1446,但是会影响 node2node 流量,下周再想办法吧。。