IP 中转学习:从 DNAT、SNAT 到 nftables 单跳转发
这篇是 IP 中转的基础篇。它不讨论 PO0 -> RFC -> 多落地那种复杂实践,只先把单跳中转讲清楚:客户端访问中转机,中转机通过 nftables 把流量转到落地机,并用 SNAT/masquerade 保证回包路径正确。文中 IP 使用文档保留地址,端口也都替换成示例值。
IP 中转到底在转什么
最小模型是这样:
客户端 -> 中转机:52000 -> 落地机:53000客户端只知道中转机地址。中转机收到访问 52000 端口的包后,把目标地址和端口改成落地机的 53000。这一步叫 DNAT。
但只做 DNAT 还不够。落地机收到包后,如果直接回客户端,客户端看到的来源就不是它最初连接的中转机,连接状态会乱掉。所以中转机还要在包离开自己时做 SNAT 或 masquerade,让落地机看到的来源是中转机。这样回包会先回到中转机,再由中转机交还给客户端。
所以一条完整的单跳中转至少有三段逻辑:
| 阶段 | 作用 |
|---|---|
prerouting | DNAT:把入口端口改到落地机地址和端口 |
forward | 放行这条被转发的流量 |
postrouting | SNAT/masquerade:保证回包回到中转机 |
示例拓扑
下面统一使用脱敏示例。
| 角色 | 示例值 |
|---|---|
| 中转机公网地址 | 203.0.113.10 |
| 中转机入口端口 | 52000 |
| 落地机地址 | 192.0.2.20 |
| 落地机服务端口 | 53000 |
| 公网入口网卡 | eth0 |
客户端填写:
server = 203.0.113.10port = 52000实际到达服务端:
203.0.113.10:52000 -> 192.0.2.20:53000开启内核转发
Linux 默认不一定允许转发包,所以先打开 IPv4 forward:
apt updateapt install -y nftables conntrack tcpdumpsystemctl enable --now nftables
cat <<'EOF' > /etc/sysctl.d/99-forwarding.confnet.ipv4.ip_forward = 1net.ipv4.tcp_mtu_probing = 1net.netfilter.nf_conntrack_max = 65536EOF
sysctl --systemsysctl net.ipv4.ip_forward预期:
net.ipv4.ip_forward = 1nftables 单跳模板
这是最基础的单跳 TCP + UDP 转发模板:
#!/usr/sbin/nft -f
define WAN_IF = "eth0"define LANDING_IP = 192.0.2.20define FRONT_PORT = 52000define 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 }}加载前先检查语法:
nft -c -f /etc/nftables.confnft -f /etc/nftables.confnft list table ip one_hop_forward如果系统不支持 destroy table,不要把 2>/dev/null || true 写进 nft 配置文件。那是 shell 语法,只能写在 shell 里:
nft delete table ip one_hop_forward 2>/dev/null || truenft -c -f /etc/nftables.confnft -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.21define LAND1_FRONT_PORT = 52001define LAND1_BACKEND_PORT = 53001
define LAND2_IP = 192.0.2.22define LAND2_FRONT_PORT = 52002define LAND2_BACKEND_PORT = 53002
define LAND3_IP = 192.0.2.23define LAND3_FRONT_PORT = 52003define 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,然后只显式接受这几条目标地址和端口。
原因很简单:中转机上可能不止这一条规则。显式写出允许的目标,比“所有转发都放行”更容易维护,也更容易排障。
验证方式
从外部机器测入口端口:
nc -vz 203.0.113.10 52000中转机上抓包:
tcpdump -ni any "port 52000 or host 192.0.2.20"落地机上抓包:
tcpdump -ni any "host 203.0.113.10 and port 53000"查看 conntrack:
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 内部文章和外部网页:
- [[PO0 到 RFC 再到多落地:一次 nftables 转发链路学习记录]]
- VPS 安全加固教程
- nftables 端口转发教程
- NodeSeek 相关讨论
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!