nftables搭建防火墙


作者Lou Xiao创建时间2025-01-06 19:24:02更新时间2025-01-07 11:15:00

0. 概要

nftablesiptables的替代版本,nftables解决了iptables的功能有限、性能差的问题。nftables的实现在内核的虚拟机上运行,nftables的逻辑更像是一个编程语言,从而更灵活、高效。

nftables设计的特点:
1. 防火墙规则的编写类似于 编程语言;
2. hook机制,hook对应Linux内核的网络处理流程的阶段(stage);
3. rule规则:每个rule包含N个expression(表达式)、N个非终止statement(语句)、至多1个终止statement(语句);

想要了解更多信息请查看帮助手册:man 8 nft, 👉 nftables official documentation

小技巧:

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 # 安装pandoc
2 apt install pandoc
3 # 把man转化为pdf(竖版A4)
4 zcat /usr/share/man/man8/nft.8.gz | pandoc -V geometry:a4paper,margin=2cm -f man -t pdf -o nft.8.pdf
5 # 把man转化为pdf(横版A4)
6 zcat /usr/share/man/man8/nft.8.gz | pandoc -V geometry:a4paper,margin=2cm -V classoption:landscape -f man -t pdf -o nft.8.pdf

1. 核心概念

首先要明确概念的关系,概念从大到小:ruleset > address family > table > chain > rule > expression & statement
每个address family包含N个table,每个table包含chain,每个chain包含N个rule,每个rule包含N个expression和N个statement

1.1 ruleset(规则集合)

ruleset代表整个nftables的规则。

常用的命令:

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 # 1. 列出所有的规则
2 nft list ruleset
3 # 2. 列出某address family(此处是`ip`)的规则
4 nft list ruleset ip
5 # 3. 清空所有的规则
6 nft flush ruleset
7 # 4. 清空某address family(此处是`ip`)的规则
8 nft flush ruleset ip

1.2 address family

address family 决定了正在处理的报文的类型。

address family说明
ipIPv4 address family
ip6IPv6 address family
inetIPv4 和 IPv6
arpARP address family,处理IPv4 ARP报文
bridgeBridge address family,处理与网桥相关的报文
netdev处理ingress(进站)和egress(出站)的报文

1.3 table

table用来承载 chainsetmapstateful objects(有状态的对象)。
table可以被当作编程语言的namespace,table的名称是什么不重要,但习惯上可以仍旧使用iptablestable name
常见的table name有:filternatroute

1.4 chain

chainrule的容器,从编程角度来看,chain就是一个listchain中的元素rule是有序的,排在前边的rule优先被处理。

有两种chain
1. base chain,使用hook嵌入到Linux的networking stack(网络栈)中。可以jump到regular chain
2. regular chain,普通的chain,可以jump到其他的regular chain

通常我们先定义base chain,如果base chainrule太复杂,可以把部分的rule提取出来放到regular chain

1.4.1 chain type

chain type 指定了base chain的类型(用途)。

chain type table:

TypeFamilyHook说明
fitlerip,ip6,inet,arp,bridge,netdevingress,prerouting,input,forward,output,postrouting,egress
natip,ip6,inetprerouting,input,output,postrouting
routeip,ip6output

1.4.2 hook

hooknftables在内核网络处理流程上预留的接入点hook对应内核的网络处理流程的某个processing stage(处理阶段);base chainhooknetworking stack(网络栈)的预留的接入点,才能处理packets(报文)。

完整的processing stage

序号stage or hook说明
0ingress报文刚刚被网卡接收(进入主机),且被第3层协议栈(IP层)处理之前
1prerouting报文被第3层协议栈(IP层)处理之后,且在被路由之前
-routing报文被路由,这个stage不能被hook,此时分叉:input->output分支和forward分支
2 (分支1)input报文被发向本机进程之前
3 (分支1)output报文被本机进程发出之后
2 (分支2)forward报文被转发到其他的主机或网卡。注意forwardinput -> output 是并行的分支,参考下文的netfilter流程图。
-routing报文被路由,这个stage不能被hook。input->output分支和forward分支在此处汇合。
4postrouting报文被路由之后,且被第3层协议栈(IP层)处理之前。
5egress报文被第3层协议栈(IP层)处理之后,且被发送到网卡(离开主机)前
  • IPv4/IPv6/inet address family支持的hookprerouting,intput,forward,output,postrouting,ingress
  • ARP address family支持的hookinput,output
  • bridge address family支持的hookprerouting,intput,forward,output,postrouting,ingress
  • netdev address family支持的hookingress, egress

