WEBKT

告别盲人摸象:用 eBPF 透视 Linux 网络连接全貌,揪出幕后黑手

146 0 0 0

什么是 eBPF?你的系统分析新利器

场景:揪出偷偷建立连接的“内鬼”

如何用 eBPF 监控网络连接?实战演练

1. 准备工作

2. 编写 eBPF 程序

3. 编译 eBPF 程序

4. 加载 eBPF 程序

5. 运行程序

进阶:更强大的分析能力

eBPF 的应用场景:无限可能

总结:拥抱 eBPF,提升你的运维技能

作为一名老运维,我深知服务器网络安全的重要性。每天面对海量的网络连接数据,就像大海捞针,想精准定位恶意连接,简直难如登天。传统的网络监控工具,要么性能开销太大,影响业务运行;要么只能提供粗略的信息,难以深入分析。直到我遇到了 eBPF,才发现原来 Linux 系统里还藏着这么一个强大的“透视镜”。

什么是 eBPF?你的系统分析新利器

eBPF (extended Berkeley Packet Filter) 是一种革命性的内核技术,它允许你在内核中安全地运行自定义代码,而无需修改内核源码或加载内核模块。你可以把它想象成一个“内核探针”,可以实时监测和分析系统中的各种事件,例如网络连接、系统调用、函数执行等等。而且,eBPF 代码运行在沙箱环境中,即使出现问题也不会影响内核的稳定性。

为什么 eBPF 这么牛?

  • 高性能: eBPF 代码运行在内核中,避免了用户态和内核态之间频繁切换的开销,性能非常高。
  • 安全: eBPF 代码经过严格的验证,确保其不会崩溃或恶意修改内核数据。
  • 灵活: 你可以使用 C 语言编写 eBPF 代码,然后使用 LLVM 编译成 BPF 字节码,加载到内核中运行。

场景:揪出偷偷建立连接的“内鬼”

假设你是一名系统管理员,负责维护一个重要的服务器。最近你发现服务器的网络流量异常,怀疑有恶意程序在偷偷建立连接,窃取数据。传统的排查方法可能需要你登录服务器,使用 tcpdump 抓包,然后分析大量的网络数据包。这种方法效率低下,而且容易遗漏关键信息。

现在,有了 eBPF,你可以编写一个简单的 eBPF 程序,实时监测系统中所有进程的网络连接行为,识别出哪些进程正在建立新的连接,以及它们连接的目标 IP 地址和端口。这样,你就可以快速定位到可疑的进程,并采取相应的措施。

如何用 eBPF 监控网络连接?实战演练

下面,我将分享一个使用 eBPF 监控网络连接的示例程序,帮助你了解 eBPF 的基本原理和使用方法。

1. 准备工作

  • 安装 bpftool: bpftool 是一个用于管理 eBPF 程序的命令行工具,你可以使用它来加载、卸载和查看 eBPF 程序的信息。在 Ubuntu 上,你可以使用以下命令安装 bpftool:

    sudo apt-get update
    sudo apt-get install bpftool
  • 安装 libbpf: libbpf 是一个用于开发 eBPF 程序的 C 语言库,它提供了一些方便的 API,可以简化 eBPF 程序的开发。你可以从 libbpf 的 GitHub 仓库下载源码,然后编译安装。

    git clone https://github.com/libbpf/libbpf.git
    cd libbpf
    make
    sudo make install
  • 安装 clang 和 llvm: clang 和 llvm 是用于编译 eBPF 程序的编译器和工具链,你需要安装它们才能将 C 语言代码编译成 BPF 字节码。在 Ubuntu 上,你可以使用以下命令安装 clang 和 llvm:

    sudo apt-get install clang llvm
    

2. 编写 eBPF 程序

