WEBKT

使用eBPF在内核空间构建DDoS攻击检测与自动防御系统

24 0 0 0

1. eBPF简介

2. DDoS攻击检测原理

3. eBPF程序设计

3.1 数据结构定义

3.2 eBPF程序代码

3.3 代码解释

4. 用户空间程序

4.1 加载eBPF程序

4.2 代码解释

5. 编译和运行

5.1 编译eBPF程序

5.2 生成BPF骨架代码

5.3 编译用户空间程序

5.4 运行程序

6. 测试

7. 总结

DDoS(分布式拒绝服务)攻击是常见的网络安全威胁,攻击者通过控制大量“肉鸡”向目标服务器发送海量请求,导致服务器资源耗尽,无法正常提供服务。传统的DDoS防御方案通常依赖于部署在网络边缘的硬件设备或云服务,但这些方案往往成本高昂,且存在一定的延迟。eBPF(扩展的伯克利包过滤器)作为一种强大的内核技术,允许开发者在内核空间安全高效地运行自定义代码,为DDoS防御提供了新的思路。

本文将介绍如何使用eBPF程序在Linux内核中实现一个简易的DDoS攻击检测器,并能够自动阻断恶意流量。该检测器通过监控网络流量,识别异常流量模式,并采取相应的防御措施。

1. eBPF简介

eBPF 是一种革命性的内核技术,它允许用户在内核中安全地运行自定义代码,而无需修改内核源代码或加载内核模块。eBPF程序通常由用户空间的应用程序加载到内核中,并在特定的事件发生时被触发,例如网络数据包到达或系统调用发生。

eBPF程序具有以下优点:

  • 安全: eBPF程序在加载到内核之前会经过严格的验证,确保程序的安全性和稳定性。
  • 高效: eBPF程序在内核空间运行,避免了用户空间和内核空间之间频繁的数据拷贝和上下文切换,提高了性能。
  • 灵活: eBPF程序可以用于各种用途,例如网络监控、性能分析、安全审计等。

2. DDoS攻击检测原理

DDoS攻击通常具有以下特征:

  • 高流量: 攻击者会发送大量的请求,导致网络流量异常增加。
  • 源地址欺骗: 攻击者可能会伪造源IP地址,增加追踪难度。
  • 请求模式异常: 攻击者可能会发送特定类型的请求,例如SYN flood或UDP flood。

基于以上特征,我们可以通过以下方式检测DDoS攻击:

  • 流量监控: 监控网络流量,检测流量是否超过预设的阈值。
  • 源IP地址分析: 分析源IP地址的分布,检测是否存在大量的来自同一IP地址的请求。
  • 请求类型分析: 分析请求的类型,检测是否存在特定类型的攻击请求。

3. eBPF程序设计

我们的eBPF程序将实现以下功能:

  • 流量监控: 统计每个源IP地址的请求数量。
  • 异常检测: 检测源IP地址的请求数量是否超过预设的阈值。
  • 流量阻断: 阻断来自异常IP地址的流量。

3.1 数据结构定义

我们需要定义以下数据结构:

  • ip_counts: 用于存储每个源IP地址的请求数量,使用eBPF的map数据结构实现。
  • blocked_ips: 用于存储被阻断的IP地址,也使用eBPF的map数据结构实现。
// 定义IP地址计数map
BPF_MAP_DEF(ip_counts, LRU_HASH, __u32, __u64, 65536, 0);
BPF_MAP_ADD(ip_counts);
// 定义被阻止IP地址的map
BPF_MAP_DEF(blocked_ips, HASH, __u32, __u8, 65536, 0);
BPF_MAP_ADD(blocked_ips);
// 定义配置结构体
struct config {
__u64 threshold; // 流量阈值
};
BPF_MAP_DEF(config_map, ARRAY, __u32, struct config, 1, 0);
BPF_MAP_ADD(config_map);

3.2 eBPF程序代码

