FTP主动模式NAT穿透原理解析

FTP被动模式NAT穿透

被动模式成功场景流程图

  sequenceDiagram
    participant 客户端 as 客户端(内网)<br>192.168.1.100
    participant NAT as NAT设备<br>1.2.3.4
    participant 服务器 as FTP服务器(外网)<br>203.0.113.50

    Note over 客户端,NAT: 控制连接建立
    客户端->>NAT: TCP SYN (随机端口 -> 21)
    NAT-->>服务器: TCP SYN (SNAT转换)
    服务器-->>NAT: TCP SYN ACK
    NAT-->>客户端: TCP SYN ACK (DNAT转换)

    Note over 客户端,NAT: PASV命令处理
    客户端->>服务器: PASV (请求被动模式)
    服务器->>服务器: 配置检查
    Note right of 服务器: 服务器配置了正确的<br>公网IP地址
    服务器-->>NAT: 227 Entering Passive Mode (203,0,113,50,10,20)
    NAT-->>客户端: 227 Entering Passive Mode (203,0,113,50,10,20)

    Note over 客户端,NAT: 数据连接建立
    客户端->>NAT: TCP SYN (随机端口 -> 2580)
    NAT-->>服务器: TCP SYN (SNAT转换)
    服务器-->>NAT: TCP SYN ACK
    NAT-->>客户端: TCP SYN ACK

    Note over 客户端,服务器: ✅ 数据连接成功建立

FTP主动模式交互流程

无ALG支持时的失败场景

  sequenceDiagram
    participant 客户端 as 客户端(内网)<br>192.168.1.100
    participant NAT as NAT设备<br>1.2.3.4
    participant 服务器 as FTP服务器(外网)<br>203.0.113.50

    客户端->>NAT: (1) 控制连接: TCP SYN (随机端口 -> 21)
    NAT->>服务器: (2) 控制连接建立
    服务器->>NAT: (3) 控制连接响应
    NAT->>客户端: (4) 控制连接响应

    客户端->>NAT: (5) PORT 192,168,1,100,200,10
    NAT->>服务器: (6) PORT 192,168,1,100,200,10

    服务器->>NAT: (7) 数据连接尝试 (20 -> 51210)
    Note over 服务器,NAT: ❌ 失败:公网无法路由到内网私有IP

    客户端->>客户端: (8) 数据连接超时

在此场景下,NAT设备仅执行基本的地址转换,无法识别FTP协议载荷中的IP地址。

有ALG支持时的成功场景

  sequenceDiagram
    participant 客户端 as 客户端(内网)<br>192.168.1.100
    participant NAT as NAT设备<br>1.2.3.4
    participant 服务器 as FTP服务器(外网)<br>203.0.113.50

    客户端->>NAT: (1) 控制连接: TCP SYN (随机端口 -> 21)
    NAT->>服务器: (2) 控制连接建立 (SNAT)
    服务器->>NAT: (3) 控制连接响应
    NAT->>客户端: (4) 控制连接响应 (DNAT)

    客户端->>NAT: (5) PORT 192,168,1,100,200,10
    Note over NAT: 【FTP ALG介入处理】<br>1. 检测PORT命令载荷<br>2. 修改IP: 192.168.1.100 -> 1.2.3.4<br>3. 建立NAT映射(端口51210)

    NAT->>服务器: (6) PORT 1,2,3,4,200,10

    服务器->>NAT: (7) 数据连接 (服务器连1.2.3.4:51210)
    NAT->>客户端: (8) DNAT转发 (1.2.3.4:51210 -> 192.168.1.100:51210)

    客户端->>客户端: (9) 数据连接成功建立

核心机制分析

状态检测防火墙机制

现代NAT设备通常具备状态检测功能。它们不仅检查IP包头,还会跟踪连接的状态:

  • 当控制连接建立后,NAT设备知道这是一个活跃的会话
  • 当数据连接请求进入时,设备检查发现该流量符合已建立的会话规则
  • 因此判定其为合法的关联流量,而不是黑客的随机攻击

Conntrack的"预期"条目

当ALG修改PORT命令时,它不仅修改了数据包内容,还在内核的Conntrack表中插入了一条特殊的记录。

Conntrack记录示例:

1
2
3
tcp      6 431999 ESTABLISHED src=192.168.1.100 dst=203.0.113.50 ... [控制连接主条目]

tcp      6 30 EXPECTED src=203.0.113.50 dst=1.2.3.4 sport=20 dport=51210 [ASSURED] master src=192.168.1.100 dst=203.0.113.50 ...

这条EXPECTED记录明确告诉内核:“我知道服务器马上要从20端口向我的51210端口发起连接,这是合法的,请不要丢弃。”

