BCC 和 Ftrace 追踪内核网络模块实战

Posted YRCloudFile

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BCC 和 Ftrace 追踪内核网络模块实战相关的知识,希望对你有一定的参考价值。

今天给技术伙伴们分享一下 BCC 和 Ftrace 的使用方法,在开发实践过程中会遇到哪些问题,本篇文章都将一一道来~

BCC 一个用于跟踪内核和操作程序的工具集,其软件包中包含了一些有用的工具和例子,是 eBPF 技术的一个前端,集成了 ebpf/kprobe/uprobe 等工具,可以使用 Python 来封装,对一些常见功能进行了整合。

而 eBPF 是近几年比较热门的一项技术,它的前身是 BPF(Berkeley Packet Filter),也就是 tcpdump 使用过滤数据包的技术。eBPF 对 BPF 进行了扩展,它能在内核中运行自定义程序,而无需修改内核源码或者加载内核模块。

Ftrace 有两种概念,一种是广义的 Ftrace 框架,一种是狭义的追踪技术。广义的 Ftrace 框架包含了 krpobe trace event 很多追踪技术,而狭义的 Ftrace 也就是本期的重点内容,今天会对其中一种 Ftrace 的使用做着重介绍。

BCC

Tracer 的概念直译为追踪器,可以直观理解为内核提供的一些追踪技术,比如有 function tracer,function graph tracer 等等,今天将着重介绍 function graph tracer,因为 function tracer 的很多功能用 BCC 就可以实现,而 function graph 却是 BCC 没有的功能。

function graph tracer 的效果如图:

BCC

BCC 效果如图:

BCC

如何使用 BCC Ftrace?

>>>> 安装

BCC 使用 yum install bcc -y 就能安装。Ftrace 不用安装,是内核提供的,只要开启 CONFIG_DYNAMIC_FTRACE 就行,而这个选项我们是默认开启的。

内核版本越高,BCC 和 Ftrace 能使用的功能就越多。也就是说,在低内核版本上,可能一些功能并没有集成进去所以就不能使用这个功能。

BCC 安装完成后如图所示:

BCC

而 Ftrace 需要手动开启内核一些选项:

BCC

Trace 语法:

  • name 或者 p:name:对内核函数 name() 进行插桩
  • r::name:对内核函数 name() 的返回值进行插桩
  • lib:name 或者 p:lib:name:对用户态 lib 库中的函数 name() 进行插桩
  • r:lib:name:对用户态 lib 库中的函数 name() 进行插桩
  • path::name:对位于 path 路径下的用户态函数 name() 进行插桩
  • r:path:name:对位于 path 路径下的用户态函数 name() 的返回值进行插桩
  • t:system:name:对名为 system:name 的跟踪点进行插桩
  • u:lib:name:对 lib 库中名为 name 的 USDT 探针进行插桩

*: 用来匹配任意字符的通配符。-r 选项允许使用正则表达式。

详细可见《bpf之巅——洞悉linux系统和应用性能》

开发过程中的实际案例

>>>> 安装

BCC

可以看到环境上有两个同网段的 ip,这个环境作为 client 向 server 发消息,server 地址是 100.1.1.21。

>>>> ifdown 测试

在 client 和 server 的正常通信中通过 ifdown 把网卡 down 掉,会发生什么呢?

BCC

BCC

我们可以看到,client 在 down 掉网卡后,返回值还是正常的,但是 server 阻塞在 read(),就已经接收不到数据。

>>>> ip link set down 测试

在 client 和 server 的正常通信中通过 ip link set ens192 down 把 link down 掉,会发生什么呢?

BCC

BCC

可以看到,client 和 server 都还可以正常收发!

这里有两个问题:

  1. 为什么 ifdown 之后,client 还是可以发送成功?
  2. 为什么 ifdown 之后,server 收不到消息,而 ip link set down 测试中 server 可以接收到消息?