参考下图理解netfilter预留的hook点:

❶ iptables流程图
来源:https://www.frozentux.net/iptables-tutorial/images/tables_traverse.jpg
iptables_traverse.jpg

❷ netfilter简要流程图
来源:http://linux-ip.net/nf/nfk-traversal.png
nfk-traversal.png

❸ netfilter详细且复杂的流程图
来源:wikimedia.org
netfilter-packet-flow

1.4.3 example nftables script

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 table inet filter {
2 # chain: inet filter input
3 chain input {
4 type filter hook input priority 0; policy drop;
5 # ===== rules =====
6 }
7
8 # chain: inet filter forward
9 chain forward {
10 type filter hook forward priority 0; policy accept;
11 # ===== rules =====
12 }
13
14 # chain: inet filter output
15 chain output {
16 type filter hook output priority 0; policy accept;
17 # ===== rules =====
18 }
19 }

代码解释:
type filter hook input priority 0; policy drop;表示type=filter,hook=input,priority=0。
priority数值越小越优先,priority可以看作队列的序号,序号越小越被优先处理。

1.5 rule

rulenftables的核心,是用来处理packets代码。
一个rule由N个expression、N个non-terminal statement、至多1个terminal statement构成。

❶ 从编程语言的角度理解ruleexpressionstatement

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 // 对于每个rule
2 if (expression1 && expression2 && expression3 && ...)
3 {
4 // N个`non-terminal statement`
5 ...
6 // 至多1个`terminal statement`
7 ...
8 }

❷ 与iptables的关系

  • expression 等价于 iptablesmatches
  • statement 对应 iptablestarget/action,不同的是nftables可以有多个statement

1.5.1 expression

expression表示一个value(数值),value可以是常量,也可以是复杂的计算表达式expression
expression也有自己的数据类型(如同编程语言)。

使用命令查看 expression的信息:

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 # 查看 expression `ip saddr` 的信息
2 nft describe ip saddr
3 # 查看 expression `tcp flags` 的信息
4 nft describe tcp flags

built-in operations(内置算子): 👉 Building rules through expressions

写法1写法2说明
eq==equal(等于)可以省略
ne!=not equal(不等于)
lt<less than(小于)
gt>greater than(大于)
le<=less than or equal to(小于或等于)
ge>=greater than or equal to(大约或等于)

expression的常见用法:

算子示例说明
相等tcp dport 22如果 tcp dport 等于 22,则匹配成功,反之则匹配失败跳过此rule。
不相等tcp dport != 22如果 tcp dport 不等于 22,则匹配成功,反之则匹配失败跳过此rule。
包含tcp dport {20,21,22}如果tcp dport 是 20,21,22 的其中之一,则匹配成功,反之则匹配失败跳过此rule。
映射maptcp dport map {20: 192.168.0.20, 21: 192.168.0.21}如果tcp dport等于20,则此表达=192.168.0.20;
如果tcp dport等于21,则此表达=192.168.0.21;
否则rule匹配失败,跳过此rule
映射vmapip protocol vmap {tcp: jump tcp_chain, udp: jump udp_chain}如果ip protocol等于tcp,则执行jump tcp_chain;
如果ip protocol等于udp,则执行jump udp_chain;
否则rule匹配失败,跳过此rule

常用的expression

