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

简介: 如何将网络报文与内核协议栈清晰关联起来精准追踪到关注的报文行进路径呢?

image.png

文/系统运维 SIG


Surftrace 是由系统运维 SIG 推出的一个 ftrace 封装器和开发编译平台,让用户既能基于 libbpf 快速构建工程进行开发,也能作为 ftrace 的封装器进行 trace 命令编写。项目包含 Surftrace 工具集和 pylcc、glcc(python or generic C language for libbpf Compiler Collection),提供远程和本地 eBPF 的编译能力。


通过对 krobe 和 ftrace 相关功能最大化抽象,同时对各种场景下的追踪能力增强(比如网络协议抓包),使得用户非常快速的上手,对定位问题效率提升 10 倍以上另外,现如今火到天际的技术——eBPF,Surftrace 支持通过 libbpf 及 CO-RE 能力,对 bpf 的 map 和 prog 等常用函数进行了封装和抽象,基于此平台开发的 libbpf 程序可以无差别运行在各个主流内核版本上,开发、部署和运行效率提升了一个数量级。


Surftrace 最大的优势在于将当前主流的 trace 技术一并提供给广大开发者,可以通过 ftrace 也可以使用 eBPF,应用场景覆盖内存、IO 等 Linux 各个子系统,特别是在网络协议栈跟踪上面,对 skb 内部数据结构,网络字节序处理做到行云流水,把复杂留给自己,简单留给你。今天就让我们来见识一下 Surftrace 在网络领域的强劲表现吧。

一、理解 Linux 内核协议栈

定位网络问题是一个软件开发者必备一项基础技能,诸如 ping 连通性、tcpdump 抓包分析等手段,可以对网络问题进行初步定界。然而,当问题深入内核协议栈内部,如何将网络报文与内核协议栈清晰关联起来,精准追踪到关注的报文行进路径呢?

1.1 网络报文分层结构

引用自《TCP/IP 详解》卷一。

image.png如上图所示,网络报文对数据报文数据在不同层进行封装。不同 OS 均采用一致的报文封装方式,达到跨软件平台通讯的目的。

1.2 sk_buff 结构体

sk_buff 是网络报文在 Linux 内核中的实际承载者,它在 include/linux/skbuff.h 文件中定义,结构体成员较多,本文不逐一展开。

image.png

用户需要重点关注下面两个结构体成员:


unsignedchar *head, *data;


其中 head 指向了缓冲区开始,data 指向了当前报文处理所在协议层的起始位置,如当前协议处理位于 tcp 层,data 指针就会指向 struct tcphdr。在 IP 层,则指向了struct iphdr。因此,data 指针成员,是报文在内核处理过程中的关键信标。

1.3 内核网络协议栈地图

下图是协议栈处理地图,可以保存后放大观看(图源网络)。

image.png

不难发现,上图中几乎所有函数都涉及到 skb 结构体处理,因此要想深入了解网络报文在内核的处理过程,skb->data 应该就是最理想的引路蜂。

二、Surftrace 对网络报文增强处理

Surftrace 基于 ftrace 封装,采用接近于 C 语言的参数语法风格,将原来繁琐的配置流程优化到一行命令语句完成,极大简化了 ftrace 部署步骤,是一款非常方便的内核追踪工具。但是要追踪网络报文,光解析一个 skb->data 指针是远远不够的。存在以下障碍:

  • skb->data 指针在不同网络层指向的协议头并不固定;
  • 除了获取当前结构内容,还有获取上一层报文内容的需求,比如一个我们在 udphdr结构体中,是无法直接获取到 udp 报文内容;
  • 源数据呈现不够人性化。如 ipv4 报文 IP 是以一个 u32 数据类型,可读性不佳,过滤器配置困难。

针对上述困难,Surftrace 对 skb 传参做了相应的特殊处理,以达到方便易用的效果。

2.1 网络协议层标记处理

以追踪网协议栈报文接收的入口__netif_receive_skb_core 函数为例,函数原型定义:


staticint__netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc,  struct packet_type **ppt_prev);

解析每个 skb 对应报文三层协议成员的方法:


surftrace 'p __netif_receive_skb_core proto=@(struct iphdr *)l3%0->protocol`

协议成员获取方法为@(struct iphdr *)l3%0->protocol

image.png

