WEBKT

网络工程师的eBPF利器-实时网络连接监控工具开发指南

50 0 0 0

网络工程师的eBPF利器-实时网络连接监控工具开发指南

作为一名网络工程师,你是否经常面临以下挑战?

  • 网络流量异常难以追踪:面对突如其来的网络拥堵或攻击,传统的监控工具往往无法提供足够精细的数据,让你难以快速定位问题根源。
  • 故障诊断效率低下:排查网络故障时,需要在大量的日志和数据中大海捞针,耗时费力,影响业务连续性。
  • 安全威胁难以发现:恶意软件或黑客攻击往往伪装成正常流量,传统的安全设备难以有效识别,导致安全风险。

如果你的答案是肯定的,那么基于 eBPF (Extended Berkeley Packet Filter) 技术的实时网络连接监控工具将是你的得力助手。它能够实时捕获和分析网络数据包,提供细粒度的网络连接信息,帮助你快速诊断故障、优化网络性能和提升安全防护能力。

为什么选择 eBPF?

eBPF 是一种革命性的内核技术,它允许用户在内核中安全地运行自定义代码,而无需修改内核源代码或加载内核模块。这使得 eBPF 具有以下优势?

  • 高性能:eBPF 程序运行在内核态,可以直接访问网络数据包,避免了用户态和内核态之间的数据拷贝,大大提高了性能。
  • 低开销:eBPF 程序运行在虚拟机中,可以有效地隔离用户代码和内核,避免了用户代码崩溃导致内核崩溃的风险。
  • 灵活性:eBPF 允许用户自定义监控逻辑,可以根据实际需求定制监控指标和策略,满足各种复杂的网络监控场景。

本文将手把手教你使用 eBPF 开发一个实时网络连接监控工具,让你能够:

  • 实时显示系统中所有网络连接的信息,包括源 IP 地址、目标 IP 地址、端口号、协议类型等。
  • 根据连接状态、流量大小等指标对连接进行过滤和排序。
  • 将监控数据导出到文件或数据库,方便后续分析和告警。

准备工作

在开始之前,你需要准备以下环境?

  • Linux 系统:建议使用 Ubuntu 18.04 或更高版本,内核版本需要支持 eBPF (4.14 或更高)。
  • bcc 工具:bcc (BPF Compiler Collection) 是一套用于创建 eBPF 程序的工具,提供了 Python 绑定,方便用户编写和调试 eBPF 程序。
  • Python 3:用于编写用户态程序,与 eBPF 程序进行交互。

你可以使用以下命令安装 bcc 工具?

sudo apt-get update
sudo apt-get install bpfcc-tools linux-headers-$(uname -r)
sudo apt-get install python3-pip
sudo pip3 install pyroute2

eBPF 程序开发

首先,我们需要编写 eBPF 程序,用于捕获网络数据包并提取连接信息。以下是一个简单的 eBPF 程序示例?

#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>
struct key_t {
u32 saddr;
u32 daddr;
u16 sport;
u16 dport;
u8 protocol;
};
BPF_HASH(connections, struct key_t, u64);
int kprobe__tcp_v4_connect(struct pt_regs *ctx, struct sock *sk) {
struct key_t key = {};
key.saddr = sk->__sk_common.skc_rcv_saddr;
key.daddr = sk->__sk_common.skc_daddr;
key.sport = sk->__sk_common.skc_num;
key.dport = sk->__sk_common.skc_dport;
key.protocol = IPPROTO_TCP;
u64 ts = bpf_ktime_get_ns();
connections.update(&key, &ts);
return 0;
}
int kprobe__tcp_v4_disconnect(struct pt_regs *ctx, struct sock *sk, int reason) {
struct key_t key = {};
key.saddr = sk->__sk_common.skc_rcv_saddr;
key.daddr = sk->__sk_common.skc_daddr;
key.sport = sk->__sk_common.skc_num;
key.dport = sk->__sk_common.skc_dport;
key.protocol = IPPROTO_TCP;
connections.delete(&key);
return 0;
}
int kprobe__inet_csk_accept(struct pt_regs *ctx, struct sock *sk) {
struct sock *newsk = (struct sock *)ctx->di;
struct key_t key = {};
key.saddr = newsk->__sk_common.skc_rcv_saddr;
key.daddr = newsk->__sk_common.skc_daddr;
key.sport = newsk->__sk_common.skc_num;
key.dport = newsk->__sk_common.skc_dport;
key.protocol = IPPROTO_TCP;
u64 ts = bpf_ktime_get_ns();
connections.update(&key, &ts);
return 0;
}

