WEBKT

利用 eBPF 精准追踪 TCP 和 DNS 延迟,揪出网络性能瓶颈

22 0 0 0

什么是 eBPF?

eBPF 在网络性能分析中的应用

跟踪 TCP 三次握手延迟

1. 确定跟踪点

2. 编写 eBPF 程序

3. 分析数据

跟踪 DNS 查询延迟

1. 确定跟踪点

2. 编写 eBPF 程序

3. 分析数据

案例分析

总结

网络延迟是影响用户体验的关键因素之一。当网站加载缓慢、视频卡顿或者在线游戏延迟过高时,用户往往会感到沮丧。网络工程师和系统管理员需要快速定位并解决这些问题,而 eBPF(extended Berkeley Packet Filter)提供了一种强大的工具,可以深入内核追踪网络数据包,分析延迟的来源。

本文将重点介绍如何使用 eBPF 跟踪 TCP 三次握手和 DNS 查询的延迟,并利用这些数据来诊断常见的网络瓶颈。

什么是 eBPF?

eBPF 最初是 BSD 内核中的一个数据包过滤器,后来被扩展到可以运行用户自定义的沙箱程序,无需修改内核源码即可动态地向内核添加新的功能。eBPF 程序运行在内核态,但通过了严格的验证,保证安全性和稳定性。

eBPF 的强大之处在于其能够监听内核事件,例如网络数据包的收发、系统调用等。通过编写 eBPF 程序,我们可以捕获这些事件,提取关键信息,并进行实时分析。

eBPF 在网络性能分析中的应用

eBPF 提供了多种工具和框架,可以用于网络性能分析,例如:

  • 跟踪网络数据包: 捕获网络数据包,分析其头部信息,例如源 IP 地址、目标 IP 地址、端口号、协议类型等。
  • 测量延迟: 记录数据包到达和离开内核的时间戳,计算延迟。
  • 统计网络流量: 统计特定类型的数据包的数量、字节数等。
  • 监控 TCP 连接: 跟踪 TCP 连接的状态变化,例如建立连接、关闭连接等。

跟踪 TCP 三次握手延迟

TCP 三次握手是建立 TCP 连接的关键步骤。如果三次握手过程出现延迟,会导致连接建立缓慢,影响用户体验。我们可以使用 eBPF 跟踪三次握手过程,分析延迟的来源。

1. 确定跟踪点

我们需要跟踪以下内核函数:

  • tcp_v4_connect:客户端发起 SYN 包时调用。
  • tcp_v4_syn_recv:服务器收到 SYN 包时调用。
  • tcp_v4_建立连接完成的函数:客户端收到 SYN-ACK 包后,发送 ACK 包时调用 (具体函数名取决于内核版本,可以使用 trace 工具查找)。

2. 编写 eBPF 程序

以下是一个简单的 eBPF 程序,用于跟踪 TCP 三次握手延迟:

#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <net/tcp.h>
#include <linux/tcp.h>
struct data_t {
u32 pid;
u32 saddr;
u32 daddr;
u16 sport;
u16 dport;
u64 ts;
char state[8];
};
BPF_PERF_OUTPUT(events);
// 记录 SYN_SENT 的时间
int kprobe__tcp_v4_connect(struct pt_regs *ctx, struct sock *sk) {
struct data_t data = {};
data.pid = bpf_get_current_pid_tgid();
data.saddr = sk->__sk_common.skc_rcv_saddr;
data.daddr = sk->__sk_common.skc_daddr;
data.sport = sk->__sk_common.skc_num;
data.dport = sk->__sk_common.skc_dport;
data.dport = ntohs(data.dport);
data.ts = bpf_ktime_get_ns();
strcpy(data.state, "SYN_SENT");
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}
// 记录 SYN_RECV 的时间
int kprobe__tcp_v4_syn_recv(struct pt_regs *ctx, struct sock *sk, struct sk_buff *skb, const struct tcp_request_sock *req) {
struct data_t data = {};
data.pid = bpf_get_current_pid_tgid();
data.saddr = sk->__sk_common.skc_rcv_saddr;
data.daddr = sk->__sk_common.skc_daddr;
data.sport = sk->__sk_common.skc_num;
data.dport = sk->__sk_common.skc_dport;
data.dport = ntohs(data.dport);
data.ts = bpf_ktime_get_ns();
strcpy(data.state, "SYN_RECV");
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}
// 记录 ESTABLISHED 的时间 (需要根据内核版本调整函数名)
int kprobe__tcp_建立连接完成的函数(struct pt_regs *ctx, struct sock *sk) {
struct data_t data = {};
data.pid = bpf_get_current_pid_tgid();
data.saddr = sk->__sk_common.skc_rcv_saddr;
data.daddr = sk->__sk_common.skc_daddr;
data.sport = sk->__sk_common.skc_num;
data.dport = sk->__sk_common.skc_dport;
data.dport = ntohs(data.dport);
data.ts = bpf_ktime_get_ns();
strcpy(data.state, "ESTABLISHED");
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}