tips:

  • 可以跨协议层向上解析报文结构体,如在 l3 层去分析 struct icmphdr 中的数据成员
  • 不可以跨协议层向下解析报文结构体,如在 l4 层去分析 struct iphdr 中的成员

2.2 扩充下一层报文内容获取方式

surftrace 为 ethhdr、iphdr、icmphdr、udphdr、tcphdr 结构体添加了 xdata 成员,用于获取下一层报文内容。xdata 有以下 5 类类型:

数据 数据类型 数据长度(字节)
cdata unsgined char [] 1
sdata unsigned short [] 2
ldata unsigned int [] 4
qdata unsigned long long [] 8
Sdata char* [] 字符串

数组下标是按照位宽进行对齐的,比如要提取 icmp 报文中的 2~3 字节内容,组成一个 unsigned short 的数据,可以通过以下方法获取:


data=@(struct icmphdr*)l3%0->sdata[1]

2.3 IP 和字节序模式转换

网络报文字节序采取的是大端模式,而我们的操作系统一般采用小端模式。同时,ipv4 采用了一个 unsigned int 数据类型来表示一个 IP,而我们通常习惯采用 1.2.3.4 的方式来表示一个 ipv4 地址。上述差异导致直接去解读网络报文内容的时候非常费力。surftrace 通过往变量增加前缀的方式,在数据呈现以及过滤的时候,将原始数据根据前缀命名规则进行转换,提升可读性和便利性。

前缀名 数据输出形式 数据长度(字节)
ip_ a.b.c.d ip字符串
b16_ 10 进制 2
b32_ 10 进制 4
b64_ 10 进制 8
B16_ 16 进制 2
B32_ 16 进制 4
B64_ 16 进制 8

2.4  牛刀小试

我们在一个实例上抓到一个非预期的 udp 报文,它会往目标 ip 10.0.1.221 端口号  9988 发送数据,现在想要确定这个报文的发送进程。由于 udp 是一种面向无连接的通讯协议,无法直接通过 netstat 等方式锁定发送者。用 Surftrace 可以在 ip_output 函数处中下钩子:


intip_output(struct net *net, struct sock *sk, struct sk_buff *skb)

追踪表达式:


surftrace 'p ip_output proto=@(struct iphdr*)l3%2->protocol ip_dst=@(struct iphdr*)l3%2->daddr b16_dest=@(struct udphdr*)l3%2->dest comm=$comm body=@(struct udphdr*)l3%2->Sdata[0] f:proto==17&&ip_dst==10.0.1.221&&b16_dest==9988'


追踪结果:

surftrace 'p ip_output proto=@(struct iphdr*)l3%2->protocol ip_dst=@(struct iphdr*)l3%2->daddr b16_dest=@(struct udphdr*)l3%2->dest comm=$comm body=@(struct udphdr*)l3%2->Sdata[0] f:proto==17&&ip_dst==10.0.1.221&&b16_dest==9988' echo 'p:f0 ip_output proto=+0x9(+0xe8(%dx)):u8 ip_dst=+0x10(+0xe8(%dx)):u32 b16_dest=+0x16(+0xe8(%dx)):u16 comm=$comm body=+0x1c(+0xe8(%dx)):string' >> /sys/kernel/debug/tracing/kprobe_events echo 'proto==17&&ip_dst==0xdd01000a&&b16_dest==1063' > /sys/kernel/debug/tracing/instances/surftrace/events/kprobes/f0/filter echo 1 > /sys/kernel/debug/tracing/instances/surftrace/events/kprobes/f0/enable echo 0 > /sys/kernel/debug/tracing/instances/surftrace/options/stacktrace echo 1 > /sys/kernel/debug/tracing/instances/surftrace/tracing_on <...>-2733784 [014] .... 12648619.219880: f0: (ip_output+0x0/0xd0) proto=17 ip_dst=10.0.1.221 b16_dest=9988 comm="nc" body="Hello World\!  @"


通过上述命令,可以确定报文的发送的 pid 为 2733784,进程名为 nc。

三、实战:定位网络问题

接下来我们从一个实际网络网络问题出发,讲述如何采用 Surftrace 定位网络问题。

3.1 问题背景

我们有两个实例通讯存在性能问题,经抓包排查,确认性能上不去的根因是存在丢包导致的。幸运的是,该问题可以通过 ping 对端复现,确认丢包率在 10% 左右。

image.png