eBPF程序将挂载到tc(Traffic Control)的ingress(入站) hook点,对每个进入的网络数据包进行处理。

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#define MAX_TCP_OPTIONS_SIZE 40
// 定义IP地址计数map
BPF_MAP_DEF(ip_counts, LRU_HASH, __u32, __u64, 65536, 0);
BPF_MAP_ADD(ip_counts);
// 定义被阻止IP地址的map
BPF_MAP_DEF(blocked_ips, HASH, __u32, __u8, 65536, 0);
BPF_MAP_ADD(blocked_ips);
// 定义配置结构体
struct config {
__u64 threshold; // 流量阈值
};
BPF_MAP_DEF(config_map, ARRAY, __u32, struct config, 1, 0);
BPF_MAP_ADD(config_map);
SEC("ingress")
int handle_ingress(struct __sk_buff *skb) {
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
// 以太网头部
struct ethhdr *eth = data;
if (data + sizeof(struct ethhdr) > data_end)
return TC_ACT_OK;
// 只处理IP数据包
if (eth->h_proto != bpf_htons(ETH_P_IP))
return TC_ACT_OK;
// IP头部
struct iphdr *iph = data + sizeof(struct ethhdr);
if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) > data_end)
return TC_ACT_OK;
__u32 src_ip = iph->saddr;
// 获取配置
__u32 key = 0;
struct config *cfg = bpf_map_lookup_elem(&config_map, &key);
if (!cfg) {
// 默认阈值
return TC_ACT_OK;
}
// 检查IP是否被阻止
if (bpf_map_lookup_elem(&blocked_ips, &src_ip)) {
// 如果IP被阻止,则丢弃数据包
return TC_ACT_DROP;
}
// 增加IP地址的计数
__u64 *count = bpf_map_lookup_elem(&ip_counts, &src_ip);
if (count) {
(*count)++;
} else {
__u64 init_count = 1;
bpf_map_update_elem(&ip_counts, &src_ip, &init_count, BPF_ANY);
}
// 检查是否超过阈值
count = bpf_map_lookup_elem(&ip_counts, &src_ip);
if (count && *count > cfg->threshold) {
// 超过阈值,阻止该IP地址
__u8 blocked = 1;
bpf_map_update_elem(&blocked_ips, &src_ip, &blocked, BPF_ANY);
bpf_printk("DDoS detected, blocking IP: %x\n", src_ip);
return TC_ACT_DROP; // 丢弃数据包
}
return TC_ACT_OK; // 允许数据包通过
}
char _license[] SEC("license") = "GPL";

3.3 代码解释

  1. 包含头文件: 包含必要的头文件,例如linux/bpf.hbpf/bpf_helpers.h等。
  2. 定义Map: 定义ip_countsblocked_ips两个Map,用于存储IP地址的计数和被阻止的IP地址。
  3. handle_ingress函数: 这是eBPF程序的入口函数,当网络数据包到达ingress hook点时,该函数会被调用。
  4. 数据包解析: 解析以太网头部和IP头部,获取源IP地址。
  5. 检查是否被阻止: 检查源IP地址是否在blocked_ips Map中,如果在,则丢弃该数据包。
  6. 增加计数: 增加源IP地址的计数,如果该IP地址是第一次出现,则在ip_counts Map中创建一个新的条目。
  7. 检查阈值: 检查源IP地址的计数是否超过预设的阈值,如果超过,则将该IP地址添加到blocked_ips Map中,并丢弃该数据包。
  8. 返回: 如果数据包没有被阻止,则返回TC_ACT_OK,允许数据包通过。

4. 用户空间程序

用户空间程序负责将eBPF程序加载到内核,并设置阈值。

4.1 加载eBPF程序

使用libbpf库加载eBPF程序。

