告别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引入了对象的概念,例如 setsmaps,可以存储和查询数据,使得规则更加灵活和高效。

iptablesnftables的平滑过渡

对于习惯了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
2
3
4
01_wwan.nft      445_samba.nft    51_hotspot.nft    6800.nft         8083.nft         9898.nft
10_dhcp.nft 4533.nft 51_usb_inet.nft 6880.nft x-ui.nft
3000_eapi.nft 50_ssh.nft 5244_alist.nft 80.nft 9000_mihomo.nft
443.nft 51_docker.nft 5700_ql.nft 8001.nft 9876_ddns.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
2
# /etc/nftables.d/50_ssh.nft
add rule inet filter input tcp dport 22 accept

这种模块化的方式带来了诸多好处:

  • 清晰明了:当你需要修改某个服务的防火墙规则时,你只需要找到对应的文件,而不用在庞大的规则集中搜索。
  • 易于维护:添加或删除一个服务的防火墙规则,只需要简单地添加或删除一个对应的.nft文件即可。
  • 便于自动化:你可以通过脚本来动态生成和管理这些规则文件。

实践:构建你自己的nftables规则集

简单配置

  1. 安装和启用nftables

    在大多数现代Linux发行版中,nftables都已经预装。如果没有,你可以使用包管理器来安装:

    1
    2
    3
    4
    5
    # Debian/Ubuntu
    sudo apt install nftables

    # CentOS/RHEL
    sudo yum install nftables

    然后启用并启动nftables服务:

    1
    2
    sudo systemctl enable nftables
    sudo systemctl start nftables
  2. 创建第一个规则文件

    让我们来创建一个简单的规则集。首先,清空现有的规则:

    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; # 默认允许所有出站连接
    }
    }

    在这个基础规则集中,我们设置了默认的inputforward策略为drop,这是一个比较安全的默认设置。同时,我们允许了回环接口的流量和已经建立的连接,这是保证系统正常运行所必需的。

  3. 为服务添加规则

    现在,我们可以像你的例子中那样,为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两个端口放在一个大括号内,非常简洁。

  4. 加载规则

    最后,编辑主配置文件/etc/nftables.conf,确保包含了include语句:

    1
    2
    3
    4
    5
    6
    7
    flush 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# /etc/nftables.d/50_ssh.nft

# 定义一个名为 'filter' 的表和 'input' 链(如果你的基础配置中已定义,则无需重复定义)
# table inet filter {
# chain input {
# type filter hook input priority 0; policy drop;
# }
# }

# 允许来自特定IP段的SSH访问
add rule inet filter input ip saddr 192.168.10.0/24 tcp dport 22 accept

# (可选)如果你需要从公网某个固定IP访问,可以追加一条规则
# add rule inet filter input ip saddr 203.0.113.5 tcp dport 22 accept

# 注意:为了安全,确保没有其他规则会允许所有IP访问22端口

示例2:使用命名集 (Named Sets) 管理多个IP或端口

当需要管理的IP地址或端口列表很长时,使用set会极大提升规则的可读性和性能。

场景:你有多个管理员的IP地址,需要允许他们访问所有端口。同时,有一些已知的恶意IP地址需要被永久封禁。

创建一个专门管理黑白名单的文件,例如 /etc/nftables.d/05_sets.nft,确保的加载顺序靠前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# /etc/nftables.d/05_sets.nft

# 定义 filter 表
table inet filter {
# 定义一个名为 'allow_admins' 的集合,用于存放IPv4地址
set allow_admins {
type ipv4_addr
elements = { 1.1.1.1, 2.2.2.2, 192.168.10.100 }
}

# 定义一个名为 'blacklist' 的集合
set blacklist {
type ipv4_addr
flags timeout # (可选)可以给集合中的元素设置超时自动过期
}
}

然后,在你的主输入链规则中引用这些set。可以修改 /etc/nftables.d/10_base_rules.nft

1
2
3
4
5
6
7
8
9
# /etc/nftables.d/10_base_rules.nft

# 引用之前定义的 'blacklist' 集合,阻止所有来自黑名单的IP
add rule inet filter input ip saddr @blacklist drop

# 引用 'allow_admins' 集合,允许管理员IP访问所有端口
add rule inet filter input ip saddr @allow_admins accept

# ... 其他规则 ...