通过进一步抓包分析,可以明确报文丢失在实例 B 内部。

image.png

通过检查 /proc/net/snmp 以及分析内核日志,没有发现可疑的地方。

3.2 surftrace 跟踪

在 1.1 节的地图中,我们可以查到网络报文是内核由 dev_queue_xmit 函数将报文推送到网卡驱动。因此,可以在这个出口先进行 probe,过滤 ping 报文,加上 -s 选项,打出调用栈:

surftrace 'p dev_queue_xmit proto=@(struct iphdr *)l2%0->protocol ip_dst=@(struct iphdr *)l2%0->daddr f:proto==1&&ip_dst==192.168.1.3' -s

可以获取到以下调用栈:

image.png

由于问题复现概率比较高,我们可以将怀疑的重点方向先放在包发送流程中,即从 icmp_echo 函数往上,用 Surftrace 在每一个符号都加一个 trace 点,追踪下回包到底消失在哪里。

image.png

3.3 锁定丢包点

问题追踪到了这里,对于经验丰富的同学应该是可以猜出丢包原因。我们不妨纯粹从代码角度出发,再找一下准确的丢包位置。结合代码分析,我们可以在函数内部找到以下两处 drop 点:image.png通过 Surftrace 函数内部追踪功能,结合汇编代码信息,可以明确丢包点是出在了 qdisc->enqueue 钩子函数中。


rc = q->enqueue(skb, q, &to_free) & NET_XMIT_MASK;

此时,可以结合汇编信息:

image.png

找到钩子函数存入的寄存名为 bx,然后通过 surftrace 打印出来。


surftrace 'p dev_queue_xmit+678 pfun=%bx'


然后将 pfun 值在 /proc/kallsyms 查找匹配。

image.png

image.gif至此可以明确是 htb qdisc 导致丢包。确认相关配置存在问题后,将相关配置回退,网络性能得以恢复。

四、总结

Surftrace 在网络层面的增强,使得用户只需要有相关的网络基础和一定的内核知识储备,就可以用较低编码工作量达到精准追踪网络报文在 Linux 内核的完整处理过程。适合用于追踪 Linux 内核协议栈代码、定位深层次网络问题。


参考文献:

【1】《TCP/IP详解》

【2】《Linux内核设计与实现》

【3】《深入理解 Linux 网络技术内幕》

【4】surftrace readmde:

https://github.com/aliyun/surftrace/blob/master/ReadMe.md

【5】https://lxr.missinglinkelectronics.com


龙蜥社区系统运维 SIG 致力于打造一个集主机管理、配置部署、监控报警、异常诊断、安全审计等一系列功能的自动化运维平台。


欢迎更多开发者加入系统运维 SIG:
网址:https://openanolis.cn/sig/sysom
邮件列表:sysom@lists.openanolis.cn
surftracel链接:https://gitee.com/anolis/surftrace

—— 完 ——

加入龙蜥社群

加入微信群:添加社区助理-龙蜥社区小龙(微信:openanolis_assis),备注【龙蜥】与你同在;加入钉钉群:扫描下方钉钉群二维码。欢迎开发者/用户加入龙蜥社区(OpenAnolis)交流,共同推进龙蜥社区的发展,一起打造一个活跃的、健康的开源操作系统生态!

公众号&小龙交流群.png

关于龙蜥社区

龙蜥社区OpenAnolis)由企事业单位、高等院校、科研单位、非营利性组织、个人等在自愿、平等、开源、协作的基础上组成的非盈利性开源社区。龙蜥社区成立于 2020 年 9 月,旨在构建一个开源、中立、开放的Linux 上游发行版社区及创新平台。

龙蜥社区成立的短期目标是开发龙蜥操作系统(Anolis OS)作为 CentOS 停服后的应对方案,构建一个兼容国际 Linux 主流厂商的社区发行版。中长期目标是探索打造一个面向未来的操作系统,建立统一的开源操作系统生态,孵化创新开源项目,繁荣开源生态。

目前,龙蜥OS 8.4已发布,支持 X86_64 、Arm64、LoongArch 架构,完善适配 Intel、兆芯、鲲鹏、龙芯等芯片,并提供全栈国密支持。

欢迎下载:

https://openanolis.cn/download

加入我们,一起打造面向未来的开源操作系统!

https://openanolis.cn