expression说明
meta length报文的长度,单位:bytes
meta l4proto第4层的协议名称,参考nft describe meta l4proto
预定义的符号常量:ip,icmp,igmp,ggp,ipencap,st,tcp,egp,igp,pup,udp,hmp,xns-idp,rdp,iso-tp4,dccp,xtp,ddp,idpr-cmtp,ipv6,ipv6-route,ipv6-frag,idrp,rsvp,gre,esp,ah,skip,ipv6-icmp,ipv6-nonxt,ipv6-opts,rspf,vmtp,eigrp,ospf,ax.25,ipip,etherip,encap,pim,ipcomp,vrrp,l2tp,isis,sctp,fc,mobility-header,udplite,mpls-in-ip,manet,hip,shim6,wesp,rohc,ethernet.
meta iif报文进站的网卡设备Index
meta iffname报文进站的网卡设备名称
meta oif报文出站使用的网卡设备Index
meta oifname报文出站使用的网卡设备名称
meta ibrname报文进站使用的网桥设备名称
meta obrname报文出站使用的网桥设备名称
meta skuid关联socket的进程的UID
meta skgid关联socket的进程的GID
socket transparentsocket IP_TRANSPARENT option
socket marksocket SO_MARK
socket wildcardwildcard-bound例如:0.0.0.0
rt [ip|ip6] classidrouting realm
rt [ip|ip6] nexthoprouting nexthop
rt [ip|ip6] mtuTCP MTU
rt [ip|ip6] ipsecroute via ipsec
ether saddr源MAC地址
ether daddr目的MAC地址
ether typeEther Type:ip,arp,ip6,8021q,8021ad,vlan
ip lengthIPv4报文总长度
ip protocol传输层协议,参考nft describe ip protocol.
预定义的符号常量:ip,icmp,igmp,ggp,ipencap,st,tcp,egp,igp,pup,udp,hmp,xns-idp,rdp,iso-tp4,dccp,xtp,ddp,idpr-cmtp,ipv6,ipv6-route,ipv6-frag,idrp,rsvp,gre,esp,ah,skip,ipv6-icmp,ipv6-nonxt,ipv6-opts,rspf,vmtp,eigrp,ospf,ax.25,ipip,etherip,encap,pim,ipcomp,vrrp,l2tp,isis,sctp,fc,mobility-header,udplite,mpls-in-ip,manet,hip,shim6,wesp,rohc,ethernet.
ip saddr源IPv4地址
ip daddr目的IPv4地址
ip6 lengthIPv6报文总长度
ip6 protocol传输层协议
ip6 saddr源IPv6地址
ip6 daddr目的IPv6地址
icmp typeicmp type 字段,参考nft describe icmp type.
预定义的符号常量:echo-reply,destination-unreachable,source-quench,redirect,echo-request,router-advertisement,router-solicitation,time-exceeded,parameter-problem,timestamp-request,timestamp-reply,info-request,info-reply,address-mask-request,address-mask-reply.
icmp codeicmp code 字段,参考nft describe icmp code.
预定义的符号常量:net-unreachable,host-unreachable,prot-unreachable,port-unreachable,net-prohibited,host-prohibited,admin-prohibited,frag-needed.
icmpv6 typeICMPv6 type 字段,参考nft describe icmpv6 type.
预定义的符号常量:destination-unreachable,packet-too-big,time-exceeded,parameter-problem,echo-request,echo-reply,mld-listener-query,mld-listener-report,mld-listener-done,mld-listener-reduction,nd-router-solicit,nd-router-advert,nd-neighbor-solicit,nd-neighbor-advert,nd-redirect,router-renumbering,ind-neighbor-solicit,ind-neighbor-advert,mld2-listener-report.
icmpv6 codeICMPv6 code 字段,参考nft describe icmpv6 code.
预定义的符号常量:no-route,admin-prohibited,addr-unreachable,port-unreachable,policy-fail,reject-route.
tcp sporttcp source port
tcp dporttcp destination port
tcp flagstcp flags字段,参考nft describe tcp flags.
预定义的符号常量: fin,syn,rst,psh,ack,urg,ecn,cwr.
udp sportudp source port
udp dportudp destination port
sctp sportsctp source port
sctp dportsctp destination port
ct stateconnection track,连接的状态:pre-defined,invalid,new,established,related,untracked
ct statusconnection track,连接的状态:expected,seen-reply,assured,confirmed,snat,dnat,dying
ct markconnection mark
ct count当前连接的数量

1.5.2 statement

statement示例说明
accepttcp dport 22 accept如果tcp dport 等于 22,则接受packet并终止执行此chain;否则,继续查看下个rule。
droptcp dport 22 drop如果tcp dport 等于 22,则丢弃packet并终止后续ruleset;否则,继续查看下个rule。
queuetcp dport 22 queue如果tcp dport 等于 22,则提交packet到用户态的队列中并终止后续ruleset;否则,继续查看下个rule。
continuetcp dport 22 continue如果tcp dport 等于 22,继续查看下个rule。continue 是rule的默认statement。
jumptcp dport 22 other_chain如果tcp dport 等于 22,则跳转到other_chain执行,执行完后再跳回当前chain的rule继续执行;否则,继续查看下个rule。jump类似于函数调用。
gototcp dport 22 other_chain如果tcp dport 等于 22,则跳转到other_chain执行,执行流程不会返回当前chain;否则,继续查看下个rule。
rejecttcp dport 22 reject with icmpx admin-prohibited如果tcp dport 等于 22,则丢弃packet并发送ICMP错误代码(admin-prohibited)并终止后续ruleset;否则,继续查看下个rule。
limit ratetcp dport 22 limit rate over 10/second burst 10 drop如果tcp dport 等于 22 并且 发送的报文速率超过10个每秒,则丢弃该报文。
limit ratetcp dport 22 limit rate over 10mbytes/second burst 4mbytes drop如果tcp dport 等于 22 并且 发送的报文速率超过10Mbytes每秒,则丢弃该报文。
snatoif eth0 snat to 1.2.3.4如果报文来自eth0,则修改报文的源IP地址为1.2.3.4
dnatiif eth0 dnat to 192.168.1.120如果报文流向eth0,则修改报文的目的IP地址为192.168.1.120
masqueradeiif eth0 masquerade如果报文流向eth0,则修改报文的源IP地址为eth0的IP地址。masqueradesnat的特化版本
settcp flags syn tcp option maxseg size set rt mtu如果tcp flags等于syn,则修改 tcp option maxseg sizert mtu