动态管理Set:你可以随时通过命令行向set中添加或删除元素,而无需重载整个防火墙规则。

1
2
3
4
5
6
7
8
# 动态添加一个恶意IP到黑名单
sudo nft add element inet filter blacklist { 3.3.3.3 }

# 添加一个带30分钟超时的IP
sudo nft add element inet filter blacklist { 4.4.4.4 timeout 30m }

# 查看set中的内容
sudo nft list set inet filter blacklist

示例3:端口转发 / DNAT (Destination NAT)

这是非常常见的需求,比如将服务器公网IP的某个端口映射到内网的一台虚拟机或Docker容器上。

场景:将服务器公网接口 eth08443 端口收到的所有TCP流量,转发到内网 10.0.0.5443 端口。

这需要一个新的 nat 表。创建一个文件 /etc/nftables.d/90_nat.nft

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# /etc/nftables.d/90_nat.nft

table inet nat {
chain prerouting {
type nat hook prerouting priority -100;

# 将公网接口eth0收到的、目标端口为8443的TCP流量
# 转发到内网IP 10.0.0.5 的 443 端口
iifname "eth0" tcp dport 8443 dnat to 10.0.0.5:443
}

chain postrouting {
type nat hook postrouting priority 100;

# 确保来自内网的数据包在出去时,源地址被正确转换为公网IP
# 这对于让内网服务能够响应外部请求至关重要
ip saddr 10.0.0.0/24 oifname "eth0" masquerade
}
}

重要提示

  1. nat表的prerouting链中执行DNAT后,数据包的目标地址就被修改了。因此,在filter表的inputforward链中,你需要根据新的目标地址(即10.0.0.5:443)来编写放行规则。
  2. priority 值很重要,prerouting 的 NAT 操作通常需要较早执行。

示例4:安全加固 - 防止SSH暴力破解

我们可以利用nftablesset和动态更新能力来限制来自单个IP的连接速率。

场景:限制每个IP在1分钟内最多只能尝试3次新的SSH连接。

创建一个新文件 /etc/nftables.d/51_ssh_bruteforce_protection.nft

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# /etc/nftables.d/51_ssh_bruteforce_protection.nft

table inet filter {
# 创建一个set来追踪发起SSH连接的IP地址
set ssh_bruteforce {
type ipv4_addr
flags dynamic # 'dynamic' 标志允许我们添加带有计数器和超时的条目
timeout 1m # 记录的IP地址在一分钟后会自动移除
size 65535 # set 的大小
}
}

# 在 input 链中添加规则
# 注意: 这条规则需要放在允许SSH连接的规则之前
add rule inet filter input tcp dport 22 ct state new \
update @ssh_bruteforce { ip saddr counter packets 1 } \
over 3 reject with tcp reset

规则解释

  • 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.nftinput链的末尾,也就是drop策略生效前,添加日志规则。

1
2
3
4
5
6
7
8
9
10
# /etc/nftables.d/10_base_rules.nft (追加到 input 链的最后)

# ... 已有的 accept 规则 ...

# 对所有将要被丢弃的包进行日志记录
# limit rate 5/minute: 每分钟最多记录5条,防止日志刷屏
# log prefix "[NFTABLES DROP]: ": 为日志添加一个自定义前缀,方便在系统日志中过滤
add rule inet filter input limit rate 5/minute log prefix "[NFTABLES DROP]: "

# 链的默认策略是 drop, 所以执行完日志后,数据包会被自动丢弃

你可以通过 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
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# /etc/nftables.d/01_base_ipv6.nft

# 创建一个专门用于IPv6的表
table ip6 filter {
chain input {
type filter hook input priority 0;
policy drop; # 默认策略为丢弃

# 1. 允许回环接口流量 (等同于 ::1)
iifname "lo" accept

# 2. 允许已建立和相关的连接
ct state established,related accept

# 3. 允许保障IPv6核心功能的ICMPv6流量
# - Neighbor Discovery, Router Advertisement, etc.
ip6 nexthdr icmpv6 icmpv6 type {
nd-neighbor-solicit,
nd-neighbor-advert,
nd-router-solicit,
nd-router-advert,
echo-request,
echo-reply,
packet-too-big
} accept

# 在此之后可以添加其他 'accept' 规则
# ...
}

chain forward {
type filter hook forward priority 0;
policy drop;
}

chain output {
type filter hook output priority 0;
policy accept; # 允许所有出站流量
}
}