相关文章
|
22小时前
|
API 调度
xenomai内核解析--双核系统调用(三)--如何为xenomai添加一个系统调用
本文介绍了如何在Xenomai中添加自定义系统调用`get_timer_hits()`,该调用用于获取CPU定时器中断次数。首先,在`syscall.h`中定义127号系统调用,并在`clock.h`和`clock.c`中声明和实现该函数。接着,更新libcobalt库以包含新接口。最后,提供了一个示例应用,演示如何在实时任务中使用此系统调用。编译内核和库后,运行示例程序显示定时器中断次数,体现了Xenomai的tickless特性。
9 1
|
22小时前
|
Linux 编译器 调度
xenomai内核解析--双核系统调用(二)--应用如何区分xenomai/linux系统调用或服务
本文介绍了如何将POSIX应用程序编译为在Xenomai实时内核上运行的程序。
12 1
xenomai内核解析--双核系统调用(二)--应用如何区分xenomai/linux系统调用或服务
|
22小时前
|
存储 Linux API
xenomai内核解析--双核系统调用(一)
本文介绍了Xenomai内核系统调用的实现,探讨了在Linux内核与Xenomai实时内核共存时,系统调用如何区分和交互。系统调用是用户空间与内核空间通信的关键,它提供了硬件抽象、系统稳定性、安全性和可移植性。在32位系统中,通过`int 0x80`指令触发,而在64位系统中,使用`syscall`指令。Xenomai通过I-pipe拦截系统调用,区分实时与非实时任务,并通过`cobalt_syscalls`表执行相应操作。文章还详细解析了系统调用表的生成和权限控制机制。
12 1
xenomai内核解析--双核系统调用(一)
|
22小时前
|
消息中间件 调度 数据安全/隐私保护
xenomai内核解析--任务同步互斥机制(一)--优先级倒置
本文是关于Xenomai实时操作系统中资源管理和优先级倒置问题的概述。Xenomai使用`xnobject`和`xnregistry`管理任务间的同步互斥资源,如信号量、互斥锁等。资源管理涉及访问控制和资源保存,确保共享资源的正确调度。文章还介绍了优先级倒置现象,即高优先级任务因低优先级任务持有资源而被阻塞。为解决此问题,Xenomai采用了优先级继承策略,临时提升低优先级任务的优先级,以防止持续的优先级反转。文章后续将深入分析`xnsynch`模块和优先级倒置解决方案。
12 1
xenomai内核解析--任务同步互斥机制(一)--优先级倒置
|
23小时前
|
机器学习/深度学习 算法 Linux
xenomai内核解析--实时内存管理--xnheap
Xenomai是一个实时操作系统(RTOS)层,用于Linux,旨在提供确定性的任务调度和服务。其内存管理机制包括一个名为xnheap的内存池,确保内存分配和释放的时间确定性,以满足硬实时系统的严格需求。
9 0
xenomai内核解析--实时内存管理--xnheap
|
23小时前
|
Linux 测试技术 API
xenomai内核解析之xenomai初探
本文是关于Xenomai实时操作系统的初探,Xenomai是一个实时性增强的Linux系统,它通过实时内核和用户空间库提供硬实时性能。Xenomai 3主要由实时内核Cobalt、实时驱动模型RTDM、用户空间库libcobalt等组成,支持两种构建实时系统的方式:Cobalt和Mercury。Cobalt在内核空间与标准Linux内核并存,通过I-Pipe处理中断,确保实时任务的执行。Mercury则是通过修改Linux内核实现。
8 0
xenomai内核解析之xenomai初探
|
23小时前
|
存储 消息中间件 Linux
xenomai内核解析--实时IPC概述
本文介绍了在Xenomai实时内核下,实时任务与非实时任务如何进行通讯
8 0
xenomai内核解析--实时IPC概述
|
23小时前
|
算法 Linux 调度
xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程
xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程
6 1
xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程
|
23小时前
|
Linux 调度 数据库
|
23小时前
|
存储 缓存 Linux
xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(三)--实时与非实时数据交互
本文介绍了Xenomai中的XDDP(Xenomai Distributed Data Protocol)通信机制,XDDP用于实时和非实时进程之间的数据交换。XDDP在Xenomai内核中涉及的数据结构和管理方式,以及创建XDDP通道后的实时端和非实时端连接过程。
6 0
xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(三)--实时与非实时数据交互

推荐镜像

更多