下面是一个简单的 eBPF 程序,用于监控网络连接的建立:

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include <linux/socket.h>
#include <linux/tcp.h>
#include <linux/ip.h>
char LICENSE[] SEC("license") = "Dual BSD/GPL";
// 定义一个 BPF 映射,用于存储网络连接的信息
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(key_size, sizeof(int));
__uint(value_size, sizeof(struct conn_info));
__uint(max_entries, 1024);
} connections SEC(".maps");
// 定义一个结构体,用于存储网络连接的信息
struct conn_info {
__u32 pid;
__u32 saddr;
__u32 daddr;
__u16 sport;
__u16 dport;
};
// 定义一个 BPF 探针函数,用于在 TCP 连接建立时被调用
SEC("kprobe/tcp_v4_connect")
int BPF_KPROBE(tcp_v4_connect, struct sock *sk) {
int pid = bpf_get_current_pid_tgid() >> 32;
struct conn_info conn = {};
// 获取进程 ID
conn.pid = pid;
// 获取源 IP 地址和端口
conn.saddr = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);
conn.sport = BPF_CORE_READ(sk, __sk_common.skc_num);
// 获取目标 IP 地址和端口
conn.daddr = BPF_CORE_READ(sk, __sk_common.skc_daddr);
conn.dport = BPF_CORE_READ(sk, __sk_common.skc_dport);
// 将网络连接的信息存储到 BPF 映射中
bpf_map_update_elem(&connections, &pid, &conn, BPF_ANY);
return 0;
}
// 定义一个 BPF 探针函数,用于在 TCP 连接关闭时被调用
SEC("kretprobe/tcp_v4_connect")
int BPF_KRETPROBE(tcp_v4_connect_ret, int ret) {
int pid = bpf_get_current_pid_tgid() >> 32;
// 从 BPF 映射中删除网络连接的信息
bpf_map_delete_elem(&connections, &pid);
return 0;
}

代码解释:

  • SEC("kprobe/tcp_v4_connect"):定义一个 BPF 探针函数,用于在 tcp_v4_connect 函数被调用时执行。tcp_v4_connect 函数是 Linux 内核中用于建立 TCP 连接的函数。
  • SEC("kretprobe/tcp_v4_connect"):定义一个 BPF 返回探针函数,用于在 tcp_v4_connect 函数返回时执行。
  • bpf_get_current_pid_tgid():获取当前进程的 PID 和 TGID。
  • BPF_CORE_READ():从内核数据结构中读取数据。例如,BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr) 用于读取 sock 结构体中的源 IP 地址。
  • bpf_map_update_elem():将数据更新到 BPF 映射中。
  • bpf_map_delete_elem():从 BPF 映射中删除数据。

3. 编译 eBPF 程序

将上面的 C 语言代码保存为 connect_monitor.c 文件,然后使用以下命令编译成 BPF 字节码:

clang -target bpf -D__TARGET_ARCH_x86_64 -O2 -Wall -Werror -c connect_monitor.c -o connect_monitor.o

4. 加载 eBPF 程序