#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/libbpf.h>
#include <bpf/bpf.h>
#include "ddos_detect.skel.h" // 由 bpftool gen skeleton 生成
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
return vfprintf(stderr, format, args);
}
static void sig_int(int signo)
{
exit(0);
}
int main(int argc, char **argv)
{
struct rlimit rlim = {
.rlim_cur = RLIM_INFINITY,
.rlim_max = RLIM_INFINITY,
};
if (setrlimit(RLIMIT_MEMLOCK, &rlim)) {
fprintf(stderr, "failed to increase RLIMIT_MEMLOCK limit!\n");
exit(1);
}
libbpf_set_print(libbpf_print_fn);
struct ddos_detect_bpf *skel = ddos_detect_bpf__open();
if (!skel) {
fprintf(stderr, "Failed to open BPF skeleton\n");
return 1;
}
// 加载和验证BPF程序
int err = ddos_detect_bpf__load(skel);
if (err) {
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
goto cleanup;
}
// 设置流量阈值
__u32 key = 0;
struct config cfg = {.threshold = 1000}; // 设置阈值为1000
err = bpf_map_update_elem(bpf_map__fd(skel->maps.config_map), &key, &cfg, BPF_ANY);
if (err) {
fprintf(stderr, "Failed to set threshold: %s\n", strerror(errno));
goto cleanup;
}
// 附加BPF程序
err = ddos_detect_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}
printf("DDoS detect eBPF program loaded and running...\n");
signal(SIGINT, sig_int);
while (1) {
sleep(1);
}
cleanup:
ddos_detect_bpf__destroy(skel);
return -err;
}

4.2 代码解释

  1. 包含头文件: 包含必要的头文件,例如stdio.hstdlib.hbpf/libbpf.h等。
  2. ddos_detect.skel.h 这个头文件是由bpftool gen skeleton命令生成的,它包含了eBPF程序的结构体定义和相关的函数声明。
  3. 加载eBPF程序: 使用ddos_detect_bpf__open函数打开eBPF程序,使用ddos_detect_bpf__load函数加载和验证eBPF程序,使用ddos_detect_bpf__attach函数将eBPF程序附加到tcingress hook点。
  4. 设置阈值: 设置流量阈值,例如设置为1000,表示当来自同一个IP地址的请求数量超过1000时,就认为该IP地址正在进行DDoS攻击。
  5. 循环: 程序进入一个无限循环,保持eBPF程序运行。

5. 编译和运行

5.1 编译eBPF程序

使用clang编译eBPF程序,并生成eBPF目标文件。

clang -target bpf -D__KERNEL__ -I./include -I./usr/include -c ddos_detect.c -o ddos_detect.o

5.2 生成BPF骨架代码

使用bpftool生成BPF骨架代码,用于简化用户空间程序的开发。

bpftool gen skeleton ddos_detect.o -o ddos_detect.skel.h

5.3 编译用户空间程序

使用gcc编译用户空间程序。

gcc -Wall ddos_detect_user.c -o ddos_detect_user -lbpf -lz

5.4 运行程序

  1. 配置tc: 首先需要配置tc,将ingress hook点添加到网络接口。

    # 替换 eth0 为你的网卡名称
    sudo tc qdisc add dev eth0 clsact
    sudo tc filter add dev eth0 ingress bpf da obj ddos_detect.o sec ingress
  2. 运行用户空间程序:

    sudo ./ddos_detect_user
    

6. 测试

可以使用hping3等工具模拟DDoS攻击,测试eBPF程序的防御效果。

sudo hping3 -c 2000 -d 120 -S -p 80 --flood --rand-source <目标IP地址>

7. 总结

本文介绍了如何使用eBPF技术在Linux内核中构建一个简易的DDoS攻击检测器,并实现自动阻断恶意流量的功能。该方案具有以下优点:

  • 高效: eBPF程序在内核空间运行,避免了用户空间和内核空间之间频繁的数据拷贝和上下文切换,提高了性能。
  • 实时: eBPF程序可以实时监控网络流量,及时发现和阻断DDoS攻击。
  • 灵活: 可以根据实际需求,自定义eBPF程序,实现更复杂的DDoS防御策略。

当然,本文介绍的只是一个简易的DDoS攻击检测器,实际的DDoS攻击防御需要更复杂的策略和技术,例如:

  • 更精确的流量分析: 使用机器学习等技术,对流量进行更深入的分析,识别更复杂的攻击模式。
  • 动态阈值调整: 根据网络流量的变化,动态调整阈值,避免误判。
  • 多层防御: 结合其他防御手段,例如CDN、Web应用防火墙等,构建多层防御体系。

希望本文能够帮助读者理解和掌握如何利用eBPF技术进行DDoS防御,为构建更安全、更可靠的网络环境提供参考。

安全小黑哥 eBPFDDoS防御网络安全

评论点评

打赏赞助
sponsor

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

分享

QRcode

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