2. 编写nftables script基础流程

2.0 nftables script基础知识

❶ 语法格式

  1. 按行解析,每个rule对应一行;
  2. 如果每行的结尾是\,表示续行;
  3. 一行可以有多个命令,用;分割;
  4. #表示(行)注释,#之后的此行的字符被忽略掉;
  5. 标识符(id),以字母(a-z,A-Z)开头,后续N个字母、数字、/\_.;特别的,如果id与nftables的关键字重名,需要加上;id可用于tablechainvariablesetmap等的名字。

❷ 加载其他nftables script文件

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 # 加载"/etc/xxx.nft"文件内容
2 include "/etc/xxx.nft"
3 # 加载"/etc/nftables/"文件夹下面的以`.nft`结尾的文件内容
4 include "/etc/nftables/*.nft"

❸ 变量

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 # `<variable name>`替换为实际的id。
2 # 定义名字是`<variable name>`的变量
3 define <variable name> = <some value>
4 # 删除变量`<variable name>`
5 undefine <variable name>
6 # 重新定义变量`<variable name>`,改变了数值与类型。
7 redefine <variable name> = <new value>
8 # 定义变量ports
9 define ports = {22,23,24}
10 # 使用变量ports,需要加上前缀`$`。
11 tcp dport $ports accept

❹ set 和 map

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 # 把`<your name>`, `<your type>`, `<your flags>`, `<your element1>`, `<your element2>`替换为实际的值。
2 table inet filter {
3 # 在某table中定义set 或 map
4 set <your name> {
5 type <your type>; flags <your flags>;
6 elements = {
7 # element1
8 <your element1>,
9 <your element2>,
10 }
11 }
12 }

示例:

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 table inet filter {
2 # 定义set,名字是denylist,type是ipv4_addr,flags interval,元素类型就是Ipv4地址且支持CIDR.
3 set denylist {
4 type ipv4_addr; flags interval;
5 elements = {
6 1.2.3.4/8,
7 5.6.7.8,
8 }
9 }
10 # 使用
11 chain input {
12 type filter hook input priority 0; policy drop;
13 # 此处使用denylist,需要加上前缀@
14 ip saddr @denylist drop
15 # more rules ...
16 }
17 }

2.1 install nftables

on debian 12:

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 apt install nftables
2 systemctl disable firewalld
3 systemctl stop firewalld

2.2 edit nftables script file

创建临时文件/root/nftables.conf并编辑,可以参考下文的模版。

2.3 防呆措施

在root用户的crontab上添加

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 */5 * * * * /usr/sbin/nft flush ruleset >/dev/null 2>/dev/null

这样每5分钟,nftables ruleset就会被清空,当错误的修改了nftables时,有机会远程登录。

2.4 测试nftables script

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 # 语法检查并加载`/root/nftables.conf`
2 nft -c -f /root/nftables.conf && nft -f /root/nftables.conf

检查防火墙是否符合预期。若是改错了nftables导致不能远程登录,耐心等5分钟直至防火墙ruleset被清空,方可再次登录。
修改/root/nftables.nft并测试 /root/nftables.nft,这样反复进行直至符合预期。

2.5 提交

debian 12上,查看/usr/lib/systemd/system/nftables.service的内容:

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 # 省略...
2 [Service]
3 Type=oneshot
4 RemainAfterExit=yes
5 StandardInput=null
6 ProtectSystem=full
7 ProtectHome=true
8 ExecStart=/usr/sbin/nft -f /etc/nftables.conf
9 ExecReload=/usr/sbin/nft -f /etc/nftables.conf
10 ExecStop=/usr/sbin/nft flush ruleset
11 # 省略...

