IP 中转学习:从 DNAT、SNAT 到 nftables 单跳转发

1341 字
7 分钟
IP 中转学习:从 DNAT、SNAT 到 nftables 单跳转发
Abstract

这篇是 IP 中转的基础篇。它不讨论 PO0 -> RFC -> 多落地那种复杂实践,只先把单跳中转讲清楚:客户端访问中转机,中转机通过 nftables 把流量转到落地机,并用 SNAT/masquerade 保证回包路径正确。文中 IP 使用文档保留地址,端口也都替换成示例值。

IP 中转到底在转什么#

最小模型是这样:

客户端 -> 中转机:52000 -> 落地机:53000

客户端只知道中转机地址。中转机收到访问 52000 端口的包后,把目标地址和端口改成落地机的 53000。这一步叫 DNAT。

但只做 DNAT 还不够。落地机收到包后,如果直接回客户端,客户端看到的来源就不是它最初连接的中转机,连接状态会乱掉。所以中转机还要在包离开自己时做 SNAT 或 masquerade,让落地机看到的来源是中转机。这样回包会先回到中转机,再由中转机交还给客户端。

所以一条完整的单跳中转至少有三段逻辑:

阶段作用
preroutingDNAT:把入口端口改到落地机地址和端口
forward放行这条被转发的流量
postroutingSNAT/masquerade:保证回包回到中转机

示例拓扑#

下面统一使用脱敏示例。

角色示例值
中转机公网地址203.0.113.10
中转机入口端口52000
落地机地址192.0.2.20
落地机服务端口53000
公网入口网卡eth0

客户端填写:

server = 203.0.113.10
port = 52000

实际到达服务端:

203.0.113.10:52000 -> 192.0.2.20:53000

开启内核转发#

Linux 默认不一定允许转发包,所以先打开 IPv4 forward:

Terminal window
apt update
apt install -y nftables conntrack tcpdump
systemctl enable --now nftables
cat <<'EOF' > /etc/sysctl.d/99-forwarding.conf
net.ipv4.ip_forward = 1
net.ipv4.tcp_mtu_probing = 1
net.netfilter.nf_conntrack_max = 65536
EOF
sysctl --system
sysctl net.ipv4.ip_forward

预期:

net.ipv4.ip_forward = 1

nftables 单跳模板#

这是最基础的单跳 TCP + UDP 转发模板:

#!/usr/sbin/nft -f
define WAN_IF = "eth0"
define LANDING_IP = 192.0.2.20
define FRONT_PORT = 52000
define BACKEND_PORT = 53000
destroy table ip one_hop_forward
table ip one_hop_forward {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
iifname $WAN_IF meta l4proto { tcp, udp } th dport $FRONT_PORT dnat to $LANDING_IP:$BACKEND_PORT
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state invalid drop
ct state established,related accept
ip daddr $LANDING_IP meta l4proto { tcp, udp } th dport $BACKEND_PORT ct state new accept
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip daddr $LANDING_IP meta l4proto { tcp, udp } th dport $BACKEND_PORT masquerade
}
}

加载前先检查语法:

Terminal window
nft -c -f /etc/nftables.conf
nft -f /etc/nftables.conf
nft list table ip one_hop_forward

如果系统不支持 destroy table,不要把 2>/dev/null || true 写进 nft 配置文件。那是 shell 语法,只能写在 shell 里:

Terminal window
nft delete table ip one_hop_forward 2>/dev/null || true
nft -c -f /etc/nftables.conf
nft -f /etc/nftables.conf

多个落地端口#

多个落地本质上就是重复几组端口映射。

中转机:52001 -> 落地 1:53001
中转机:52002 -> 落地 2:53002
中转机:52003 -> 落地 3:53003

模板可以写成:

