龙蜥开源内核追踪利器 Surftrace:协议包解析效率提升 10 倍! | 龙蜥技术
简介:如何将网络报文与内核协议栈清晰关联起来精准追踪到关注的报文行进路径呢?
文/系统运维 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 详解》卷一。
如上图所示,网络报文对数据报文数据在不同层进行封装。不同 OS 均采用一致的报文封装方式,达到跨软件平台通讯的目的。
1.2 sk_buff 结构体
sk_buff 是网络报文在 Linux 内核中的实际承载者,它在 include/linux/skbuff.h 文件中定义,结构体成员较多,本文不逐一展开。
用户需要重点关注下面两个结构体成员:
unsignedchar *head, *data;
其中 head 指向了缓冲区开始,data 指向了当前报文处理所在协议层的起始位置,如当前协议处理位于 tcp 层,data 指针就会指向 struct tcphdr。在 IP 层,则指向了struct iphdr。因此,data 指针成员,是报文在内核处理过程中的关键信标。
1.3 内核网络协议栈地图
下图是协议栈处理地图,可以保存后放大观看(图源网络)。

不难发现,上图中几乎所有函数都涉及到 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。
tips:
- 可以跨协议层向上解析报文结构体,如在 l3 层去分析 struct icmphdr 中的数据成员
- 不可以跨协议层向下解析报文结构体,如在 l4 层去分析 struct iphdr 中的成员
2.2 扩充下一层报文内容获取方式
surftrace 为 ethhdr、iphdr、icmphdr、udphdr、tcphdr 结构体添加了 xdata 成员,用于获取下一层报文内容。xdata 有以下 5 类类型:

编辑
数组下标是按照位宽进行对齐的,比如要提取 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 通过往变量增加前缀的方式,在数据呈现以及过滤的时候,将原始数据根据前缀命名规则进行转换,提升可读性和便利性。

编辑
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% 左右。
通过进一步抓包分析,可以明确报文丢失在实例 B 内部。
通过检查 /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
可以获取到以下调用栈:
由于问题复现概率比较高,我们可以将怀疑的重点方向先放在包发送流程中,即从 icmp_echo 函数往上,用 Surftrace 在每一个符号都加一个 trace 点,追踪下回包到底消失在哪里。