内核源码实现分析FTP

ALG 的 Hook 位置FTP ALG 的核心代码位于 net/netfilter/nf_conntrack_ftp.c,主要通过 Netfilter 的 Helper 机制动态注册。

ALG的工作需要在数据包刚产生、还未进行NAT转换时进行拦截和修改。因此,其主要挂载点位于NF_IP_LOCAL_OUT

关键 Hook 点

● 主要位置NF_IP_LOCAL_OUT

● 次要位置NF_IP_PRE_ROUTING

时间轴分析:

阶段 Netfilter Hook 主要动作
1 NF_IP_LOCAL_OUT 【ALG介入点】 ALG截获数据包,把Payload里的内网IP改成公网IP,并注册Expectation
2 NF_IP_POST_ROUTING 【SNAT介入点】 NAT表规则生效,把IP头的源地址从192.168.1.100改成1.2.3.4
3 发送出网卡 数据包发往互联网

核心函数分析

注册入口函数: nf_conntrack_ftp_init_net 核心处理函数: help

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 源码片段示意 (简化版)
static struct nf_conntrack_helper nf_conntrack_helper_ftp[] __read_mostly = {
    {
        .name           = "ftp",
        .me             = THIS_MODULE,
        .expect_policy  = &ftp_expect_policy,
        .help           = help, // 核心处理函数
        .tuple.src.l3num = NFPROTO_IPV4,
        .tuple.dst.protonum = IPPROTO_TCP,
    },
};

处理流程

  • 拦截时机:数据包经过 NF_IP_LOCAL_OUT 钩子

  • ALG 动作

    • 解析数据包载荷,识别 PORT 命令
    • 将载荷中的内网 IP 替换为公网 IP
    • 调用 Conntrack 接口,插入 IP_CT_EXPECTED 记录
  • 后续处理:数据包继续流转,在 NF_IP_POST_ROUTING 阶段由 SNAT 规则修改 IP 头部的源地址

硬件加速对 FTP 主动模式的影响

HWNAT/Flow Offloading 的工作原理

现代路由器为了提高转发性能和降低系统负载,通常会支持硬件加速网络地址转换(HWNAT)或流量卸载(Flow Offloading)技术。其核心思想是建立转发表后,让已知数据流的后续数据包直接经由交换芯片(Switch Chip)或网络加速处理器(NPU)转发,不再上送至主 CPU 和 Linux 内核进行处理。

软件 NAT vs 硬件加速 NAT

特性 软件 NAT (Linux Netfilter) 硬件加速 NAT / 流量卸载 (HWNAT/Flow Offloading)
处理层级 操作系统内核态,逐层解析 底层硬件硬件芯片或协处理器直接转发
CPU 占用 较高,CPU负载随吞吐量显著增加 极低,CPU近乎零参与
处理路径 完整穿越网络栈及所有 Netfilter 钩子 (Hook) 绕过协议栈,首包建立连接后后续包硬件直通
灵活性 极高,支持复杂的 ALG、七层检测及深度 QoS 极低,通常仅支持基本五元组匹配及基础的 NAT 修改

为什么硬件加速导致主动模式失败

  • 控制连接建立:客户端连接服务器的 21 端口,可能被硬件加速接管
  • 发送 PORT 命令
    • 正常流程:包应进入 Netfilter 的 LOCAL_OUT 链,被 ALG 捕获
    • HWNAT 流程:包可能直接绕过 Netfilter 的软件钩子
  • 结果
    • ALG 没看到包:因为包没经过 Netfilter,ALG 模块没有机会运行
    • 内容未修改:服务器收到的PORT命令中依然是内网 IP
    • Expectation 未创建:Conntrack 表中没有生成"预期"条目

ALG