编写一个用户态程序,用于加载 eBPF 程序到内核中,并从 BPF 映射中读取数据:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#define MAP_PATH "/sys/fs/bpf/connections"
struct conn_info {
__u32 pid;
__u32 saddr;
__u32 daddr;
__u16 sport;
__u16 dport;
};
static int map_fd;
static void cleanup(int sig)
{
close(map_fd);
exit(0);
}
int main(int argc, char **argv)
{
struct rlimit rlim_new = {
.rlim_cur = RLIM_INFINITY,
.rlim_max = RLIM_INFINITY,
};
if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) {
perror("setrlimit");
exit(1);
}
signal(SIGINT, cleanup);
signal(SIGTERM, cleanup);
// 加载 eBPF 程序
struct bpf_object *obj = bpf_object__open_file("connect_monitor.o", NULL);
if (!obj) {
perror("bpf_object__open_file");
exit(1);
}
int err = bpf_object__load(obj);
if (err) {
perror("bpf_object__load");
exit(1);
}
// 获取 BPF 映射的 FD
map_fd = bpf_object__find_map_fd_by_name(obj, "connections");
if (map_fd < 0) {
perror("bpf_object__find_map_fd_by_name");
exit(1);
}
printf("Monitoring network connections...
");
// 循环读取 BPF 映射中的数据
while (1) {
int key = 0;
struct conn_info conn;
__u32 next_key;
// 遍历 BPF 映射
while (bpf_map_get_next_key(map_fd, &key, &next_key) == 0) {
err = bpf_map_lookup_elem(map_fd, &next_key, &conn);
if (err) {
perror("bpf_map_lookup_elem");
exit(1);
}
// 打印网络连接的信息
printf("PID: %d, Source: %u.%u.%u.%u:%d, Destination: %u.%u.%u.%u:%d
",
conn.pid,
(conn.saddr >> 0) & 0xFF, (conn.saddr >> 8) & 0xFF, (conn.saddr >> 16) & 0xFF, (conn.saddr >> 24) & 0xFF,
conn.sport,
(conn.daddr >> 0) & 0xFF, (conn.daddr >> 8) & 0xFF, (conn.daddr >> 16) & 0xFF, (conn.daddr >> 24) & 0xFF,
conn.dport);
key = next_key;
}
sleep(1);
}
return 0;
}

代码解释:

  • bpf_object__open_file():打开 eBPF 程序的目标文件。
  • bpf_object__load():将 eBPF 程序加载到内核中。
  • bpf_object__find_map_fd_by_name():根据 BPF 映射的名称查找其 FD。
  • bpf_map_get_next_key():获取 BPF 映射中的下一个 key。
  • bpf_map_lookup_elem():根据 key 从 BPF 映射中查找数据。

将上面的 C 语言代码保存为 main.c 文件,然后使用以下命令编译:

gcc main.c -o main -lbpf

5. 运行程序

首先,运行 connect_monitor.c 编译出的 connect_monitor.o 文件,再运行 main.c 编译出的 main 程序:

sudo ./main

现在,你可以看到程序正在实时打印系统中所有进程的网络连接信息。当你使用浏览器访问一个网站时,你就可以在终端中看到浏览器进程建立连接的信息。

进阶:更强大的分析能力

上面的示例程序只是一个简单的演示,你可以根据自己的需求,编写更复杂的 eBPF 程序,实现更强大的分析能力。

  • 监控指定进程的网络连接: 你可以在 eBPF 程序中添加过滤条件,只监控指定进程的网络连接行为。
  • 统计网络流量: 你可以使用 eBPF 程序统计每个进程的网络流量,找出占用带宽最多的进程。
  • 检测恶意连接: 你可以编写 eBPF 程序,检测与恶意 IP 地址或端口建立的连接,及时发现潜在的安全威胁。

eBPF 的应用场景:无限可能

eBPF 的应用场景非常广泛,除了网络监控之外,还可以用于性能分析、安全审计、容器监控等等。例如:

  • 性能分析: 你可以使用 eBPF 监测函数的执行时间,找出性能瓶颈。
  • 安全审计: 你可以使用 eBPF 记录系统调用,审计用户的行为。
  • 容器监控: 你可以使用 eBPF 监控容器的网络和文件系统活动。

总结:拥抱 eBPF,提升你的运维技能

eBPF 是一项非常强大的内核技术,它可以帮助你深入了解系统的运行状态,解决各种疑难问题。虽然 eBPF 的学习曲线比较陡峭,但是只要你掌握了基本原理和使用方法,就可以利用它来提升你的运维技能,成为一名更优秀的系统管理员。

希望这篇文章能够帮助你入门 eBPF,并在实际工作中应用 eBPF 技术。记住,实践是最好的老师,多动手尝试,你一定能够掌握 eBPF 的精髓。

Linux大玩家 eBPF网络监控系统安全

评论点评

打赏赞助
sponsor

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

分享

QRcode

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