eBPF/XDP实现防火墙功能

概述

互联网服务常常需要预防DDoS攻击,希望尽早丢弃攻击流量减少服务器资源的浪费。从概念上分析,防火墙是抵御DDoS的有效手段,但是实际基于NetFilter的IpTables并不能解决该问题,因为其处理报文的位置已分配SKB,大量无效SKB会耗尽内存资源,拖垮服务器。但基于eBPF/XDP实现的防火墙能够更早处理攻击流量而不消耗CPU和内存资源,更高效,更安全。

当然,仅靠eBPF/XDP防火墙是不能解决DDoS问题,防火墙只是手段,还需网络流分析系统识别流量特征并对其下发指令。

文本提供一个基于eBPF/XDP实现防火墙的思路和程序设计实现,码字不易,劳烦一键三连,感谢

环境配置 Ubuntu >= 20.10

  • 安装依赖库 elf & zlib
apt install -y libelf1 libelf-dev
apt install -y zlib1g zlib1g-dev apt install -y libcap2 libcap-dev
  • 安装编译器g++ & g++ & cmake & git
apt install -y git cmake g++ gcc pkg-config
gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
g++ --version
g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
git --version
git version 2.25.1
cmake --version
cmake version 3.16.3
pkg-config --version
0.29.1
  • 源码安装libbpf
git clone https://github.com/libbpf/libbpf.git
cd libbpf/src
make
make install

必备工具bpftool

uname -r        //查看内核版本
5.15.8-200.fc35.x86_64
wget https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-`uname -r|awk -F '-' '{print $1}'`.tar.gz //下载内核源代码

tar -zxf linux-5.15.8.tar.gz
cd linux-5.15.8/tools/bpf/bpftool/
make
mv bpftool /usr/bin/

bpftool --version
bpftool v5.15.8

bpffs 持久化

mount | grep bpf
none on /sys/fs/bpf type bpf (rw,nosuid,nodev,noexec,relatime,mode=700) 

如果不存在则创建 bpffs
mount bpffs/sys/fs/bpf -t bpf

设计与实现

基于 eBPF/XDP 实现防火墙功能,支持四层网络,可以接受或者丢弃 IP+Port,或 IP/Mask+Port/Mask 的组合。DDoS 以大量无效报 文来浪费 CPU 资源来达到攻击目的。所以在数据路径上,越早处理 越安全。该服务在 XDP 位置挂载 eBPF 程序,在执行效率上明显高于 IPTables。

在程序设计方面,使用 eBPF LPM Map 在 eBPF 程序和用户态程 序共享储存。用户态程序将防火墙规则构造成 bitmap 数据结构并存 储到 map 中,eBPF 程序在数据路径上读取 map 数据并进行数据包过 滤,将满足条件的数据报文放行,否则丢包拒绝。防火墙规则如 下:

协议源IP源端口目的IP目的端口动作
属性示例
协议TCP UDP ICMP GRE
源IP101.101.102.0/24 10.10.10.10/32
源端口1-65535 22-444
目的IP101.101.102.0/24 10.10.10.10/32
目的端口1-65535 22-444
动作accept drop

若在匹配规则进行串型匹配,就会从走IPTables O(n)复杂度的老路,为了更高效的处理报文, 为了数据面尽可能的精简和高效,将复杂的规则映射到bitmap 这种高效的数据结构中, 控制面将规则生成对应的数据结构。构造 bitmap 需要 O(n),查询需要 O(logn)。

索引协议源IP源端口目的IP目的端口动作
#1tcp10.10.2.51-6553510.10.2.422accept
#2tcp10.10.2.51-6553510.10.2.480accept
#3udp10.10.2.6400010.10.2.43389accept

核心思路将规则生成6张表的bitmap索引。1 表示该规则有该属性,0表示该规则无该属性

协议表bitmap
tcp110
udp001
源IP表bitmap
10.10.2.5110
10.10.2.6001
目的IP表bitmap
10.10.2.4111
源端口表bitmap
1-65535110
4000001
目的端口表bitmap
22100
80010
3389001
动作表bitmap
accept111
  • udp 10.10.2.6 4000 10.10.2.4 3389
    b=001&001&001&001&111&111=001 ✅

  • udp 10.10.2.6 4000 10.10.2.4 3389
    b=001&001&001&001&111&111=001 ✅

  • udp 10.10.2.6 4001 10.10.2.4 3389
    b=001&001&000&111&001&111=000 ❌

  • tcp 10.10.2.5 4001 10.10.2.4 3 81
    b=110&110&110&000&111&111=000 ❌

防火墙程序

安装程序

dpkg-i fw-0.1.0-0-x86_64.deb 

systemctl start fw
systemctl stop fw

dpkg-rfw

接口指南

  • UpdateFirewall
POST http://192.168.56.4:3333
{
	"action":"UpdateFirewall",
	"traceId":"86475a74-c5a3-4632-9214-20bca82d433d",
	"version": 10,
	"rules":[
		{
			"protocol":"tcp",
			"srcIp":"119.119.119.119/32",
			"srcPort":"4000-5000",
			"dstIp":"10.10.2.1/24",
			"dstPort":"22-100",
			"action":"accept"
		}, 
		{
			"protocol":"icmp",
			"srcIp":"10.10.2.1/24",
			"dstIp":"10.10.2.1/24",
			"action":"accept"
		}
	]
}
  • QueryFirewall