解释

  • 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
2
3
4
# /etc/nftables.d/50_ssh.nft

# 这条规则在 inet family 的表中,所以对IPv4和IPv6都有效
add rule inet filter input tcp dport 22 accept

没错,就是这么简单!如果你的表是inet类型,那么像tcp dport 22 accept这样的规则会自动匹配IPv4和IPv6的TCP流量。

场景:只允许特定的IPv6网段访问,同时保持原有的IPv4网段规则。

1
2
3
4
5
6
7
# /etc/nftables.d/50_ssh.nft (更复杂的版本)

# 允许来自特定IPv4段的SSH访问
add rule inet filter input ip saddr 192.168.10.0/24 tcp dport 22 accept

# 允许来自特定IPv6段的SSH访问
add rule inet filter input ip6 saddr 2001:db8:1::/64 tcp dport 22 accept

解释

  • ip saddr: 明确匹配IPv4源地址。
  • ip6 saddr: 明确匹配IPv6源地址。
  • 通过这种方式,你可以在同一个inet表的input链中,为不同协议族设置不同的来源地址策略。

示例3:阻止特定的IPv6地址

和IPv4类似,你可以使用set来管理IPv6的黑名单。

在你的 05_sets.nft 文件中,可以创建一个IPv6地址的集合。

1
2
3
4
5
6
7
8
9
10
11
# /etc/nftables.d/05_sets.nft (追加内容)

table inet filter {
# ... 已有的IPv4 set ...

# 定义一个名为 'blacklist_v6' 的集合,用于存放IPv6地址
set blacklist_v6 {
type ipv6_addr
elements = { 2001:db8:dead:beef::1, 2001:db8:bad::/48 }
}
}

然后,在你的基础规则中引用:

1
2
3
4
# /etc/nftables.d/10_base_rules.nft (追加内容)

# 阻止所有来自IPv6黑名单的连接
add rule inet filter input ip6 saddr @blacklist_v6 drop

动态管理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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 仅为演示目的,大多数用户不需要这个

table ip6 nat {
chain prerouting {
type nat hook prerouting priority -150;
# 将外部前缀 2001:db8:1::/48 转换为内部前缀 fd00:1234::/48
ip6 daddr 2001:db8:1::/48 npt to fd00:1234::/48
}

chain postrouting {
type nat hook postrouting priority 150;
# 将内部前缀 fd00:1234::/48 转换回外部前缀 2001:db8:1::/48
ip6 saddr fd00:1234::/48 npt to 2001:db8:1::/48
}
}

查看规则

最全面的命令:sudo nft list ruleset

这个命令会以一种结构化的方式,输出当前内核中加载的所有 nftables 配置。输出内容包括:

  • **所有的表 (tables)**,例如 table inet filter
  • **每个表中的所有链 (chains)**,例如 chain input
  • **每个链中的所有规则 (rules)**,以及顺序
  • **每条规则的计数器 (counters)**,显示有多少数据包(packets)和字节(bytes)匹配了该规则。这对于排错和分析流量非常有用!

输出示例及解读

假设你应用了我们之前讨论过的规则,那么 sudo nft list ruleset 的输出可能看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
table inet filter { # ← 表:协议族为inet,名称为filter
chain input { # ← 链:名称为input
type filter hook input priority filter; policy drop; # 链的类型、钩子、优先级和默认策略
ct state established,related accept # 规则1: 接受已建立的连接 (counters: packets 1024 bytes 123456)
iifname "lo" accept # 规则2: 接受回环接口流量 (counters: packets 50 bytes 4321)
ip saddr 192.168.1.0/24 accept # 规则3: 接受局域网所有流量 (counters: packets 200 bytes 24680)
tcp dport 22 accept # 规则4: 接受SSH流量 (counters: packets 5 bytes 640)
}

chain forward {
type filter hook forward priority filter; policy drop;
}

chain output {
type filter hook output priority filter; policy accept;
}
}

解读要点

  • 顺序很重要:规则是从上到下依次匹配的。
  • **计数器 (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
2
3
4
5
6
7
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept # handle 4
iifname "lo" accept # handle 5
ip saddr 192.168.1.0/24 accept # handle 12
tcp dport 22 accept # handle 13
}

有了 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和端口,避免名称解析。