内核收发包,可能会由于backlog队列满、内存不足、包校验失败、特性开关如rpf、路由不可达、端口未监听等等因素将包丢弃。
在内核里面,数据包对应一个叫做skb(sk_buff结构)。当发生如上等原因丢包时,内核会调用kfree_skb把这个包释放(丢掉)。kfree_skb函数中已经埋下了trace点,并且通过__builtin_return_address(0)记录下了调用kfree_skb的函数地址并传给location参数,因此可以利用systemtap kernel.trace来跟踪kfree_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的时候,会查看系统当前路由,选择合适的接口加入组播组,进而创建邻居。
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,如果真的要怎么做,记得恢复回来。
1、 依然是drop_watch跟踪下kfree_skb,定位函数位置:
2、 查看vxlan_xmit_one,依然是pp()行号来跟踪执行流:
综述:从接口中取到underlay信息后,再去查找路由,由于underlay路由不存在,导致skb被drop。这个问题较简单,也可直接通过查看ip route定位。但是细心的你一定会发现一个有趣的问题,关键字overlay arp,欢迎读者来撩。
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 后台开发工程师