nftables
告别iptables:拥抱下一代Linux防火墙nftables
对于Linux系统管理员和高级用户来说,防火墙是网络安全的第一道防线。多年来,iptables一直是许多人心目中默认的防火墙工具。然而,时代在进步,nftables作为iptables的继任者,带来了更简洁的语法、更强大的功能和更高的性能。
为什么选择nftables?
nftables相比于iptables,主要有以下几个优势:
- 统一的框架:
iptables将不同的协议族(IPv4, IPv6, ARP, eb)分散在不同的工具中(iptables,ip6tables,arptables,ebtables)。而nftables提供了一个统一的框架和命令行工具nft,可以同时管理所有协议族的规则。 - 更简洁、更直观的语法:
nftables的语法设计更加现代化,可读性更强,也更容易编写和维护。告别了iptables中繁琐的-A,-I,-p等参数。 - 性能提升:
nftables在内核中实现了一个新的、更高效的引擎。能够更智能地处理规则,减少了数据包在内核中需要遍历的路径,从而提升了性能。 - 更好的原子性操作:
nftables允许你以原子方式更新整个规则集。这意味着要么所有更改都成功应用,要么在出现错误时所有更改都会被回滚,避免了防火墙处于中间状态的风险。 - 内置的数据集(sets)和映射(maps):这是
nftables的一大亮点。你可以创建IP地址、端口等的集合,然后在规则中直接引用这些集合,极大地简化了复杂规则的管理。
nftables的核心概念
先来了解一下nftables的几个核心概念:
- **表 (Table)**:表是链(Chains)的容器。每个表都有一个地址族(family),比如
ip(IPv4),ip6(IPv6),inet(IPv4和IPv6),arp(ARP),bridge(网桥) 或netdev。 - 链 (Chain):链是规则(Rules)的容器。链分为两种类型:基本链 (base chain) 和 **常规链 (regular chain)**。基本链是数据包进入网络协议栈的入口点,需要绑定到一个“钩子”(hook)上,比如
prerouting,input,forward,output,postrouting。常规链则可以被其他规则调用,类似于编程中的函数。 - **规则 (Rule)**:规则定义了如何处理数据包。包含匹配条件(比如源IP地址、目标端口)和动作(比如
accept接受,drop丢弃,reject拒绝)。 - **表达式 (Expression)**:表达式是规则的一部分,用于定义匹配的具体条件。
- **对象 (Object)**:
nftables引入了对象的概念,例如 sets 和 maps,可以存储和查询数据,使得规则更加灵活和高效。
从iptables到nftables的平滑过渡
对于习惯了iptables的用户,nftables的学习曲线并不陡峭。下面我们通过一些常见的例子来对比一下两者:
示例1:允许SSH(端口22)的入站连接
iptables:
1
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
nftables:
1
nft add rule inet filter input tcp dport 22 accept
示例2:阻止来自特定IP地址的连接
iptables:
1
iptables -A INPUT -s 192.168.1.100 -j DROP
nftables:
1
nft add rule inet filter input ip saddr 192.168.1.100 drop
示例3:进行网络地址转换 (NAT)
iptables:
1
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
nftables:
1
nft add rule inet nat postrouting oifname "eth0" masquerade
从上面的例子可以看出,nftables的语法更加自然,接近自然语言,可读性更强。
模块化管理你的防火墙规则
随着服务器上运行的服务越来越多,防火墙规则也会变得越来越复杂。将所有规则都写在一个巨大的配置文件中显然不是一个好主意。nftables通过引入/etc/nftables.conf和/etc/nftables.d/目录的机制,鼓励用户进行模块化的管理。
主配置文件通常是/etc/nftables.conf。这个文件通常会包含一个include语句,来加载/etc/nftables.d/目录下的所有.nft文件。
/etc/nftables.d目录下的文件列表示例:
1 | 01_wwan.nft 445_samba.nft 51_hotspot.nft 6800.nft 8083.nft 9898.nft |
这是一个非常典型的模块化nftables配置。从文件名我们可以推断出:
- 按服务或功能划分:每个文件对应一个特定的服务或功能。例如,
50_ssh.nft显然是用来管理SSH服务的规则,445_samba.nft用于Samba服务,51_docker.nft用于Docker容器。 - 按端口划分:一些文件直接以端口号命名,如
80.nft,443.nft,这很可能是为Web服务开放HTTP和HTTPS端口。 - 数字前缀用于排序:文件名开头的数字决定了这些规则文件被加载的顺序。这在某些情况下非常重要,因为防火墙规则的顺序会影响数据包的匹配结果。
以50_ssh.nft为例,是一条允许SSH端口访问的规则:
1 | # /etc/nftables.d/50_ssh.nft |
这种模块化的方式带来了诸多好处:
- 清晰明了:当你需要修改某个服务的防火墙规则时,你只需要找到对应的文件,而不用在庞大的规则集中搜索。
- 易于维护:添加或删除一个服务的防火墙规则,只需要简单地添加或删除一个对应的
.nft文件即可。 - 便于自动化:你可以通过脚本来动态生成和管理这些规则文件。
实践:构建你自己的nftables规则集
简单配置
安装和启用nftables
在大多数现代Linux发行版中,
nftables都已经预装。如果没有,你可以使用包管理器来安装:1
2
3
4
5# Debian/Ubuntu
sudo apt install nftables
# CentOS/RHEL
sudo yum install nftables然后启用并启动
nftables服务:1
2sudo systemctl enable nftables
sudo systemctl start nftables创建第一个规则文件
让我们来创建一个简单的规则集。首先,清空现有的规则:
1
sudo nft flush ruleset
然后,在
/etc/nftables.d/目录下创建一个名为00_base.nft的文件,用于存放一些基础规则:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26# /etc/nftables.d/00_base.nft
# 创建一个名为 'filter' 的表,用于IPv4和IPv6
table inet filter {
# 创建 input, forward, output 三个基本链
chain input {
type filter hook input priority 0;
policy drop; # 默认策略为丢弃所有入站连接
# 允许回环接口的流量
iifname "lo" accept
# 允许已建立和相关的连接
ct state established,related accept
}
chain forward {
type filter hook forward priority 0;
policy drop; # 默认不转发任何数据包
}
chain output {
type filter hook output priority 0;
policy accept; # 默认允许所有出站连接
}
}在这个基础规则集中,我们设置了默认的
input和forward策略为drop,这是一个比较安全的默认设置。同时,我们允许了回环接口的流量和已经建立的连接,这是保证系统正常运行所必需的。为服务添加规则
现在,我们可以像你的例子中那样,为SSH服务创建一个单独的规则文件
50_ssh.nft:1
2# /etc/nftables.d/50_ssh.nft
add rule inet filter input tcp dport 22 accept comment "Allow ssh"如果你还想开放HTTP和HTTPS端口,可以创建一个
60_web.nft文件:1
2# /etc/nftables.d/60_web.nft
add rule inet filter input tcp dport { 80, 443 } accept comment "Allow http and https"这里我们使用了
nftables的集合语法,将80和443两个端口放在一个大括号内,非常简洁。加载规则
最后,编辑主配置文件
/etc/nftables.conf,确保包含了include语句:1
2
3
4
5
6
7flush ruleset
table inet filter {
# ... (这里可以保留一些核心的链定义,也可以像我们上面那样在00_base.nft中定义)
}
include "/etc/nftables.d/*.nft"然后,重新加载
nftables服务来应用你的规则:1
sudo systemctl reload nftables
你可以使用
sudo nft list ruleset来查看当前生效的所有规则。
进阶
现在,让我们深入探讨一些更复杂的场景,包括处理IP段、使用ipset、端口转发(DNAT)和安全加固策略。
这些示例可以直接添加到你的/etc/nftables.d/目录下的相应配置文件中。
示例1:处理IP地址段 (CIDR)
nftables原生支持CIDR(无类别域间路由)表示法,这使得处理整个网段变得非常简单。
场景:只允许公司内网 192.168.10.0/24 网段访问SSH(端口22)。
首先,修改你的 50_ssh.nft 文件,注释掉或删除之前简单的 accept 规则。然后添加更精细的规则:
1 | # /etc/nftables.d/50_ssh.nft |
示例2:使用命名集 (Named Sets) 管理多个IP或端口
当需要管理的IP地址或端口列表很长时,使用set会极大提升规则的可读性和性能。
场景:你有多个管理员的IP地址,需要允许他们访问所有端口。同时,有一些已知的恶意IP地址需要被永久封禁。
创建一个专门管理黑白名单的文件,例如 /etc/nftables.d/05_sets.nft,确保的加载顺序靠前。
1 | # /etc/nftables.d/05_sets.nft |
然后,在你的主输入链规则中引用这些set。可以修改 /etc/nftables.d/10_base_rules.nft:
1 | # /etc/nftables.d/10_base_rules.nft |
动态管理Set:你可以随时通过命令行向set中添加或删除元素,而无需重载整个防火墙规则。
1 | # 动态添加一个恶意IP到黑名单 |
示例3:端口转发 / DNAT (Destination NAT)
这是非常常见的需求,比如将服务器公网IP的某个端口映射到内网的一台虚拟机或Docker容器上。
场景:将服务器公网接口 eth0 的 8443 端口收到的所有TCP流量,转发到内网 10.0.0.5 的 443 端口。
这需要一个新的 nat 表。创建一个文件 /etc/nftables.d/90_nat.nft:
1 | # /etc/nftables.d/90_nat.nft |
重要提示:
- 在
nat表的prerouting链中执行DNAT后,数据包的目标地址就被修改了。因此,在filter表的input或forward链中,你需要根据新的目标地址(即10.0.0.5:443)来编写放行规则。 priority值很重要,prerouting的 NAT 操作通常需要较早执行。
示例4:安全加固 - 防止SSH暴力破解
我们可以利用nftables的set和动态更新能力来限制来自单个IP的连接速率。
场景:限制每个IP在1分钟内最多只能尝试3次新的SSH连接。
创建一个新文件 /etc/nftables.d/51_ssh_bruteforce_protection.nft:
1 | # /etc/nftables.d/51_ssh_bruteforce_protection.nft |
规则解释:
ct state new: 只对新的连接请求生效。update @ssh_bruteforce { ip saddr counter packets 1 }: 当一个新连接到达时,将源IP地址添加到ssh_bruteforce集合中,并将其数据包计数器加1。如果该IP已存在,则只更新计数器。over 3: 如果该IP的计数器超过3。reject with tcp reset: 则拒绝该数据包。- 因为集合设置了
timeout 1m,所以计数器会在1分钟后清零,IP会从集合中移除,允许该IP再次尝试连接
示例5:日志记录 (Logging)
排查网络问题时,日志是至关重要的。你可以轻松地为特定的规则添加日志。
场景:记录所有被默认drop策略丢弃的入站数据包,但为了避免日志泛滥,进行速率限制。
在你的基础规则文件 /etc/nftables.d/10_base_rules.nft 的input链的末尾,也就是drop策略生效前,添加日志规则。
1 | # /etc/nftables.d/10_base_rules.nft (追加到 input 链的最后) |
你可以通过 journalctl -f 或查看 /var/log/syslog (取决于你的系统) 来看到这些日志信息。
ipv6配置
关键区别:ICMPv6的重要性
在开始之前,必须强调一点:你不能像对待IPv4的ICMP那样,粗暴地完全drop掉所有ICMPv6流量。ICMPv6承载了许多IPv6网络正常运行所必需的核心功能,例如:
- **邻居发现协议 (NDP)**:替代了IPv4中的ARP。
- **路径MTU发现 (PMTUD)**:用于确定网络路径上的最大传输单元。
- **路由器通告 (RA)**:用于地址自动配置。
如果阻止了这些必要的ICMPv6类型,你的IPv6连接将会出现各种奇怪的问题,甚至完全中断。
示例1:独立的IPv6基础规则集 (使用 ip6 family)
如果你想为IPv4和IPv6维护两套独立的规则,你可以创建一个专门的 ip6 family 表。这种方式逻辑清晰,易于理解。
场景:创建一个基础的IPv6防火墙,默认拒绝所有入站连接,但允许已建立的连接和保障网络正常运行所必需的ICMPv6流量。
创建一个新文件,例如 /etc/nftables.d/01_base_ipv6.nft:
1 | # /etc/nftables.d/01_base_ipv6.nft |
解释:
table ip6 filter: 明确指定这是一个用于IPv6协议族的表。ip6 nexthdr icmpv6: 匹配IPv6协议中的ICMPv6流量。icmpv6 type {...}: 使用一个集合来明确指定需要放行的ICMPv6类型。这里包含了邻居发现、路由器通告、ping请求/响应以及路径MTU发现所必需的类型。
示例2:统一的IPv4/IPv6规则集 (使用 inet family)
这是nftables最优雅的特性之一:使用inet family可以同时处理IPv4和IPv6,避免规则冗余。这也是目前推荐的做法。
场景:在一个规则集中同时管理SSH访问,无论是通过IPv4还是IPv6。
假设你已经有了一个基于inet family的基础配置文件(如之前文章所述),现在我们来修改50_ssh.nft,使其同时对两种协议生效。
1 | # /etc/nftables.d/50_ssh.nft |
没错,就是这么简单!如果你的表是inet类型,那么像tcp dport 22 accept这样的规则会自动匹配IPv4和IPv6的TCP流量。
场景:只允许特定的IPv6网段访问,同时保持原有的IPv4网段规则。
1 | # /etc/nftables.d/50_ssh.nft (更复杂的版本) |
解释:
ip saddr: 明确匹配IPv4源地址。ip6 saddr: 明确匹配IPv6源地址。- 通过这种方式,你可以在同一个
inet表的input链中,为不同协议族设置不同的来源地址策略。
示例3:阻止特定的IPv6地址
和IPv4类似,你可以使用set来管理IPv6的黑名单。
在你的 05_sets.nft 文件中,可以创建一个IPv6地址的集合。
1 | # /etc/nftables.d/05_sets.nft (追加内容) |
然后,在你的基础规则中引用:
1 | # /etc/nftables.d/10_base_rules.nft (追加内容) |
动态管理: sudo nft add element inet filter blacklist_v6 { "2001:db8:new:bad::guy" }
关于IPv6 NAT (NPTv6) 的说明
在IPv4中,NAT(特别是Masquerade)被广泛用于解决IP地址短缺的问题。但在IPv6的世界里,由于地址空间极其庞大,通常不推荐也不需要使用传统的NAT。每个设备都应该拥有一个全局唯一的公网IPv6地址。
不过,在某些特定场景下,比如更换ISP时希望保持内部网络前缀不变,可能会用到 **NPTv6 (IPv6-to-IPv6 Network Prefix Translation)**。执行的是一对一的前缀转译,而不是像IPv4 NAT那样共享一个地址。
nftables同样支持NPTv6,其配置示例如下(这是一个概念性示例,实际前缀需替换):
1 | # 仅为演示目的,大多数用户不需要这个 |
查看规则
最全面的命令:sudo nft list ruleset
这个命令会以一种结构化的方式,输出当前内核中加载的所有 nftables 配置。输出内容包括:
- **所有的表 (tables)**,例如
table inet filter - **每个表中的所有链 (chains)**,例如
chain input - **每个链中的所有规则 (rules)**,以及顺序
- **每条规则的计数器 (counters)**,显示有多少数据包(packets)和字节(bytes)匹配了该规则。这对于排错和分析流量非常有用!
输出示例及解读
假设你应用了我们之前讨论过的规则,那么 sudo nft list ruleset 的输出可能看起来像这样:
1 | table inet filter { # ← 表:协议族为inet,名称为filter |
解读要点:
- 顺序很重要:规则是从上到下依次匹配的。
- **计数器
(counters)**:如果一条规则的packets计数器大于0,说明有流量成功匹配了这条规则。如果计数器一直是0,说明这条规则可能从未被触发过。
更精确地查看
当你的规则集非常庞大时,你可能只想查看其中的一部分。
查看单个表
如果你只想看 inet filter 这个表的内容:
1 | sudo nft list table inet filter |
查看单个链
这是最常用的精确查看方式,比如只看 input 链的规则:
1 | sudo nft list chain inet filter input |
这个命令的输出会比 list ruleset 更简洁,因为只显示你关心的那一部分规则。
带选项的查看(非常有用)
显示规则句柄 (-a / --handle)
有时候你需要对某条已存在的规则进行操作(比如删除或插入),这时你就需要知道的“句柄”(handle),这是一个唯一的数字ID。
1 | sudo nft -a list chain inet filter input |
输出会变成这样:
1 | chain input { |
有了 handle 12 这个数字,你就可以精确地删除这条规则: sudo nft delete rule inet filter input handle 12。
以数字形式显示 (-n / --numeric)
默认情况下,nft 会尝试将端口号解析成服务名(比如 22 -> ssh)。使用 -n 选项可以阻止这种解析,全部以数字形式显示,有时会更清晰。
1 | sudo nft -n list ruleset |
总结
| 命令 | 用途 |
|---|---|
sudo nft list ruleset |
最常用,查看所有生效的表、链和规则。 |
sudo nft list table <family> <name> |
查看一个完整的表。 |
sudo nft list chain <family> <tbl> <chn> |
很常用,只查看一个链中的规则,更聚焦。 |
sudo nft -a list ... |
操作必备,显示规则句柄,用于删除/修改。 |
sudo nft -n list ... |
以纯数字显示IP和端口,避免名称解析。 |