#!/usr/sbin/nft -f
define WAN_IF = "eth0"
define LAND1_IP = 192.0.2.21
define LAND1_FRONT_PORT = 52001
define LAND1_BACKEND_PORT = 53001
define LAND2_IP = 192.0.2.22
define LAND2_FRONT_PORT = 52002
define LAND2_BACKEND_PORT = 53002
define LAND3_IP = 192.0.2.23
define LAND3_FRONT_PORT = 52003
define LAND3_BACKEND_PORT = 53003
destroy table ip multi_forward
table ip multi_forward {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
iifname $WAN_IF meta l4proto { tcp, udp } th dport $LAND1_FRONT_PORT dnat to $LAND1_IP:$LAND1_BACKEND_PORT
iifname $WAN_IF meta l4proto { tcp, udp } th dport $LAND2_FRONT_PORT dnat to $LAND2_IP:$LAND2_BACKEND_PORT
iifname $WAN_IF meta l4proto { tcp, udp } th dport $LAND3_FRONT_PORT dnat to $LAND3_IP:$LAND3_BACKEND_PORT
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state invalid drop
ct state established,related accept
ip daddr $LAND1_IP meta l4proto { tcp, udp } th dport $LAND1_BACKEND_PORT ct state new accept
ip daddr $LAND2_IP meta l4proto { tcp, udp } th dport $LAND2_BACKEND_PORT ct state new accept
ip daddr $LAND3_IP meta l4proto { tcp, udp } th dport $LAND3_BACKEND_PORT ct state new accept
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip daddr $LAND1_IP meta l4proto { tcp, udp } th dport $LAND1_BACKEND_PORT masquerade
ip daddr $LAND2_IP meta l4proto { tcp, udp } th dport $LAND2_BACKEND_PORT masquerade
ip daddr $LAND3_IP meta l4proto { tcp, udp } th dport $LAND3_BACKEND_PORT masquerade
}
}

这就是后来 PO0 -> RFC -> 多落地实践的基础。区别只是那篇实践里,PO0 先把流量转到 RFC,RFC 再做第二次转发。

为什么 forward 链建议显式放行#

有些临时配置会把 forward 链写成 policy accept,这样确实不容易误伤其它转发规则。但作为长期配置,我更喜欢 policy drop,然后只显式接受这几条目标地址和端口。

原因很简单:中转机上可能不止这一条规则。显式写出允许的目标,比“所有转发都放行”更容易维护,也更容易排障。

验证方式#

从外部机器测入口端口:

Terminal window
nc -vz 203.0.113.10 52000

中转机上抓包:

Terminal window
tcpdump -ni any "port 52000 or host 192.0.2.20"

落地机上抓包:

Terminal window
tcpdump -ni any "host 203.0.113.10 and port 53000"

查看 conntrack:

Terminal window
conntrack -L | grep 192.0.2.20

这里也要记住:NAT 转发端口不一定会出现在 ss -lntup 里,因为它不是某个进程监听,而是内核在改包。

和 iptables 的关系#

在这套笔记里,我把 nftables 和 iptables 分开理解:

  • nftables:负责转发 NAT。
  • iptables/ip6tables:负责主机自身的 INPUT/OUTPUT 防火墙。

比如中转机可以用 nftables 做 52000 -> 53000 的转发,同时用 iptables 阻断公网 DNS、邮件端口出站、无关 UDP 入站。这部分我单独整理到了:VPS 安全加固教程

参考文档#

这篇的公开参考只保留 Astro 内部文章和外部网页:

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

IP 中转学习:从 DNAT、SNAT 到 nftables 单跳转发
https://blog.idotcar.top/posts/ip-forwarding-nftables-basics/
作者
老鼠溺水
发布于
2026-05-11
许可协议
CC BY-NC-SA 4.0
随机文章 随机推荐

评论区

Profile Image of the Author
老鼠溺水
事实上,我们每个人都不过是在给自己写信。
分类
标签
站点统计
文章
7
分类
2
标签
9
总字数
29,837
运行时长
0
最后活动
0 天前

目录