问题
我们的 DNS 服务器遭到攻击了,每秒高达几万 qps,这个时候怎么办呢?
如果在应用层识别攻击流量并将其丢弃,虽然可以缓解部分问题问题,但是这对于 DDOS 的大流量攻击还是力不从心。 显然拦截报文应该在越靠近网卡收包的位置越好。目前比较理想的是用 ebpf,XDP 来实现报文的过滤。 虽然 ebpf,XDP 性能很高,但是他们不是简单的命令行操作就可以搞定的事情。
简单的重现
这个是利用 OpenResty 创建的一个简单的 UDP 服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
stream {
server {
listen 2023 udp reuseport;
content_by_lua_block {
local s = ngx.req.socket(true)
local bytes, err = s:send("1: received:\n")
if not bytes then
ngx.log(ngx.ERR, "server: failed to send: ", err)
return
end
}
}
}
我们使用这个仓库 udp-flood 的代码稍微修改一下 固定源 IP 和 目的 IP 地址。
可以看到,nginx 的 CPU 被打到 99.7%,收包速率高达 113306 rxpck/s。
1
2
3
4
5
6
7
$ top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
611706 nobody 20 0 70832 11524 7812 R 99.7 0.0 0:44.37 nginx
$ sar -n DEV 1 10
06:36:53 PM IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil
06:36:54 PM enp1s0 113306.00 39142.00 6086.60 2108.95 0.00 0.00 0.00 0.00
下面介绍一种基于 ipset 进行报文过滤的解决方案。
ipset 拦截攻击报文
首先创建一个名称为 black_list 的 ipset 表,在 iptables 的 PREROUTING 阶段 添加一个过滤规则。
1
2
sudo ipset create black_list hash:ip timeout 3600
sudo iptables -I PREROUTING -t raw -m set --match-set black_list src -j DROP
其次我们将黑名单中的 IP 地址添加到 ipset 中
1
2
sudo ipset add black_list 192.168.0.181
sudo ipset add black_list 192.168.0.199
再次查看 CPU 利用率,可以看到 nginx 的 CPU 利用率已经为 0,报文全部被拦截了。 这时候就有查看软中断的开销了。
1
2
3
top - 18:44:02 up 9:34, 4 users, load average: 0.07, 0.16, 0.17
Tasks: 638 total, 3 running, 633 sleeping, 2 stopped, 0 zombie
%Cpu0 : 0.0 us, 0.0 sy, 0.0 ni, 83.5 id, 0.0 wa, 3.3 hi, 13.2 si, 0.0 st
可以看到 CPU 0 的软中断开销为 13.2%。如果使用 bpf 让网卡直接处理报文,不需要拷贝到 skb, 那么这部分的 CPU 开销也可以进一步降低了。
批量添加操作
如果每一个 IP 执行一条 ipset add 命令,显然很低效。 在首次初始化的时候可以使用 ipset resotre 的命令实现批量的添加。
1
cat ips.txt | sudo ipset restore
而这个文件的格式是怎么样的?我们可以看看 ipset save
的一个输出样例。
1
2
3
4
$ sudo ipset save black_list
create black_list hash:ip family inet hashsize 1024 maxelem 65536 timeout 3600
add black_list 192.168.0.199 timeout 3598
add black_list 192.168.0.181 timeout 2831
如果是为了匹配添加而不是首次添加,那么不需要 第一条 create 命令。
ipset 的一些操作
如果想要查看添加了哪些 IP,可以使用下面的命令
1
2
3
4
5
6
7
8
9
10
$ sudo ipset list black_list
Name: black_list
Type: hash:ip
Revision: 4
Header: family inet hashsize 1024 maxelem 65536 timeout 3600
Size in memory: 216
References: 1
Number of entries: 1
Members:
192.168.0.181 timeout 3297
参考链接
https://ipset.netfilter.org/ipset.man.html