Linux nftables 防火墙
nftables
是一个 netfilter
项目,旨在替换现有的 {ip,ip6,arp,eb}tables
框架,为 {ip,ip6}tables
提供一个新的包过滤框架、一个新的用户空间实用程序(nft
)和一个兼容层(iptables-nft
)。它使用现有的钩子、链接跟踪系统、用户空间排队组件和 netfilter
日志子系统。
在 Linux 内核版本高于 3.13 时可用
它由三个主要组件组成:
- 内核实现: 内核提供了一个
netlink
配置接口以及运行时规则集评估 libnl netlink
:libnl
包含了与内核通信的基本函数nftables
: 用户空间前端。nftables
的用户空间实用程序nft
评估大多数规则集并传递到内核。规则存储在链中,链存储在表中。
与 iptables
不同点
nftables
在用户空间中运行,iptables
中的每个模块都运行在内核(空间)中- 表和链是完全可配置的。在
nftables
中,表是没有特定语义的链的容器。iptables
附带了具有预定义数量的基链的表,即使您只需要其中之一,所有链都已注册,未使用的基础链也会损害性能。 - 可以在一个规则中指定多个操作。在
iptables
中,您只能指定一个目标。这是一个长期存在的局限性,用户可以通过跳到自定义链来解决,但代价是使规则集结构稍微复杂一些。 - 每个链和规则没有内置计数器。在
nftables
中,这些是可选的,因此您可以按需启用计数器。由于iptables
内置了一个数据包计数器,所以即使这些内置的链是空的,也会带来性能损耗 - 更好地支持动态规则集更新。在
nftables
中,如果添加新规则,则剩余的现有规则将保持不变,因为规则集以链表形式表示,这与整体式 blob 表示相反,后者在执行规则集更新时内部状态信息的维护很复杂。 - 简化的双堆栈 IPv4/IPv6 管理,通过新的
inet
系列,可让您注册同时查看 IPv4 和 IPv6 流量的基链。 因此,您不再需要依赖脚本来复制规则集。
服务名称默认为 nftables
,默认配置文件为 /etc/nftables.conf
,其中已经包含一个名为 inet filter
的简单 ipv4/ipv6
防火墙列表。
!/usr/sbin/nft -f |
nftables
服务的 systemd
配置文件如下:
[Unit] |
nftables
使用的内核模块如下,加载这些模块,服务才能正常运行
lsmod | grep nf |
nftables 配置构成组件
nftables Tables
nftables
中 表(Tables) 是没有特定语义的 链(Chains) 的容器,表 包含 链。
与 iptables
中的表不同,nftables
中没有内置表。表的数量和名称都由管理员自定义,但是,每个表都只能关联一个地址簇(Address Family),并且只适用于该地址簇的数据包。 表支持的地址簇如下:
nftables 框架地址簇 | netfilter 工具 | 说明 |
---|---|---|
ip |
iptables |
IPv4 地址 |
ip6 |
ip6tables |
IPv6 地址 |
inet |
iptables 和 ip6tables |
同时包含 IPv4 和 IPv6 地址 |
arp |
arptables |
ARP 协议地址 |
bridge |
ebtables |
ip
(即IPv4)是默认的地址簇,如果未明确指定地址簇,则使用ip
要创建同时适用于 IPv4 和I Pv6 的规则,请使用
inet
。inet
允许统一ip
和ip6
簇,以便更容易地定义规则。
- 注意:
inet
不能用于route
类型的链,只能用于filter
和nat
类型的链。 具体信息可以查看man nft
- 可以定义多个 相同地址簇(类型) 的表,如定义多个都是
inet
类型的表。每个表可以有自己的链(chain
)和规则集(ruleset
),但这些表相互独立地存在,且没有直接的依赖关系。但 每个表中的链的处理顺序由链的优先级决定 。
nftables Chains
nftables
中 链(Chains) 的目的是保存规则。 与 iptables
中的链不同,nftables
没有内置链。 和表一样,链也需要被显示创建 。链有以下两种类型:
- 基本链 : 数据包的入口点,必须(强制)指定 type , 钩子(hook) 和 优先级(priority) ,相当于
itables
的内置链 - 常规链 : 不需要指定 钩子类型 和 优先级 ,可以用来做跳转,从逻辑上对规则进行分类,类似于
itables
的自定义链
这意味着与 iptables
不同,如果链没有使用 netfilter
框架中的任何类型或钩子,则流经这些链的数据包不会被 nftables
触及(处理) 。数据包要被实际处理,需要使用 链(Chains) 中的规则捕获包(使用具体的类型( filter
,nat
,route
)和钩子( prerouting
,input
,forward
,output
,postrouting
))
- 每个链(
chain
)都有一个 优先级(priority) 属性,决定了该链在处理数据包时的优先顺序。优先级值可以是正数、负数或零,数值越小,优先级越高。 - 优先级仅在基本链(
base chain
)中定义 ,因为这些链直接挂载在网络堆栈的处理钩子(hook
)上,参与系统的流量处理流程。
链的默认策略
在 nftables
中,默认策略 指的是在链(chain
)中当 没有匹配到任何规则时 ,数据包会执行的操作。与传统的 iptables
一样,nftables
也允许为每个 基本链(base chain
) 设置默认策略,比如 接受(accept
) 或 丢弃(drop
) 数据包。
默认策略为 accept
在 nftables
中,默认策略是通过设置链的 策略(policy
) 来定义的,只有 基本链(base chain
) 才能有默认策略。对于非基本链(比如用户自定义的链),需要在链中的规则中明确指定处理数据包的行为。
以下示例 创建基本链并设置默认策略 :
nft add table ip filter |
可以使用以下命令来 查看表和链的配置以及默认策略 :
nft list ruleset |
如果需要 修改已有链的默认策略 ,可以使用以下命令来更新。例如:
nft chain ip filter INPUT { policy drop\; } |
配置默认策略的注意事项 :
- 仅基本链支持默认策略 :只有与
hook
(如input
、output
、prerouting
等)关联的基本链才能有默认策略。自定义的非基本链必须显式定义处理规则。 - 优先级 :当链有多个规则时,数据包首先会根据链中的规则进行匹配。如果没有规则匹配,默认策略才会生效。因此,如果链中存在明确的
accept
或drop
规则,默认策略只会在所有规则都没有匹配时起作用。
nftables Rules
nftables
中 规则(Rules) 是实际处理数据包的语句或者表达式(EXPRESSION
),是真正的 Action,包含在 链(Chains) 中。
通常情况下,EXPRESSION
包含一些要匹配的表达式,然后是判断语句。结论语句包括 accept
、drop
、queue
、continue
、return
、jump chain
和 goto chain
。也可能是其他陈述。有关信息信息,请参阅 nft(8)
。
- 规则本身没有优先级属性 。规则在链中按 添加顺序 依次被处理。也就是说,链中的规则是按它们在链中添加的顺序进行匹配。
EXPRESSIONS
详细的 EXPRESSIONS
可以查看 man nft
DATA TYPES
nftables
中的每个 expressions
都有对应的数据类型(datatype
),具体的数据类型可以使用 nft describe
命令查看,如以下示例:
nft describe tcp flags |
Data Types
决定了对应的类型的值的大小,其代表的含义以及可以和它兼容的其他表达式。大多数的 data types
有固定的大小(长度),也有些 data types
有可变大小的值(如 字符串(string
))。也有些类型有预定义的常量值,如 tcp_flag
。
查看 data types
使用 nft describe <DATA_TYPE>
命令,如
nft describe tcp_flag |
有些 data type
是由其他基本的 data type
(称为 basetype
)组成,比如 ipv4_addr
由 integer
组成
META EXPRESSIONS
META EXPRESSIONS
是对数据包基本信息的描述。META EXPRESSIONS
有 2 种类型:
Qualified Meta Expressions
: 必须包含meta
关键字(在meta key
之前)Unqualified Meta Expressions
: 可以直接使用meta key
而无需meta
关键字
Meta expression types ,详细信息请参考 man nft
关键字 | datatype | 说明 | 示例 |
---|---|---|---|
iif oif |
iface_index |
出入网卡(接口)的 index |
和 iifname 的区别 |
iifname oifname |
string |
出入接口(网卡)名称 | iifname eth0 |
iiftype oiftype |
iface_type |
支持的类型包括:ether 、ppp 、ipip 、ipip6 、loopback 、sit 、ipgre 通过 nft describe iface_type 查看 |
|
ether |
lladdr (Link layer address),( basetype integer ) |
数据链路层协议(MAC 地址) | ether daddr 20:c9:d0:43:12:d9 ether saddr 20:c9:d0:43:12:d9 |
ip |
ipv4_addr ( basetype integer ) |
ipv4 地址 | ip daddr 127.0.0.1 ip saddr 127.0.0.1 |
ip6 |
ipv6_addr ( basetype integer ) |
ipv6 地址 IPv6 地址后面带端口时,IPv6 地址要使用 [] ,如 [1ce::d0]:22 ,否则后面的端口会被当作 IPv6 地址的一部分 |
ip6 saddr ::1 ip6 daddr ::1 ip6 nat prerouting tcp dport 2222 dnat to [1ce::d0]:22 |
icmp |
icmp_type ( basetype integer ) |
ICMP 协议 | icmp type { echo-request, echo-reply } |
icmpv6 |
icmpv6_type ( basetype integer ) |
ICMP_v6 协议 | icmpv6 type { echo-request, echo-reply } |
length |
integer |
数据包的长度(大小) ,单位为 bytes |
|
nfproto |
integer |
处理的数据包的实际的 Address Family,仅在 inet 类型的表中使用 |
|
l4proto |
integer |
4 层协议,不包括 IPv6 的扩展头部 | |
protocol |
ether_type |
EtherType 协议,包括(可选) ip 、ip6 、vlan 、arp 、8021q 、8021ad nft describe ether_type |
|
ipsec |
boolean |
数据是否由 ipsec 加密 |
|
time day hour |
integer integer string |
数据包的时间 |
iif 和 iifname 的异同
iif
、oif
和 iifname
、oifname
都是用来匹配网卡。不同之处在于:
iif
和oif
是通过interface index
来匹配网卡。使用此方式时,网卡必须存在,并且在网卡名称变更后依然生效,这是因为在内部使用的是interface index
而不是网卡名称iifname
和oifname
是通过网卡名称(interface name
) 类匹配网卡。使用此方式时,网卡名称对应的网卡可以不存在 ,或者在网卡名称变更后不会在匹配到数据包,网卡名称改回来后或者同名的网卡出现后依旧可以匹配数据包。
iptables 规则转换为 nftables 规则
nftables
提供了工具 iptables-translate
用来将 iptables
规则转换为 nftables
规则
- 转换单个规则
iptables-translate -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
nft add rule ip filter INPUT tcp dport 22 ct state new counter accept
ip6tables-translate -A FORWARD -i eth0 -o eth3 -p udp -m multiport --dports 111,222 -j ACCEPT
nft add rule ip6 filter FORWARD iifname "eth0" oifname "eth3" meta l4proto udp udp dport { 111,222} counter accept - 转换规则集
iptables-save > iptables_save.txt
cat iptables_save.txt
Generated by xtables-save v1.8.2 on Wed Feb 5 12:05:26 2020
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
COMMIT
Completed on Wed Feb 5 12:05:26 2020
iptables-restore-translate -f iptables_save.txt
Translated by iptables-restore-translate v1.8.2 on Wed Feb 5 12:05:38 2020
add table ip filter
add chain ip filter INPUT { type filter hook input priority 0; policy accept; }
add chain ip filter FORWARD { type filter hook forward priority 0; policy accept; }
add chain ip filter OUTPUT { type filter hook output priority 0; policy accept; }
add rule ip filter INPUT tcp dport 22 ct state new counter accept
Completed on Wed Feb 5 12:05:38 2020
nftables 中规则的处理顺序
在 nftables
中,规则的处理顺序主要由以下几个因素共同决定: 表(table)
、 链(chain)
的 type
和 hook
、优先级(priority
) ,以及 规则的添加顺序。
nftables
规则首先按表来组织。每个表包含一组链,链是由特定类型(type
)和挂载点(hook
)决定的。 表并不会直接影响规则的处理顺序,基本链(type hook
)和优先级才是主要决定因素 。链的
type
和hook
决定了数据包在网络堆栈中在哪个阶段处理,例如,流量在进入input
链时,可能已经经过了prerouting
链的处理。type
filter
: 用于过滤数据包(常见的过滤链类型)。nat
: 用于网络地址转换(NAT)。route
: 路由type
不决定顺序,只决定链的用途 。它定义了链的规则是处理 NAT 还是过滤数据包,但 链的实际处理顺序仍由优先级(priority
)决定 。
hook
prerouting
input
forward
output
postrouting
优先级(
priority
),nftables
允许为每条链定义优先级, 优先级的值可以是正数或负数,数值越小,优先级越高 。规则的 处理顺序由添加顺序决定 。也就是说,先添加的规则会先处理。
nft 命令用法
nft
命令的基本格式如下:nft <OPERATOR> <TARGET> <EXPRESSIONS> <STATEMENTS>
OPERATOR
常用命令:
OPERATOR | 说明 | 示例 |
---|---|---|
list |
列出目标(TARGET)中的内容 | nft list tables nft list chains nft list ruleset |
add |
添加目标 支持 table 、chain 、rule 默认如果不指定 handle 则规则添加到链的末尾 |
nft add table <TABLE_NAME> nft add chain <CHAIN> nft add rule <RULE> nft add rule family table chain handle handle statement |
create |
和 add 相同,如果存在同名的规则,返回错误 |
|
rename |
重命名链 | |
delete |
删除目标 支持 table 、chain 、rule 单个规则只能通过其 handle 删除 |
nft delete table <TABLE_NAME> nft delete chain <CHAIN> nft delete rule <RULE> |
flush |
清空目标 支持 table 、chain 、rule |
nft flush tables <TABLE> nft flush chain <ADD_FAMILIY> <TABLE> <CHAIN> |
针对 rule
的其他 OPERATOR
OPERATOR | 说明 | 示例 |
---|---|---|
add |
添加目标 支持 table 、chain 、rule 默认如果不指定 handle 则规则添加到链的末尾 |
nft add table <TABLE_NAME> nft add chain <CHAIN> nft add rule <RULE> nft add rule family table chain handle handle statement |
insert |
插入规则 如果不指定 handle ,则规则插入到链的开头 |
nft insert rule family table chain handle handle statement |
replace |
替换指定的 rule |
nft
命令常用选项,具体说明可以通过 nft -h
或者 man nft
查看
选项 | 说明 | 示例 |
---|---|---|
-h, --help -v, --version |
显示帮助信息 显示版本信息 |
|
-c, --check |
检查命令的有效性,而不实际应用更改 | |
-f , --file |
从指定文件读取配置 | nft -f /etc/nftables.conf |
-i, --interactive |
交互模式,从终端读取输入 | |
-j, --json |
以 JSON 格式输出 | |
-n, --numeric |
指定一次后,以数字方式显示网络地址(默认行为)。指定两次以数字方式显示Internet服务(端口号)。指定三次以数字方式显示协议,用户ID和组ID。 | |
-N |
将 IP 地址转换为名称(反向域名解析) | |
-a, --handle |
显示规则句柄 handle |
|
-e, --echo |
输出 追加 ,插入, 替换(更新) 的内容 | |
-I, --includepath <directory> |
添加 <directory> 目录到包含文件的搜索路径中。默认为: /etc |
|
--debug <level [,level...]> |
添加调试,在 level 处(scanner , parser , eval , netlink , mnl , proto-ctx , segtree , all ) |
常见配置示例
配置默认策略为 drop
!/usr/sbin/nft -f |
在链中使用 policy drop;
配置默认策略为 drop
,如果不指定默认策略,默认为 accept
。链的默认策略相关说明
priority filter
表示优先级为 0,具体可参考man nft
如果需要 修改已有链的默认策略 ,可以使用以下命令来更新。例如:
nft chain ip filter INPUT { policy drop\; } |
允许关联流量
在 nftables
中,允许关联流量(related traffic
)进入是一种常见的防火墙配置。关联流量指的是与已经建立的连接相关的流量。例如,服务器的出站连接可能会产生响应数据包,这些响应属于已经建立的连接,或者是与原始连接有关的消息(如 ICMP 错误消息)。
如果不特意配置允许关联流量(related traffic
)进入,则需要为每一个连接的出方向和入方向同时配置规则 ,假如本地向外发出的连接建立请求,也需要配置对应的允许响应入站,否则无法受到请求的响应,导致最终无法建立连接。
为了允许关联流量进入,通常需要使用 conntrack
模块来跟踪连接的状态。可以通过匹配连接的状态来允许 ESTABLISHED
和 RELATED
状态的流量。
conntrack
状态包括- ESTABLISHED :表示连接已经建立,允许流量在此连接上继续通信。
- RELATED :表示与现有连接有关的流量,例如 ICMP 错误消息等。
- NEW :表示新的连接请求。
- INVALID :表示无法识别的连接状态,通常应丢弃。
nft describe ct state |
可以通过以下规则配置允许关联流量(ESTABLISHED
和 RELATED
)进入,同时丢弃无效的流量:
!/usr/sbin/nft -f |
配置允许 ICMP 协议,及允许主机响应 ping
!/usr/sbin/nft -f |
ip protocol icmp
指定ip
携带(处理)的是 ICMP 协议,支持的协议类型包括:nft describe ip protocol
payload expression, datatype inet_proto (Internet protocol) (basetype integer), 8 bits
ip 0
icmp 1
igmp 2
ggp 3
ipencap 4
st 5
tcp 6
egp 8
igp 9
pup 12
udp 17
hmp 20
...- 在指定特定的 ICMP 类型时,要先指定 ICMP 协议(
ip protocol icmp
) 再指定 ICMP 类型(icmp type <TYPE>
),具体支持的type
可以通过命令nft describe icmp type
查看 - 每个 ICMP
type
需要单独配置
为规则启用计数器
在 nftables
中,默认情况下规则不启用计数器(counters) 。如果你想为特定规则启用计数器来统计匹配的数据包数量和字节数,可以通过 counter
关键字来显式启用。
当添加规则时,只需在 规则末尾、终止语句(terminal statement
)之前 添加 counter
关键字即可。这将为该规则统计所有匹配的数据包数和字节数。
ip protocol icmp icmp type echo-request accept counter; |
添加默认规则处理数据包的计数器
nftables
不能直接在默认策略(policy
)上启用计数器 。为了实现对默认策略处理的流量进行统计,可以通过添加一条 匹配 未匹配到其他规则
的所有流量的规则 并启用 counter
来实现相同的效果。
!/usr/sbin/nft -f |
为规则添加注释
在 nftables
中,你可以为规则添加注释,以帮助描述规则的用途或功能。注释可以为规则提供可读性,尤其在复杂的规则集里,可以帮助系统管理员更容易地理解规则的目的。
nftables
提供了 comment
关键字来为规则添加注释 。
语法 : nft add rule <table> <chain> <match conditions> <action> comment "<your comment>"
!/usr/sbin/nft -f |
在
nftables
中 直接修改规则的注释是不可能的 。如果需要更改注释,可以删除旧规则并重新添加带有更新注释的新规则。在 cmd 交互命令中添加注释时,
comment
后面的内容要使用双引号(")
,并且双引号(")
需要 转义(\
) ,否则命令会报错
# nft insert rule inet filter input handle 11 tcp dport { 80,443 } counter accept comment \"for nginx\"
记录日志消息
在定位防火墙规则相关问题的过程中,经常需要将经过防火墙的数据报文信息记录到日志中,以便定位问题。nftables
提供了 log
表达式来记录日志,日志信息默认写入到 kernel log
(通常是 dmesg
或者 syslog
),如 Ubuntu 系统一般在 /var/log/kern.log
中。具体配置可以参考 man nft
以下示例将所有经过 log
所在规则的数据报文信息写入日志
nft add rule ip filter INPUT log prefix "nftables-log" |
以下示例指定日志级别
nft replace rule ip filter INPUT handle 38 log prefix "nftables-log" level debug |
查看日志信息
tail /var/log/kern.log |
替换规则
假如有以下规则,替换 handle 38
的规则
nft -a list ruleset |
删除规则
删除所有规则
nft flush ruleset |
删除指定的规则:
nft delete rule inet filter input handle 16 |
限制 IP 和 端口
使用关键字 ip saddr
和 ip daddr
分别对 源 IP(source IP Address
) 和 目标 IP(Destination IP Address) 进行过滤
tcp sport
和 tcp dport
对 TCP 源端口(Source Port) 和 TCP 目标端口(Destination Port) 做过滤
以下示例放通指定源 IP 和 目标端口的数据包
ip saddr 172.27.0.2 tcp dport 22 accept comment "allow ssh from 172.27.0.2" |
同时指定多个目标,使用 集合({}
)的语法 来指定多个目标。
nft insert rule inet filter input handle 11 ip saddr { 172.27.0.2,172.27.0.4 } tcp dport { 80,443 } counter accept comment \"for nginx\" |
指定连续的端口,使用 集合格式 { 6379, 7380-7382 }
指定多个连续端口
nft insert rule inet filter input handle 11 ip saddr { 172.27.0.2,172.27.0.4 } tcp dport { 6379,7380-7383 } counter accept comment \"for redis\" |
多个目标 IP 的配置方法
nftables
中 不支持定义连续的 IP 地址 ,如 192.168.1.1-192.168.1.10
,定义连续的地址段只能使用 CIDR 格式(192.168.1.0/24
),如果连续的 IP 地址不在 CIDR 范围内,则需要使用 集合(set
)格式 : { 172.27.0.2,172.27.0.4 }
将所有的 IP 地址列入
假如需要配置的 IP 较多或者分散,不足以使用 CIDR 表示,可以考虑使用 命名的集合 (set
) 先定义目标 IP 地址,然后在 规则中引用命名集合 ,以下示例演示 set
基本用法,SET STATEMENT
详细说明请参考 man nft
创建命名集合,在指定的表(
table
)中创建 命名的集合(set
)nft add set inet filter allowed_ips { type ipv4_addr\; elements = { 192.168.1.1, 192.168.1.2, 192.168.1.3 } }
- **如果集合中有 CIDR 格式的 IP 地址,创建
set
时需要使用flags interval
**,否则会报错:You must add 'flags interval' to your set declaration if you want to add prefix elements
nft add set inet filter ssh_allowed_ips { type ipv4_addr \; flags interval \; }
nft add element inet filter ssh_allowed_ips { 192.168.1.0/24, 192.168.2.0/24 }
- **如果集合中有 CIDR 格式的 IP 地址,创建
引用 命名的集合(
set
)nft insert rule inet filter input handle 11 ip saddr @allowed_ips tcp dport { 80, 443 } counter accept comment \"for nginx\"
查看 规则集(
ruleset
)nft -a list ruleset
table inet filter { # handle 14
set allowed_ips { # handle 28
type ipv4_addr
elements = { 192.168.1.1, 192.168.1.2,
192.168.1.3 }
}
chain input { # handle 1
type filter hook input priority filter; policy drop;
ip protocol icmp icmp type echo-reply accept # handle 4
ip protocol icmp icmp type echo-request accept # handle 5
ct state established,related accept # handle 6
ct state invalid drop # handle 7
iif "lo" accept # handle 8
ip saddr @allowed_ips tcp dport { 80, 443 } counter packets 0 bytes 0 accept comment "for nginx" # handle 30
counter packets 20 bytes 1200 drop # handle 11
}
}更新 命名的集合(
set
) 中的元素nft -a list set inet filter allowed_ips
table inet filter {
set allowed_ips { # handle 28
type ipv4_addr
elements = { 192.168.1.1, 192.168.1.2,
192.168.1.3 }
}
}
# 添加元素
nft add element inet filter allowed_ips { 192.168.1.4, 192.168.1.5 }
nft -a list set inet filter allowed_ips
table inet filter {
set allowed_ips { # handle 28
type ipv4_addr
elements = { 192.168.1.1, 192.168.1.2,
192.168.1.3, 192.168.1.4,
192.168.1.5 }
}
}
# 删除元素
nft delete element inet filter allowed_ips { 192.168.1.1 }
nft -a list set inet filter allowed_ips
table inet filter {
set allowed_ips { # handle 28
type ipv4_addr
elements = { 192.168.1.2, 192.168.1.3,
192.168.1.4, 192.168.1.5 }
}
}
以上方法相当于
iptables
中配合使用ipset
NAT
以下示例测试 DNAT,当请求端口为 8000
时,在 output
钩子上(type nat hook output priority dstnat; policy accept;
)对其进行 DNAT,转为 172.27.0.3:80
chain OUTPUT { # handle 2 |
172.27.0.3:80
上监听了 Nginx HTTP (80
端口),通过 curl -Iv 172.27.0.3:8000
测试,可以正常请求到 Nginx 上面的内容
curl -Iv 172.27.0.3:8000 |
为什么不需要 SNAT?
本示例中假设本机为 T2,目标 Nginx 服务器为 T1,数据包的流向分析如下:
初始请求
在 T2 上执行
curl -Iv T1:8000
,T2 发出数据包,源地址为t2:10000
(假设源端口为10000
),目标地址为t1:8000
,数据报文流向:
t2:10000
->t1:8000
数据包流经网络协议栈,
nftables
的output
钩子对数据包进行了 DNAT,数据报文流向变为:
t2:10000
->t1:80
数据报文经路由后到达 T1,并被监听在
80
端口上的 Nginx 服务处理,Nginx 返回响应,响应包的源和目标地址为
t1:80
->t2:10000
T1 上响应报文(
t1:80
->t2:10000
) 经路由后返回 T2。此时,数据包直接进入 T2 的本地网络堆栈。并被curl
进程接收。连接跟踪(
conntrack
)机制在 T2 上,当
nftables
执行 DNAT 改写目标端口时,Linux 内核的连接跟踪(conntrack
)模块会记录下这一连接的状态,跟踪这个 NAT 连接。当返回数据包到达 T2 时, 内核会根据连接跟踪表自动识别出该数据包属于一个经过 DNAT 的连接,并自动对返回数据包执行逆向的地址转换(即将t1:80
识别为t1:8000
)
nftables
的 DNAT 改写了出站连接的目标地址(端口8000
被改写为80
),但返回时依赖 连接跟踪(conntrack
) 机制来追踪这个连接,因此不需要显式的 SNAT。