这个 eBPF 程序使用了 kprobes 技术,在 tcp_v4_connecttcp_v4_disconnectinet_csk_accept 函数被调用时执行。它会提取连接的源 IP 地址、目标 IP 地址、端口号和协议类型等信息,并将这些信息存储在 eBPF 的哈希表中。

代码解释

  • #include <uapi/linux/ptrace.h>:包含 ptrace 相关的头文件,用于 kprobes 技术。
  • #include <net/sock.h>:包含 socket 相关的头文件,用于获取连接信息。
  • #include <bcc/proto.h>:包含 bcc 相关的头文件,用于定义 eBPF 数据结构和函数。
  • struct key_t:定义连接信息的结构体,包括源 IP 地址、目标 IP 地址、端口号和协议类型。
  • BPF_HASH(connections, struct key_t, u64):定义 eBPF 的哈希表,用于存储连接信息,key 为 struct key_t,value 为时间戳。
  • kprobe__tcp_v4_connect(struct pt_regs *ctx, struct sock *sk):kprobe 函数,在 tcp_v4_connect 函数被调用时执行,用于捕获 TCP 连接的建立事件。
  • kprobe__tcp_v4_disconnect(struct pt_regs *ctx, struct sock *sk, int reason):kprobe 函数,在 tcp_v4_disconnect 函数被调用时执行,用于捕获 TCP 连接的断开事件。
  • kprobe__inet_csk_accept(struct pt_regs *ctx, struct sock *sk):kprobe 函数,在 inet_csk_accept 函数被调用时执行,用于捕获 TCP 连接的accept事件。
  • bpf_ktime_get_ns():获取当前时间戳。
  • connections.update(&key, &ts):将连接信息和时间戳存储到哈希表中。
  • connections.delete(&key):从哈希表中删除连接信息。

用户态程序开发

接下来,我们需要编写用户态程序,用于加载 eBPF 程序,从 eBPF 的哈希表中读取连接信息,并将这些信息显示在终端上。以下是一个简单的 Python 程序示例?