POST http://192.168.56.4:3333
{
	"action":"QueryFirewall",
	"traceId":"b7be689f-0a1b-481b-bbb1-2cc987d3e809"
}

错误码表

错误码错误信息错误含义解决方式
1000read request body error读取请求错误重试
1100json format error请求参数格式错误检查请求发送格式 JSON
1101not support action error请求参数错误检查接口拼写
1200update firewall error更新防火墙失败权限不足 需要 ROOT 用户启动
1201query firewall error查询防火墙失败权限不足 需要 ROOT 用户启动

加载eBPF程序

unlink /sys/fs/bpf/fw

bpftool prog load /usr/local/fw/xdp/fw.bpf.o /sys/fs/bpf/fw \
		map name metadata pinned /sys/fs/bpf/metadata \
		map name ipv4_proto pinned /sys/fs/bpf/ipv4_proto \
		map name ipv4_nw_src pinned /sys/fs/bpf/ipv4_nw_src \
		map name ipv4_nw_dst pinned /sys/fs/bpf/ipv4_nw_dst \
		map name ipv4_tp_src pinned /sys/fs/bpf/ipv4_tp_src \
		map name ipv4_tp_dst pinned /sys/fs/bpf/ipv4_tp_dst \
		map name ipv4_action pinned /sys/fs/bpf/ipv4_action

bpftool prog show pinned /sys/fs/bpf/fw

359: xdp name xpd_handle_fw tag 5b2d17efc34ca615 gpl
loaded_at 2022-01-02T16:20:17+0800 uid 0
xlated 5336B jited 2982B memlock 8192B map_ids 35,5,10,15,20,25,30
btf_id 235

网卡设备关联eBPF

enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
	link/ether 08:00:27:f0:1e:41 brd ff:ff:ff:ff:ff:ff
	inet 10.10.2.4/24 brd 10.10.2.255 scope global dynamic enp0s3
	valid_lft 415sec preferred_lft 415sec
	inet6 fe80::a00:27ff:fef0:1e41/64 scope link
	valid_lft forever preferred_lft forever

ip link set dev enp0s3 xdp pinned /sys/fs/bpf/fw

2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 xdpgeneric/id:359 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:f0:1e:41 brd ff:ff:ff:ff:ff:ff
inet 10.10.2.4/24 brd 10.10.2.255 scope global dynamic enp0s3
valid_lft 371sec preferred_lft 371sec
inet6 fe80::a00:27ff:fef0:1e41/64 scope link
valid_lft forever preferred_lft forever

ebpf

参考

[1]: https://facebookmicrosites.github.io/bpf/blog/2018/08/31 /object-lifetime.html
[2]: https://ebpf.io/what-is-ebpf

源码获取

程序设计文档, 免费获取文档,免费答疑

下面是一个简单的ebpf程序,用于防止从指定IP地址和端口发送的流量: ``` #include <linux/bpf.h> #include <linux/in.h> #include <linux/if_ether.h> #include <linux/ip.h> struct bpf_map_def SEC("maps") whitelist = { .type = BPF_MAP_TYPE_HASH, .key_size = sizeof(struct in_addr), .value_size = sizeof(short), .max_entries = 1024, }; SEC("filter") int firewall(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; struct iphdr *ip = data + sizeof(struct ethhdr); struct in_addr src_ip = { .s_addr = ip->saddr }; struct in_addr dst_ip = { .s_addr = ip->daddr }; short *ports; if (ip->protocol != IPPROTO_TCP) return XDP_PASS; if (bpf_map_lookup_elem(&whitelist, &src_ip, &ports) == 0) { if (*ports == ntohs(((struct tcphdr *)(ip + 1))->dest)) return XDP_PASS; } if (bpf_map_lookup_elem(&whitelist, &dst_ip, &ports) == 0) { if (*ports == ntohs(((struct tcphdr *)(ip + 1))->source)) return XDP_PASS; } return XDP_DROP; } char _license[] SEC("license") = "GPL"; ``` 该程序使用了一个hash map来存储允许通过的IP地址和端口。在防火墙函数中,我们首先检查数据包是否是TCP协议,如果不是,则直接通过。然后我们检查源IP地址和目标IP地址是否在白名单中,如果是,则进一步检查端口号是否匹配。如果匹配,则通过数据包,否则丢弃它。 要使用该程序,我们需要先创建一个hash map,并将允许通过的IP地址和端口添加到map中: ``` ip_address=$(ip addr show eth0 | awk '$1 == "inet" { sub("/.*", "", $2); print $2 }') bpftool map create /sys/fs/bpf/firewall/whitelist type hash key sizeof(struct in_addr) value sizeof(short) max_entries 1024 bpftool map update pinned /sys/fs/bpf/firewall/whitelist key hex $ip_address value hex 80e1 ``` 这将创建一个名为“whitelist”的hash map,并将IP地址为“$ip_address”的端口“80e1”添加到map中。现在我们可以将ebpf程序加载到内核中: ``` bpftool prog load firewall.o /sys/fs/bpf/firewall bpftool prog attach xdp pinned /sys/fs/bpf/firewall ``` 这将把程序加载到内核中,并将它附加到xdp程序中。现在我们的防火墙就可以工作了,它将阻止从指定IP地址和端口发送的流量。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cugriver

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值