一次veth丢包排查


记一次有趣的排查bug日记:

问题

事情的起因是在进行网络实验时发现出现丢包的情况,实验的设置如下下图所示:

由一个Emulator负责接收veth包并转发到对应的veth(通过AF_XDP)。在进行iperf测试时,发现跑几秒钟网络带宽就变为0。之后两个节点就相互ping不通了。

Untitled.png

排查

通过ifconfif观察到,在问题后,通过veth1向veth0投送包时总是被drop掉。

首先,我通过tcpdump,nettrace,xdp ebpf对veth0的包接收行为进行了观察,但都无法捕获到丢包的现象。怀疑可能在发送阶段包就丢了,因此为了能够观察veth的发送行为,复制了一份veth的代码,重新编译了一个“uveth”模块,如此便可以愉快地在veth的源码中打log。

构建内核模块的流程很丝滑,网上找了一份Makefile模版,把veth复制一下,改个设备名就可以直接编译了。

CONFIG_MODULE_SIG=n
obj-m:=uveth.o                     
    
CURRENT_PATH:=$(shell pwd)          
LINUX_KERNEL:=$(shell uname -r)    
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
   
all:
  make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules 
clean:
  make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean    

启动完uveth后有一个问题:如何创建相应的设备?

一般创建veth的方式是通过ip link创建,其内部使用了netlink与内核的进行通信创建。

为了避免手写netlink通信,看了下iproute的代码,发现只需要通过hack一下名称的方式就可以复用veth的所有创建逻辑。

// Hack for uveth 
if(strcmp(type,"uveth")==0){
   lu = get_link_kind("veth");
} else {
   lu = get_link_kind(type);
}

之后就可以愉快地使用 ip link add .. type uveth .. 创建相应设备了。

有了日志之后发现网络包果然是在veth发送包(veth0→veth1)时丢掉的,原因是在__dev_forward_skbis_skb_forwardable 判断失败,通过将skb的长度打印出来后,发现是长度超过了mtu。

由于网络包是直接转自来自veth的网络包,所以理论上网络包不应该超过veth设置的mtu,所以我怀疑问题来自于emulator中某个环节对网络包的内容进行了污染。

通过在emulator每次发送之前打印网络包的长度,我发现传送数据长度都是符合要求的。那么错误的原因可能出现在其他地方,我观察到af_xdp发送包的元数据FrameDesc中有描述包大小的字段,通过比较该长度和实际的数据长度,果然这个字段会出现与实际数据不一致,超过mtu的情况。

最终,我发现是因为FrameDesc在重新被分配时没有进行初始化,导致其长度可能是上一次的长度,因此再次使用FrameDesc会使得长度会变成上一次的两倍,超过实际的数据长度。

#Network