from bcc import BPF
import socket
import struct
# 加载 eBPF 程序
program = """
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>
struct key_t {
u32 saddr;
u32 daddr;
u16 sport;
u16 dport;
u8 protocol;
};
BPF_HASH(connections, struct key_t, u64);
int kprobe__tcp_v4_connect(struct pt_regs *ctx, struct sock *sk) {
struct key_t key = {};
key.saddr = sk->__sk_common.skc_rcv_saddr;
key.daddr = sk->__sk_common.skc_daddr;
key.sport = sk->__sk_common.skc_num;
key.dport = sk->__sk_common.skc_dport;
key.protocol = IPPROTO_TCP;
u64 ts = bpf_ktime_get_ns();
connections.update(&key, &ts);
return 0;
}
int kprobe__tcp_v4_disconnect(struct pt_regs *ctx, struct sock *sk, int reason) {
struct key_t key = {};
key.saddr = sk->__sk_common.skc_rcv_saddr;
key.daddr = sk->__sk_common.skc_daddr;
key.sport = sk->__sk_common.skc_num;
key.dport = sk->__sk_common.skc_dport;
key.protocol = IPPROTO_TCP;
connections.delete(&key);
return 0;
}
int kprobe__inet_csk_accept(struct pt_regs *ctx, struct sock *sk) {
struct sock *newsk = (struct sock *)ctx->di;
struct key_t key = {};
key.saddr = newsk->__sk_common.skc_rcv_saddr;
key.daddr = newsk->__sk_common.skc_daddr;
key.sport = newsk->__sk_common.skc_num;
key.dport = newsk->__sk_common.skc_dport;
key.protocol = IPPROTO_TCP;
u64 ts = bpf_ktime_get_ns();
connections.update(&key, &ts);
return 0;
}
""
b = BPF(text=program)
# 打印连接信息
while True:
try:
for key, ts in b["connections"].items():
saddr = socket.inet_ntoa(struct.pack("<I", key.saddr))
daddr = socket.inet_ntoa(struct.pack("<I", key.daddr))
sport = key.sport
dport = key.dport
protocol = key.protocol
print(f"源IP: {saddr}, 目标IP: {daddr}, 源端口: {sport}, 目标端口: {dport}, 协议: {protocol}")
b["connections"].clear()
sleep(1)
except KeyboardInterrupt:
exit()

代码解释

  • from bcc import BPF:导入 bcc 模块,用于加载 eBPF 程序。
  • import socket:导入 socket 模块,用于将 IP 地址转换为字符串。
  • import struct:导入 struct 模块,用于将整数转换为字节流。
  • program = """...""":定义 eBPF 程序的字符串。
  • b = BPF(text=program):加载 eBPF 程序。
  • b["connections"].items():获取 eBPF 哈希表中的所有键值对。
  • socket.inet_ntoa(struct.pack("<I", key.saddr)):将 IP 地址转换为字符串。
  • print(f"源IP: {saddr}, 目标IP: {daddr}, 源端口: {sport}, 目标端口: {dport}, 协议: {protocol}"):打印连接信息。
  • b["connections"].clear():清空 eBPF 哈希表。
  • sleep(1):休眠 1 秒。
  • except KeyboardInterrupt: exit():捕获键盘中断信号,退出程序。

运行程序

将 eBPF 程序保存为 connect_monitor.c,将 Python 程序保存为 connect_monitor.py,然后执行以下命令?

sudo python3 connect_monitor.py

你将看到终端上实时显示的网络连接信息。

功能增强

以上只是一个简单的示例,你可以根据实际需求对该工具进行功能增强?

  • 增加过滤功能:可以根据 IP 地址、端口号、协议类型等条件对连接进行过滤。
  • 增加排序功能:可以根据连接状态、流量大小等指标对连接进行排序。
  • 增加统计功能:可以统计每个 IP 地址或端口号的连接数、流量大小等指标。
  • 将监控数据导出到文件或数据库:方便后续分析和告警。
  • 增加可视化界面:使用 Web 技术将监控数据以图表的形式展示出来,更加直观。

进阶:TCP状态跟踪

上述例子只是简单地展示了连接的建立和断开。为了更全面地监控TCP连接,我们需要跟踪TCP状态的变化。可以利用eBPF attach到tcp_probe函数上,这个函数在TCP状态发生变化时会被调用。

int kprobe__tcp_probe(struct pt_regs *ctx, struct sock *sk) {
struct key_t key = {};
key.saddr = sk->__sk_common.skc_rcv_saddr;
key.daddr = sk->__sk_common.skc_daddr;
key.sport = sk->__sk_common.skc_num;
key.dport = sk->__sk_common.skc_dport;
key.protocol = IPPROTO_TCP;
u32 state = sk->sk_state;
// 根据TCP状态进行处理,例如更新状态信息到哈希表
states.update(&key, &state);
return 0;
}

安全注意事项

  • 权限控制:运行 eBPF 程序需要 root 权限,请确保只授权给信任的用户。
  • 程序审计:在生产环境中使用 eBPF 程序之前,请务必进行充分的测试和审计,确保程序的安全性和稳定性。
  • 资源限制:eBPF 程序会消耗系统资源,请合理设置程序的资源限制,避免影响系统性能。

总结

基于 eBPF 技术的实时网络连接监控工具可以帮助网络工程师快速诊断故障、优化网络性能和提升安全防护能力。本文提供了一个简单的示例,你可以根据实际需求进行功能增强,打造一款属于自己的网络监控利器。

希望本文能够帮助你入门 eBPF 技术,并在实际工作中发挥它的强大威力!

网络游侠 eBPF网络监控性能分析

评论点评

打赏赞助
sponsor

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

分享

QRcode

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