代码解释:

  • #include:包含必要的头文件,例如 ptrace.hsock.htcp.h,这些头文件定义了内核数据结构和函数。
  • struct data_t:定义了一个数据结构,用于存储我们需要的信息,例如 PID、源 IP 地址、目标 IP 地址、端口号、时间戳和连接状态。
  • BPF_PERF_OUTPUT(events):定义了一个 perf 事件输出,用于将数据从内核空间传递到用户空间。
  • kprobe__tcp_v4_connectkprobe__tcp_v4_syn_recvkprobe__tcp_建立连接完成的函数:定义了三个 kprobe 函数,分别用于跟踪 tcp_v4_connecttcp_v4_syn_recvtcp_建立连接完成的函数 函数的调用。在这些函数中,我们提取相关信息,并将其存储到 data_t 结构体中,然后通过 events.perf_submit 将数据发送到用户空间。

编译和加载 eBPF 程序:

可以使用 bcclibbpf 等工具来编译和加载 eBPF 程序。

3. 分析数据

在用户空间,我们可以编写程序来读取 perf 事件,并分析 TCP 三次握手的延迟。例如,我们可以计算 SYN_SENT 到 SYN_RECV 的时间差,以及 SYN_RECV 到 ESTABLISHED 的时间差,从而确定延迟的来源。

跟踪 DNS 查询延迟

DNS 查询是将域名解析为 IP 地址的过程。如果 DNS 查询延迟过高,会导致网站加载缓慢。我们可以使用 eBPF 跟踪 DNS 查询过程,分析延迟的来源。

1. 确定跟踪点

我们需要跟踪以下内核函数:

  • dns_lookup:发起 DNS 查询时调用。
  • dns_query_recv:收到 DNS 响应时调用。

2. 编写 eBPF 程序

以下是一个简单的 eBPF 程序,用于跟踪 DNS 查询延迟:

#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <net/dns.h>
struct data_t {
u32 pid;
u32 saddr;
u32 daddr;
u16 sport;
u16 dport;
u64 ts_start;
u64 ts_end;
char qname[64];
};
BPF_HASH(start, u32, struct data_t);
BPF_PERF_OUTPUT(events);
// 记录 DNS 查询开始的时间
int kprobe__dns_lookup(struct pt_regs *ctx, struct sock *sk, struct sk_buff *skb, const unsigned char *name, int len) {
u32 pid = bpf_get_current_pid_tgid();
struct data_t data = {};
data.pid = pid;
data.saddr = sk->__sk_common.skc_rcv_saddr;
data.daddr = sk->__sk_common.skc_daddr;
data.sport = sk->__sk_common.skc_num;
data.dport = sk->__sk_common.skc_dport;
data.dport = ntohs(data.dport);
data.ts_start = bpf_ktime_get_ns();
bpf_probe_read_str(data.qname, sizeof(data.qname), name);
start.update(&pid, &data);
return 0;
}
// 记录 DNS 查询结束的时间
int kprobe__dns_query_recv(struct pt_regs *ctx, struct sock *sk, struct sk_buff *skb) {
u32 pid = bpf_get_current_pid_tgid();
struct data_t *data = start.lookup(&pid);
if (data) {
data->ts_end = bpf_ktime_get_ns();
events.perf_submit(ctx, data, sizeof(struct data_t));
start.delete(&pid);
}
return 0;
}

代码解释:

  • BPF_HASH(start, u32, struct data_t):定义了一个哈希表,用于存储 DNS 查询的开始时间。
  • kprobe__dns_lookup:记录 DNS 查询开始的时间,并将数据存储到哈希表中。
  • kprobe__dns_query_recv:记录 DNS 查询结束的时间,从哈希表中读取开始时间,计算延迟,并将数据发送到用户空间。

3. 分析数据

在用户空间,我们可以编写程序来读取 perf 事件,并分析 DNS 查询的延迟。例如,我们可以统计不同域名的查询延迟,找出延迟较高的域名。

案例分析

以下是一些使用 eBPF 分析网络延迟的案例:

  • 案例 1:TCP 三次握手延迟过高
    • 使用 eBPF 跟踪 TCP 三次握手过程,发现 SYN_SENT 到 SYN_RECV 的时间差较长。
    • 进一步分析,发现服务器的 SYN backlog 队列已满,导致 SYN 包被丢弃。
    • 解决方法:调整服务器的 SYN backlog 队列大小。
  • 案例 2:DNS 查询延迟过高
    • 使用 eBPF 跟踪 DNS 查询过程,发现特定域名的查询延迟较高。
    • 进一步分析,发现 DNS 服务器的负载过高,导致查询响应缓慢。
    • 解决方法:增加 DNS 服务器的资源,或者使用 CDN 等技术来缓存 DNS 记录。

总结

eBPF 是一种强大的网络性能分析工具,可以深入内核追踪网络数据包,分析延迟的来源。通过使用 eBPF,我们可以快速定位网络瓶颈,并采取相应的措施来优化网络性能。本文介绍了如何使用 eBPF 跟踪 TCP 三次握手和 DNS 查询的延迟,并提供了一些案例分析,希望能帮助读者更好地理解和应用 eBPF 技术。

注意: 上述代码只是示例,实际应用中需要根据具体情况进行调整。同时,eBPF 程序的编写需要一定的内核知识和编程经验。建议参考相关的文档和教程,例如:

希望这篇文章能够帮助你利用 eBPF 来诊断和解决网络性能问题。

网络侦探 eBPF网络延迟TCPDNS

评论点评

打赏赞助
sponsor

感谢您的支持让我们更好的前行

分享

QRcode

https://www.webkt.com/article/10124