Skip to content

Latest commit

 

History

History
179 lines (89 loc) · 12.4 KB

【NO.169】内核调试技巧--systemtap定位丢包原因.md

File metadata and controls

179 lines (89 loc) · 12.4 KB

【NO.169】内核调试技巧--systemtap定位丢包原因

内核收发包,可能会由于backlog队列满、内存不足、包校验失败、特性开关如rpf、路由不可达、端口未监听等等因素将包丢弃。

在内核里面,数据包对应一个叫做skb(sk_buff结构)。当发生如上等原因丢包时,内核会调用kfree_skb把这个包释放(丢掉)。kfree_skb函数中已经埋下了trace点,并且通过__builtin_return_address(0)记录下了调用kfree_skb的函数地址并传给location参数,因此可以利用systemtap kernel.trace来跟踪kfree_skb获取丢包函数。考虑到该丢包函数可能调用了子函数,子函数继续调用子子函数,如此递归。为了揪出最深层的函数,本文通过举例几个丢包场景,来概述一种通用方法,来定位丢包原因及精确行号。

1.用例1:ospf hello组播报文被drop,导致ospf 邻居不能建立

1.1 方法:saddr是10.10.2.4的skb是我们关心的数据。

1、 drop_watch跟踪下kfree_skb,定位函数位置:

图片

2、 查看ip_rcv_finish内核源码,编写stap脚本,通过pp()行号来跟踪执行流:

图片

图片

3、 从行号可知ip_route_input_noref返回错误,编写stap脚本,查看ip_route_input_noref的返回值:

图片

返回-22,即-EINVAL。

图片

4、 即ip_route_input_rcu返回错误,同样方法,通过pp()行号来跟踪执行流:

图片

图片

图片

此路不通,看下原因:原来有些行号$saddr不能访问。

图片

5、 此时需要码一码代码了,由于ospf的hello报文是组播,所以下图中红色方框的ipv4_is_multicast为真:

图片

先看下__in_dev_get_rcu(dev)的返回值是否为null,同时也把dev->priv_flags的值打印出来:

图片

__in_dev_get_rcu(dev)返回不为null,再看ip_check_mc_rcu函数的返回值:

图片

返回值为0,同时通过打印的dev->priv_flags 为0x00029820,可判断netif_is_l3_slave为假:

图片

另外ospf使用的组播ip为224.0.0.5或224.0.0.6,本例中使用的是224.0.0.5。因此ipv4_is_local_multicast为真:

图片

最终精确定位到了ip_check_mc_rcu:

图片

先看下in_dev->mc_hash是否为null,通过cast强转来访问结构体成员mc_hash:

图片

则需要走如下分支:

图片

需要遍历in_dev->mc_list,有两种方法,一种是通过crash工具查看,遍历in_dev->mc_list,输出im->multiaddr,如下:

图片

既然我们的主题是stap,那么继续stap,这里通过嵌入c代码来遍历mc_list,输出multiaddr到dmesg:

图片

图片

存在接口加入了224.0.0.5组播组,革命尚未成功,继续跟踪下面的代码:

图片

同样的方式,编写stap脚本或者crash通过struct展开im->sources来查看。

图片

图片

至此,一目了然,原来报文接收的接口没有加入组播组224.0.0.5。google一下:

https://www.cnblogs.com/my_life/articles/6077569.html

图片

综述:那为什么ens5没有加入组播组呢,这要从ospf的原理来说起,ospf建立邻居的时候,是不需要指定接口的,那用于建立邻居的接口是如何选择的呢:实际上是根据指定的area network配置来选择的。当配置area network的时候,会查看系统当前路由,选择合适的接口加入组播组,进而创建邻居。

图片

图片

图片

图片

图片

2.用例2:gre报文的version字段被置位,导致skb被drop。

图片

图片

2.1 方法:saddr是10.10.2.2的skb是我们关心的数据。

1、 依然是drop_watch跟踪下kfree_skb,定位函数位置:

图片

2、 查看gre_rcv的源码,有两个内核模块都存在gre_rcv,由于上面的drop_watch已经定位为ffffffffc099411a,通过crash查看模块的加载地址,来确定调用的是哪一个模块的gre_rcv:

图片

3、 依然是pp()行号来跟踪执行流,和上述不同的是,gre是模块的形式,使用stap的probe module的方式:

图片

图片

综述:这里大家要小心,这个存在”写”skb相关成员,不建议嵌入c代码的方式,去修改skb,如果真的要怎么做,记得恢复回来。

3.用例3: overlay报文根据路由找到出的vxlan隧道接口后,没有从underlay接口出,导致网络不通:

图片

3.1 方法:saddr是172.16.14.2的skb是我们关心的数据。

1、 依然是drop_watch跟踪下kfree_skb,定位函数位置:

图片

2、 查看vxlan_xmit_one,依然是pp()行号来跟踪执行流:

图片

图片

图片

综述:从接口中取到underlay信息后,再去查找路由,由于underlay路由不存在,导致skb被drop。这个问题较简单,也可直接通过查看ip route定位。但是细心的你一定会发现一个有趣的问题,关键字overlay arp,欢迎读者来撩。

4.总结,丢包精确定位行的方法:

1、 drop_watch先定位函数。

2、 使用pp()定位行。必要的时候,编写一些脚本,直接抄写内核代码或者调用stap库就可以了。

3、 递归重复步骤1和2。

是不是跃跃欲试的感觉。

最后:

这里”rpf检查”,”accept_local检查”留给读者来尝试了。实际上systemtap可以做的更多,如内存泄露,系统调用失败,统计流量等等,github上也有很多实用的脚本。

参考链接:

https://cloud.tencent.com/developer/article/1631874

https://www.cnblogs.com/wanpengcoder/p/11768483.html

原文作者:wqiangwang,腾讯 TEG 后台开发工程师

原文链接:https://mp.weixin.qq.com/s/xO2dkyZqKnUcaEXn_B39rw