上篇文章 给你们介绍了 nftables
的优势以及基本的使用方法,它的优势在于直接在用户态把网络规则编译成字节码,而后由内核的虚拟机执行,尽管和 iptables 同样都是基于 netfilter
,但 nftables 的灵活性更高。linux
以前用 iptables 匹配大量数据时,还得须要 ipset
配合,而 nftables 直接内置了集合和字典,能够直接匹配大量的数据,这一点比 iptables 方便多了,拿来练练魔法真是极好的,很少解释,请直接看 Linux全局智能分流方案。web
本文将会教你如何配置 nftables 来为服务器实现一个简单的防火墙,本文以 CentOS 7 为例,其余发行版相似。算法
首先须要安装 nftables:bash
$ yum install -y nftables复制代码
因为 nftables 默认没有内置的链,但提供了一些示例配置,咱们能够将其 include 到主配置文件中。主配置文件为 /etc/sysconfig/nftables.conf
,将下面一行内容取消注释:服务器
# include "/etc/nftables/inet-filter"复制代码
而后启动 nftables 服务:微信
$ systemctl start nftables复制代码
如今再次查看规则,就会发现多了一张 filter
表和几条链:网络
$ nft list ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy accept;
}
chain forward {
type filter hook forward priority 0; policy accept;
}
chain output {
type filter hook output priority 0; policy accept;
}
}复制代码
在 nftables 中,ipv4
和 ipv6
协议能够被合并到一个单一的地址簇 inet
中,使用了 inet 地址簇,就不须要分别为 ipv4 和 ipv6 指定两个不一样的规则了。多线程
和 iptables 同样,nftables
的 filter 表包含三条链:INPUT
、FORWARD
和 OUTPUT
,通常配置防火墙只须要配置 INPUT
链就行了。ssh
首先容许访问 localhost:tcp
$ nft add rule inet filter input iif "lo" accept
$ nft add rule inet filter input iif != "lo" ip daddr 127.0.0.0/8 drop复制代码
能够再优化一下,加上注解(comment)和计数器(counter):
$ nft add rule inet filter input \
iif "lo" \
accept \
comment \"Accept any localhost traffic\"
$ nft add rule inet filter input \
iif != "lo" ip daddr 127.0.0.0/8 \
counter \
drop \
comment \"drop connections to loopback not coming from loopback\"复制代码
查看规则:
$ nft list chain inet filter input
table inet filter {
chain input {
type filter hook input priority 0; policy accept;
iif "lo" accept comment "Accept any localhost traffic"
iif != "lo" ip daddr 127.0.0.0/8 counter packets 0 bytes 0 drop comment "drop connections to loopback not coming from loopback"
}
}复制代码
接下来的规则用到一个内核模块叫 conntrack(connection tracking)
,它被用来跟踪一个链接的状态。最多见的使用场景是 NAT
,为何须要跟踪记录链接的状态呢?由于 nftables 须要记住数据包的目标地址被改为了什么,而且在返回数据包时再将目标地址改回来。
和 iptables 同样,一个 TCP 链接在 nftables 中总共有四种状态:NEW
,ESTABLISHED
,RELATED
和 INVALID
。
除了本地产生的包由 OUTPUT
链处理外,全部链接跟踪都是在 PREROUTING
链里进行处理的,意思就是, iptables 会在 PREROUTING
链里重新计算全部的状态。若是咱们发送一个流的初始化包,状态就会在 OUTPUT
链里被设置为 NEW
,当咱们收到回应的包时,状态就会在 PREROUTING 链里被设置为 ESTABLISHED
。若是收到回应的第一个包不是本地产生的,那就会在 PREROUTING 链里被设置为 NEW
状态。综上,全部状态的改变和计算都是在 nat 表中的 PREROUTING
链和 OUTPUT
链里完成的。
还有其余两种状态:
RELATED
: RELATED 状态有点复杂,当一个链接与另外一个已是 ESTABLISHED
的链接有关时,这个链接就被认为是 RELATED。这意味着,一个链接要想成为 RELATED,必须首先有一个已是 ESTABLISHED
的链接存在。这个 ESTABLISHED 链接再产生一个主链接以外的新链接,这个新链接就是 RELATED 状态了。INVAILD
: 表示分组对应的链接是未知的,说明数据包不能被识别属于哪一个链接或没有任何状态。有几个缘由能够产生这种状况,好比,内存溢出,收到不知属于哪一个链接的 ICMP 错误信息。咱们须要 DROP 这个状态的任何东西,并打印日志:$ nft add rule inet filter input \
ct state invalid \
log prefix \"Invalid-Input: \" level info flags all \
counter \
drop \
comment \"Drop invalid connections\"复制代码
查看规则:
$ nft list chain inet filter input
table inet filter {
chain input {
type filter hook input priority 0; policy accept;
iif "lo" accept comment "Accept any localhost traffic"
iif != "lo" ip daddr 127.0.0.0/8 counter packets 0 bytes 0 drop comment "drop connections to loopback not coming from loopback"
ct state invalid log prefix "Invalid-Input: " level info flags all counter packets 0 bytes 0 drop comment "Drop invalid connections"
}
}复制代码
为了防止有恶意攻击者利用 ping 泛洪(ping flood)来进行攻击,能够利用令牌桶模型来对 ping 包限速。ping 泛洪的原理很简单,就是采用多线程的方法一次性发送多个 ICMP
请求报文,让目的主机忙于处理大量这些报文而形成速度缓慢甚至宕机。
先来介绍一下令牌桶模型。
熟悉 iptables 的朋友应该知道,iptables 经过 hashlimit
模块来实现限速的功能,而 hashlimit
的匹配方式就是基于令牌桶(Token bucket)的模型,nftables 也相似, 令牌桶是一种网络通信中常见的缓冲区工做原理,它有两个重要的参数,令牌桶容量 n
和令牌产生速率 s
:
令牌桶容量 n
:能够把令牌当成是门票,而令牌桶则是负责制做和发放门票的管理员,它手里最多有n张令牌。初始时,管理员开始手里有 n 张令牌,每当一个数据包到达后,管理员就看看手里是否还有可用的令牌。若是有,就把令牌发给这个数据包,limit 就告诉nftables,这个数据包被匹配了,而当管理员把手上全部的令牌都发完了,再来的数据包就拿不到令牌了;这时,limit 模块就告诉 nftables ,这个数据包不能被匹配。令牌产生速率 s
:当令牌桶中的令牌数量少于 n,它就会以速率 s 来产生新的令牌,直到令牌数量到达 n 为止。经过令牌桶机制,能够有效的控制单位时间内经过(匹配)的数据包数量,又能够允许短期内突发的大量数据包的经过(只要数据包数量不超过令牌桶 n),真是妙哉啊。
nftables 比 iptables 作的更绝,它不只能够基于数据包来限速,也能够基于字节来限速。为了更精确地验证令牌桶模型,咱们选择基于字节来限速:
$ nft add rule inet filter input \
ip protocol icmp icmp type echo-request \
limit rate 20 bytes/second burst 500 bytes \
counter \
accept \
comment \"No ping floods\"复制代码
上面的规则表示:
echo-request
类型的 ICMP 包创建一个匹配项;500
个字节;20
字节/s再添加一条规则,拒毫不知足上诉条件的数据包:
$ nft add rule inet filter input \
ip protocol icmp icmp type echo-request \
drop \
comment \"No ping floods\"复制代码
同时还要接收状态为 ESTABLISHED 和 RELATED 的数据包:
$ nft add rule inet filter input \
ct state \{ established, related \} \
counter \
accept \
comment \"Accept traffic originated from us\"复制代码
下面来作个实验,直接 ping 该服务器的 IP 地址,ping 包大小设置为 100 字节,每秒发送一次:
$ ping -s 92 192.168.57.53 -i 1
PING 192.168.57.53 (192.168.57.53) 92(120) bytes of data.
100 bytes from 192.168.57.53: icmp_seq=1 ttl=64 time=0.402 ms
100 bytes from 192.168.57.53: icmp_seq=2 ttl=64 time=0.373 ms
100 bytes from 192.168.57.53: icmp_seq=3 ttl=64 time=0.465 ms
100 bytes from 192.168.57.53: icmp_seq=4 ttl=64 time=0.349 ms
100 bytes from 192.168.57.53: icmp_seq=5 ttl=64 time=0.411 ms
100 bytes from 192.168.57.53: icmp_seq=11 ttl=64 time=0.425 ms
100 bytes from 192.168.57.53: icmp_seq=17 ttl=64 time=0.383 ms
100 bytes from 192.168.57.53: icmp_seq=23 ttl=64 time=0.442 ms
100 bytes from 192.168.57.53: icmp_seq=29 ttl=64 time=0.464 ms
...复制代码
首先咱们能看到前 5 个包的回应都很是正常,而后从第 6 个包开始,咱们每 6 秒能收到一个正常的回应。这是由于咱们设定了令牌桶的容量为 500
个字节,令牌产生速率为 20
字节/s,而发包的速率是每秒钟 100
个字节,即每一个包 100
个字节,当发完 5 个包后,令牌桶的容量变为 0,这时开始以 20
字节/s 的速率产生新令牌(和前面提到的令牌桶算法不太同样,只有当令牌桶容量为 0 才开始产生新的令牌),5 秒钟以后,令牌桶的容量变为 100
个字节,因此 6 秒钟后又能收到正常回应。
接收其余类型的 ICMP
协议数据包:
$ nft add rule inet filter input \
ip protocol icmp icmp type \{ destination-unreachable, router-advertisement, router-solicitation, time-exceeded, parameter-problem \} \
accept \
comment \"Accept ICMP\"复制代码
接收 IGMP
协议数据包:
$ nft add rule inet filter input \
ip protocol igmp \
accept \
comment \"Accept IGMP\"复制代码
这一步咱们将 TCP 和 UDP 的流量拆分,而后分别处理。先建立两条链:
$ nft add chain inet filter TCP
$ nft add chain inet filter UDP复制代码
而后建立一个命名字典:
$ nft add map inet filter input_vmap \{ type inet_proto : verdict \; \}复制代码
字典的键表示协议类型,值表示判决动做。
往字典中添加元素:
$ nft add element inet filter input_vmap \{ tcp : jump TCP, udp : jump UDP \}复制代码
最后建立一条规则拆分 TCP 和 UDP 的流量:
$ nft add rule inet filter input meta l4proto vmap @input_vmap复制代码
其中,meta l4proto
用来匹配协议的类型。
最后再瞄一眼规则:
$ nft list ruleset
table inet filter {
map input_vmap {
type inet_proto : verdict
elements = { tcp : jump TCP, udp : jump UDP }
}
chain input {
type filter hook input priority 0; policy accept;
iif "lo" accept comment "Accept any localhost traffic"
iif != "lo" ip daddr 127.0.0.0/8 counter packets 0 bytes 0 drop comment "drop connections to loopback not coming from loopback"
ct state invalid log prefix "Invalid-Input: " level info flags all counter packets 95 bytes 6479 drop comment "Drop invalid connections"
icmp type echo-request limit rate 20 bytes/second burst 500 bytes counter packets 17 bytes 2040 accept comment "No ping floods"
icmp type echo-request drop comment "No ping floods"
ct state { established, related } counter packets 172135 bytes 99807569 accept comment "Accept traffic originated from us"
icmp type { destination-unreachable, router-advertisement, router-solicitation, time-exceeded, parameter-problem } accept comment "Accept ICMP"
ip protocol igmp accept comment "Accept IGMP"
meta l4proto vmap @input_vmap
}
chain forward {
type filter hook forward priority 0; policy accept;
}
chain output {
type filter hook output priority 0; policy accept;
}
chain TCP {
}
chain UDP {
}
}复制代码
这一步咱们来处理 TCP 流量,首当其冲的就是 ssh
了,必须得给这位大哥放行啊:
$ nft add rule inet filter TCP \
tcp dport 22 \
ct state new \
limit rate 15/minute \
log prefix \"New SSH connection: \" \
counter \
accept \
comment \"Avoid brute force on SSH\"复制代码
其次须要放行 Web 服务,和上面同样,为了易于管理,方便后续动态添加端口,须要先建立一个命名集合:
$ nft add set inet filter web \{ type inet_service \; flags interval \; \}复制代码
查看集合:
$ nft list set inet filter web
table inet filter {
set web {
type inet_service
flags interval
}
}复制代码
向集合中添加元素:
$ nft add element inet filter web \{ 80, 443 \}复制代码
查看集合:
$ nft list set inet filter web
table inet filter {
set web {
type inet_service
flags interval
elements = { http, https }
}
}复制代码
放行 Web 服务:
$ nft add rule inet filter TCP \
tcp dport @web \
counter \
accept \
comment \"Accept web server\"复制代码
若是你还有其余不可描述的应用,好比 xxx 之类的代理,能够按照上面的方式添加规则,先建立集合:
$ nft add set inet filter xxx \{ type inet_service \; flags interval \; \}复制代码
再添加元素:
$ nft add element inet filter xxx \{ 9000-9005, 9007 \}复制代码
查看集合:
$ nft list set inet filter xxx
table inet filter {
set xxx {
type inet_service
flags interval
elements = { 9000-9005, 9007 }
}
}复制代码
如今体会到 nftables 集合的强大了吧,能够是区间,能够是单个元素组成的集合,也能够混合,iptables 麻烦让一让。
放行不可描述的服务:
$ nft add rule inet filter TCP \
tcp dport @xxx \
counter \
accept \
comment \"Accept xxx\"复制代码
这一步咱们来处理 UDP 流量,好比上面举例的不可描述的应用,除了 TCP 端口还有 UDP 端口,具体用处我就不解释了,本身面向谷歌找答案吧。
到了这一步,连集合都不用建立, 直接复用以前建立的集合,放行不可描述应用的 UDP 数据:
$ nft add rule inet filter UDP \
udp dport @xxx \
counter \
accept \
comment \"Accept xxx\"复制代码
查看规则:
$ nft list chain inet filter UDP
table inet filter {
chain UDP {
udp dport @xxx counter packets 0 bytes 0 accept comment "Accept xxx"
}
}复制代码
其余 UDP 数据均可按此套路模块化,简直不要太赏心悦目。
为了使系统或 nftables 重启后可以继续生效,咱们须要将这些规则持久化,直接将规则写入 /etc/nftables/inet-filter
:
$ echo "#! /usr/sbin/nft -f" > /etc/nftables/inet-filter
$ nft list ruleset >> /etc/nftables/inet-filter复制代码
开机自动加载 nftables 服务:
$ systemctl enable nftables复制代码
默认状况下,开启日志记录后,日志会直接进入 syslog,和系统日志混在一块儿,很差读取。最好的办法是将 nftables 的日志重定向到单独的文件。
以本文为例,咱们只开启了 ct state invalid
和 ssh
的日志记录,先在 /var/log
目录中建立一个名为 nftables
的目录,并在其中建立两个名为 invalid.log
和 ssh.log
的文件,分别存储各自的日志。
$ mkdir /var/log/nftables
$ touch /var/log/nftables/{ssh.log,invalid.log}复制代码
确保系统中已安装 rsyslog。如今进入 /etc/rsyslog.d
目录并建立一个名为 nftables.conf
的文件,其内容以下:
:msg,regex,"Invalid-Input: " -/var/log/nftables/invalid.log
:msg,regex,"New SSH connection: " -/var/log/nftables/ssh.log复制代码
最后,为了确保日志是可管理的,须要在 /etc/logrotate.d
中建立一个 nftables
文件:
$ cat /etc/logrotate.d/nftables
/var/log/nftables/* { rotate 5 daily maxsize 50M missingok notifempty delaycompress compress postrotate invoke-rc.d rsyslog rotate > /dev/null endscript }复制代码
从新经过 ssh 链接服务器,就能看到日志了:
$ tail -f /var/log/nftables/ssh.log
Dec 19 17:15:33 [localhost] kernel: New SSH connection: IN=ens192 OUT= MAC=00:50:56:bd:2f:3d:00:50:56:bd:d7:24:08:00 SRC=192.168.57.2 DST=192.168.57.53 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=43312 DF PROTO=TCP SPT=41842 DPT=22 WINDOW=29200 RES=0x00 SYN URGP=0复制代码
本文教你如何使用 nftables 搭建一个简单的防火墙,并经过集合和字典将规则集模块化,后续可动态添加端口和 IP 等元素,而不用修改规则。更复杂的规则将会在后面的文章介绍,下篇文章将会教你如何使用 nftables 来防 DDoS
攻击,敬请期待。
扫一扫下面的二维码关注微信公众号,在公众号中回复◉加群◉便可加入咱们的云原生交流群,和孙宏亮、张馆长、阳明等大佬一块儿探讨云原生技术