FTP SIP PPTP RTSP
📌 总结:为什么这些协议需要 ALG?
这类协议之所以让普通 NAT 路由器“头疼”,是因为它们都具备一个共同特征:控制流与数据流分离,且数据流的连接信息(IP/Port)被放在应用层载荷中

  • 📺 RTSP(实时流协议)
    • 场景:网络摄像头监控、流媒体点播。
    • 过程:客户端通过 554 端口(控制通道)发送 SETUP 指令,告诉服务器:“请通过 UDP 协议,把视频流发到我的 内网IP 和 随机端口(如 5000)”。
    • 问题:普通 NAT 只修改 IP 包头,不修改包体。服务器收到指令后,拿着那个不可达的“内网IP”去发视频流,导致黑屏或花屏。
    • ALG 作用:RTSP ALG 会深度解析 SETUP 指令,把包体里的“内网IP:端口”修改为“公网IP:映射端口”,确保视频流能正确打洞进入内网。
  • 📂 FTP(文件传输协议 - 主动模式)
    • 场景:文件上传下载。
    • 过程:客户端连接服务器 21 端口发指令:“我要下载文件,请连接我的 内网IP 的 2000端口 发送数据”。
    • 问题:服务器尝试连接那个内网 IP,连接超时,文件传输失败。
    • ALG 作用:FTP ALG 拦截 PORT 指令,修改其中的 IP 和端口,并临时在防火墙上打开对应的数据端口。
  • 📞 SIP / H.323(VoIP 网络电话)
    • 场景:IP 电话、视频会议。
    • 过程:信令包(SDP 协议)里写着:“请把语音流(RTP)发到我的 内网IP 的 3000端口”。
    • 问题:对方听到了拨号音,但一旦接通,语音包发往内网 IP,导致单通(一方听不到声音)或静音。
    • ALG 作用:SIP ALG 修改 SDP 包里的 IP 地址,保证语音流能双向互通。
  • 🌐 PPTP / IPSec(老旧 VPN 协议)
    • 场景:远程办公接入公司内网。
    • 过程:在建立隧道握手时,数据包里携带了用于校验或加密的特定参数(如 Call ID 或 SPI)。
    • 问题:普通 NAT 可能会改变这些参数的校验和,或者因为 PPTP 没有端口号(使用 GRE 协议)而导致 NAT 无法区分不同用户的流量,导致VPN 拨号卡死或连上后无法上网。
    • ALG 作用:专门识别 GRE 包或 IPSec 包,维护一张复杂的会话表,确保加密隧道能穿过 NAT。

普通 NAT 就像一个只会看信封的邮递员,它只负责把信封上的“收件人/发件人”(IP头)改掉,不管信里写了什么。 ALG 就像一个会拆信检查的秘书。当它发现信里写着“请联系我旧的电话号码(内网IP)”时,它会把信纸上的号码涂改成“新的电话号码(公网IP)”,然后再把信发出去。


STUN

TUN 和 ALG 虽然都是为了解决 NAT 穿透问题,但两者的实现思路截然不同:ALG 依赖路由器“帮忙修改”,而 STUN 则是客户端“自力更生”。

  • STUN 的全称是 NAT 会话穿透工具。你可以把它想象成一个位于公网的“回声服务器”或“镜子”。
    🤔 核心原理:借一双“公网的眼睛”
    位于内网的设备(比如你的电脑)通常只知道自己的内网 IP(如 192.168.1.100),它完全不知道自己在公网上的“马甲”(公网 IP 和端口)是什么。

    • STUN 的工作流程非常简单,就像照镜子:
      • 询问(Binding Request) 内网客户端向公网的 STUN 服务器发送一个数据包,问:“你看我是谁?”
      • NAT 转换(关键一步) 数据包经过路由器(NAT)时,路由器会给它盖上自己的“公章”:把源 IP 换成公网 IP,源端口换成一个映射端口。
      • 回答(Binding Response) STUN 服务器收到包后,看到的就是路由器盖完章的地址。 它把这个地址写在回信里,发回给客户端:“我看你的地址是 X.X.X.X:Y。”
      • 获知公网地址
  • 客户端收到回信,就知道了:“原来我在公网上的门牌号是 X.X.X.X:Y!”
    🔄 客户端拿到地址后做什么? 客户端一旦知道了自己的公网映射地址,它就会把这个地址通过信令服务器(比如 SIP 服务器或 WebRTC 的信令服务器)告诉对方。

    • 对方拿到这个公网地址后,直接往这个地址发数据。
    • 路由器收到数据,查 NAT 表,转发给内网客户端。
    • 穿透成功!
  • ⚠️ STUN 的致命弱点:对称型 NAT
    STUN 虽然好用,但它搞不定一种叫对称型 NAT的路由器。

  • 锥形 NAT(STUN 能搞定):不管你发给谁(STUN 服务器还是对方电脑),路由器都给你用同一个公网端口。

  • 对称型 NAT(STUN 搞不定):

    • 你发给 STUN 服务器,路由器开了端口 1000。
    • 你发给对方电脑,路由器觉得目标变了,于是开了个新端口 2000。
    • 结果:STUN 告诉你端口是 1000,你告诉对方用 1000。但对方发数据过来时,路由器的 1000 端口根本没开(或者是给 STUN 专用的),导致连接失败。

解决办法:当 STUN 失败(遇到对称型 NAT)时,通常就需要 TURN(中继)登场了——既然直连不通,那就找个公网服务器帮忙转发数据吧。

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计