3.3 锁定丢包点
问题追踪到了这里,对于经验丰富的同学应该是可以猜出丢包原因。我们不妨纯粹从代码角度出发,再找一下准确的丢包位置。结合代码分析,我们可以在函数内部找到以下两处 drop 点:
通过 Surftrace 函数内部追踪功能,结合汇编代码信息,可以明确丢包点是出在了 qdisc->enqueue 钩子函数中。
rc = q->enqueue(skb, q, &to_free) & NET_XMIT_MASK;
此时,可以结合汇编信息:
找到钩子函数存入的寄存名为 bx,然后通过 surftrace 打印出来。
surftrace 'p dev_queue_xmit+678 pfun=%bx'
然后将 pfun 值在 /proc/kallsyms 查找匹配。
至此可以明确是 htb qdisc 导致丢包。确认相关配置存在问题后,将相关配置回退,网络性能得以恢复。
四、总结
Surftrace 在网络层面的增强,使得用户只需要有相关的网络基础和一定的内核知识储备,就可以用较低编码工作量达到精准追踪网络报文在 Linux 内核的完整处理过程。适合用于追踪 Linux 内核协议栈代码、定位深层次网络问题。
参考文献:
【1】《TCP/IP详解》
【2】《Linux内核设计与实现》
【3】《深入理解 Linux 网络技术内幕》
【4】surftrace readmde:
surftrace/ReadMe.md at master · aliyun/surftrace · GitHub
【5】https://lxr.missinglinkelectronics.com
本文为阿里云原创内容,未经允许不得转载。
龙蜥开源内核追踪利器 Surftrace:协议包解析效率提升 10 倍! | 龙蜥技术的更多相关文章
- MingQQ v1.0高仿版开源了,使用WebQQ协议实现了QQ客户端基本的聊天功能...
MingQQ v1.0高仿版开源了,使用WebQQ协议实现了QQ客户端基本的聊天功能... MingQQ目前支持的功能如下:1.支持普通方式登录.验证码方式登录.注销.保持在线.改变在线状态.2.支持 ...
- 【干货】快速部署微软开源GPU管理利器: OpenPAI
[干货]快速部署微软开源GPU管理利器: OpenPAI 介绍 不管是机器学习的老手,还是入门的新人,都应该装备上尽可能强大的算力.除此之外,还要压榨出硬件的所有潜力来加快模型训练.OpenPAI作为 ...
- 【开源GPS追踪】 之 为何费力不讨好
GPS追踪,在X宝上一搜一大堆,价格几十到几百层次不齐,为何还要自己开发? 1 对我来说,就是手头有这些硬件资源(GPRS GPS MCU)以及软件资源(VPS),算闲的蛋疼,其实不然,本人工作也很忙 ...
- linux内核追踪(trace)(QEMU+gdb)
1.引言 Linux内核是一个很大的模块,如果只是看源码有时会难以理解Linux内核的一些代码设计情况,如果可以结合Linux内核运行同时阅读源码再好不过,本文大致介绍Linux内核追踪方式,采用工具 ...
- [转帖]国科微发布纯正国产SSD主控 龙芯IP内核,速度可达500MB/s
国科微发布纯正国产SSD主控龙芯IP内核,速度可达500MB/s https://www.expreview.com/68071.html 自主内核 龙芯处理器. 2019.4. 在存储芯片领域,中国 ...
- 龙芯开源社区上线.NET主页
龙芯团队从2019年7 月份开始着手.NET Core的MIPS64支持研发,经过将近一年的研发,在2020年6月18日完成了里程碑性的工作,在github CoreCLR 仓库:https://gi ...
- ETL利器Kettle实战应用解析系列一【Kettle使用介绍】
本系列文章主要索引如下: 一.ETL利器Kettle实战应用解析系列一[Kettle使用介绍] 二.ETL利器Kettle实战应用解析系列二 [应用场景和实战DEMO下载] 三.ETL利器Kettle ...
- (转)rtmp协议简单解析以及用其发送h264的flv文件
Adobe公司太坑人了,官方文档公布的信息根本就不全,如果只按照他上面的写的话,是没法用的.按照文档上面的流程,server和client连接之后首先要进行握手,握手成功之后进行一些交互,其实就是交互 ...
- (转载)ETL利器Kettle实战应用解析系列一【Kettle使用介绍】
http://www.cnblogs.com/limengqiang/archive/2013/01/16/kettleapply1.html ETL利器Kettle实战应用解析系列一[Kettle使 ...
- 阿里巴巴开源项目:分布式数据库同步系统otter(解决中美异地机房) - agapple - ITeye技术网站
阿里巴巴开源项目:分布式数据库同步系统otter(解决中美异地机房) - agapple - ITeye技术网站 阿里巴巴开源项目:分布式数据库同步系统otter(解决中美异地机房)
随机推荐
- day03-分析SpringBoot底层机制
分析SpringBoot底层机制 Tomcat启动分析,Spring容器初始化,Tomcat如何关联Spring容器? 1.创建SpringBoot环境 (1)创建Maven程序,创建SpringBo ...
- MyBatis Java 和 Mysql数据库 数据类型对应表
类型处理器(typeHandlers) MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Jav ...
- 【732. 我的日程安排表 III】差分数组
class MyCalendarThree { private TreeMap<Integer, Integer> cnt; public MyCalendarThree() { cnt ...
- find、grep、sed、awk命令(总结)
find.grep.sed.awk命令(总结) 大纲 *一.常见系统特殊符号* *(一)基础符号系列* *1)美元符号 $* *2)叹号符号 !* *3)竖线符号 |* *4)井号符号 #* *(二) ...
- 深度解读UUID:结构、原理以及生成机制
What 是 UUID UUID (Universally Unique IDentifier) 通用唯一识别码 ,也称为 GUID (Globally Unique IDentifier) 全球唯一 ...
- 记录--Vue开发历程---音乐播放器
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.audio标签的使用 1.Audio 对象属性 2.对象方法 二.效果 效果如下: 三.代码 代码如下: MusicPlayer.vu ...
- axios封装(处理token跟get中Content-Type的请求问题)
axios封装 import axios from 'axios' //引入axios import store from '@/store/index' //引入store //此处引入router ...
- KingbaseES Json 系列七:Json记录操作函数二
KingbaseES Json 系列七--Json记录操作函数二(JSONB_POPULATE_RECORD,JSONB_POPULATE_RECORDSET,JSON_POPULATE_RECORD ...
- archlinux 安装后xfce没有声音,声音无法调节
参照 http://ivo-wang.github.io/2018/02/17/fix/ sudo pacman -S alsa-utils pavucontrol sudo pacman -S pi ...
- 论文阅读小结(B/S和C/S结构)
论文阅读小结 一.B/S 和 C/S 软件体系结构选择 1) C/S . B/S 结构概述 C/S 结构,即 Client/Server (客户机 / 服务器 ), C/S 结构软件分为客户机和服务器 ...