接下来,我们将通过 BCC 和 Ftrace 追踪工具解答这两个问题。

BCC Trace 追踪 __sk_dst_check()

根据排查定位,我们定位到了路由这一块。而路由是在 ip 层做的事情,所以我们来看 ip 层的代码:

ip_queue_xmit() -> ip_queue_xmit() ip_queue_xmit() 是 ip 层的入口函数。

BCC

可以看到,ip 层的第一步就是一个检查,那么我们需要看一下对两种测试来说,检查的结果是什么,有什么不同?

我们需要用 BCC 自带的 Trace 工具来查看 sk_dst_check() 的返回值。

>>>> ip link set down 测试

BCC

>>>> ifdown

BCC

可以看到,在 ifdown 测试中,后面的返回值全部变为了 NULL ,而 ip link set down 测试中,只有一次变为了 NULL ,而后面就恢复了正常。

经过了解后,变为 NULL 的原因很简单。因为 struct sock 里缓存了上次成功的路由,而这 sk_dst_check() 检查路由缓存是否还可用, ifdown 和 ip link set down 后肯定就不可用了。

梳理代码逻辑后可以观察到,如果路由缓存不可用的话,会通过 ip_route_output_ports() -> ip_route_output_flow() 来查找路由表,如果查找成功会再次将其缓存起来。所以这里发生的事情是, ifdown 路由缓存失效后,查找路由表找不到有效路由。而 ip link set down 后,查找路由表可以找到有效路由。

那么要通过 Ftrace 看一下,在两种测试中,ip_route_output_flow() 的逻辑到底有什么不同?

Ftrace 追踪 ip_route_output_flow()

Ip link set down后,路由查找的过程:

BCC

ifdown 后,路由查找的过程:

BCC

对比两种输出的不同和内核代码,可以发现,ifdown 后会直接查找设备失败,进而直接返回给上层 (tcp)-EHOSTUNREAD。而 ip link down 后,会查找设备成功,进而查找路由成功。

再对比内核代码中的查找路由过程,发现在 fib_table_lookup() 中,有以下逻辑:

BCC

然后发现 /proc/sys/net/ipv4/conf/ens40/ignore_routes_with_linkdown 这个选项,控制了 link down 后,原来路由是否还会生效,而这个值默认是 0,也就是不忽略原来的路由项。

至此,终于可以解答前面的两个疑问了:

  1. 为什么 ifdown 之后,client 还是可以发送成功?
  2. 因为 ip 层向 tcp 返回了 EHOSTUNREACH,而 tcp 进行了重试,再次收到 EHOSTUNREACH,再达到重试 /proc/sys/net/ipv4/tcp_retries2 这么多次之前,tcp 是不会返回失败的。
  3. 为什么 ifdown 之后,server 收不到消息,而 ip link set down 测试中 server 可以接收到消息。
  4. 因为 ifdown 后,设备被删除了,直接向 tcp 层返回了 EHOSTUNREACH,而下一次重试又会失败。所以消息发布出去, sever 收不到消息。
  5. 而 ip link set down 后,设备没有被删除,而 /proc/sys/net/ipv4/conf/ens40/ignore_routes_with_linkdown 默认为 0,也就是不忽略已经 down 掉 Link 的路由,所以会查找路由成功,进而发送成功,server 接收成功。

脚本

BCC

通过本期文章的分享,大家应该对 BCC 和 Ftrace 的使用方法有了一定的了解,我们后续还会有更多技术文章的分享,可以持续关注。

以上是关于BCC 和 Ftrace 追踪内核网络模块实战的主要内容,如果未能解决你的问题,请参考以下文章

龙蜥开源内核追踪利器 Surftrace:协议包解析效率提升 10 倍 | 龙蜥技术

Debugging the kernel using Ftrace

Debugging the kernel using Ftrace

Debugging the kernel using Ftrace

Debugging the kernel using Ftrace

怎么在ftrace中监视自己的函数