可以看到nftables的主配置文件就是/etc/nftables.conf。对于其他系统,查看nftables.service的内容以确定配置文件的路径。

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 # 把临时script文件内容更新到正式的配置文件`/etc/nftables.conf`
2 cp /root/nftables.conf /etc/nftables.conf
3 # 确保正确的权限
4 chmod 755 /etc/nftables.conf
5 # 仅需首次执行
6 systemctl enable --now nftables
7 # 重新加载nftables配置文件,使得新配置生效。
8 systemctl reload nftables

2.6 关闭防呆措施

在root用户的crontab上注释掉

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 # 注释掉这行,关闭防呆措施。
2 # */5 * * * * /usr/sbin/nft flush ruleset >/dev/null 2>/dev/null

3. nftables script template

1.双击鼠标左键复制此行;2.单击复制所有代码。
                                
                                    
1 #!/usr/sbin/nft -f
2
3 flush ruleset
4
5 table inet filter {
6 # deny access from these hosts.
7 set denylist {
8 type ipv4_addr; flags interval;
9 elements = {
10 # below IP address are examples, replace them by yours.
11 1.1.1.0/24,
12 4.5.6.7,
13 }
14 }
15 # limit upload speed for these hosts.
16 set slowdown_ratelimiter {
17 type ipv4_addr; flags dynamic; size 65536; timeout 60s;
18 }
19 set slowdown {
20 type ipv4_addr; flags interval;
21 elements = {
22 # below IP address are examples, replace them by yours.
23 1.2.3.0/8,
24 4.5.6.0/13,
25 7.8.9.0/24,
26 }
27 }
28 # chain: inet filter input
29 chain input {
30 type filter hook input priority 0; policy drop;
31 # ===== deny list =====
32 ip saddr @denylist reject with icmpx host-unreachable
33 # ===== default =====
34 meta nfproto ipv6 drop
35 # allow icmp
36 meta l4proto { icmp, ipv6-icmp } accept
37 ct state { established, related } accept
38 ct status dnat accept
39 iifname "lo" accept
40 ct state invalid drop
41 # ===== custom =====
42 # ssh
43 tcp dport 22 accept
44 # smb
45 udp dport { 137, 138 } accept
46 tcp dport { 139, 445 } accept
47 # ftp
48 tcp dport { 20, 21, 65000-65535 } accept
49 # xrdp
50 tcp dport 3389 accept
51 # haproxy
52 tcp dport 9000 accept
53 # ===== libvirt =====
54 iifname "virbr0" tcp dport { 53, 67 } accept
55 iifname "virbr0" udp dport { 53, 67 } accept
56 }
57 # chain: inet filter forward
58 chain forward {
59 type filter hook forward priority 0; policy accept;
60 # ===== default =====
61 meta nfproto ipv6 drop
62 # ct state { established, related } accept
63 # ct status dnat accept
64 # ct state invalid drop
65 # iifname "lo" accept
66 # oifname "lo" accept
67 # iifname "enp0s31f6" accept
68 # oifname "enp0s31f6" accept
69 # ===== libvirt =====
70 # iifname "virbr0" accept
71 # oifname "virbr0" accept
72 # oifname "virbr0" ct state { established, related } accept
73 # oifname "virbr0" reject
74 }
75
76 # chain: inet filter output
77 chain output {
78 type filter hook output priority 0; policy accept;
79 # ===== deny list =====
80 ip daddr @denylist reject with icmpx host-unreachable
81 # ===== slowdown =====
82 ip daddr @slowdown update @slowdown_ratelimiter { ip daddr limit rate over 100 kbytes/second } drop
83 # # ===== default =====
84 # meta nfproto ipv6 drop
85 # ct state { established, related } accept
86 # oifname "lo" accept
87 # oifname "virbr0" accept
88 # # accept all private IP address
89 # ip daddr { 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } accept
90 # # ===== custom =====
91 }
92 }
93
94 table inet nat {
95 # chain: inet nat postrouting
96 chain postrouting {
97 type nat hook postrouting priority srcnat; policy accept;
98 # ===== default =====
99 meta nfproto ipv6 drop
100 # ===== libvirt =====
101 oifname != "virbr0" iifname "virbr0" masquerade
102 }
103 }
文章目录