Skip to content

assumeengagefigureout/zerotier-one-network-hypervisor

Repository files navigation

UDP NAT穿透原型实现

项目概述

这是一个用于学习UDP内网穿透原理的教学项目,实现了简化版的STUN和TURN协议,以及NAT类型探测功能。通过这个项目,你可以深入理解NAT穿透的底层机制。

核心概念

NAT(Network Address Translation)

NAT是一种在IPv4地址短缺背景下广泛使用的技术,它允许多个内网设备共享一个公网IP地址。但NAT也带来了P2P通信的挑战,因为外部无法直接访问NAT后的设备。

NAT类型分类

graph TD
    A[NAT类型] --> B[开放互联网<br/>Open Internet]
    A --> C[完全锥形<br/>Full Cone]
    A --> D[限制锥形<br/>Restricted Cone]  
    A --> E[端口限制锥形<br/>Port Restricted]
    A --> F[对称型<br/>Symmetric]
    
    B --> B1[无NAT<br/>直接P2P]
    C --> C1[最容易穿透<br/>映射固定]
    D --> D1[需要IP白名单<br/>端口任意]
    E --> E1[需要IP:Port白名单<br/>最常见]
    F --> F1[最难穿透<br/>映射动态变化]
Loading

系统架构

graph TB
    subgraph "内网A"
        ClientA[客户端A<br/>192.168.1.100:5000]
        RouterA[NAT路由器A<br/>内: 192.168.1.1<br/>外: 203.0.113.1]
    end
    
    subgraph "公网"
        STUN[STUN服务器<br/>198.51.100.1:3478<br/>198.51.100.1:3479]
        TURN[TURN服务器<br/>198.51.100.1:3480]
        Signal[信令服务器<br/>可选]
    end
    
    subgraph "内网B"
        ClientB[客户端B<br/>10.0.0.50:6000]
        RouterB[NAT路由器B<br/>内: 10.0.0.1<br/>外: 203.0.113.2]
    end
    
    ClientA -.->|1. STUN Binding| STUN
    ClientB -.->|1. STUN Binding| STUN
    
    ClientA -->|2. 获取公网地址| RouterA
    ClientB -->|2. 获取公网地址| RouterB
    
    ClientA -.->|3. 交换地址信息| Signal
    ClientB -.->|3. 交换地址信息| Signal
    
    ClientA ===>|4. UDP打洞| RouterA
    ClientB ===>|4. UDP打洞| RouterB
    
    RouterA <==>|5. P2P连接| RouterB
    
    ClientA -.->|6. TURN中继<br/>如果P2P失败| TURN
    ClientB -.->|6. TURN中继<br/>如果P2P失败| TURN
Loading

NAT类型检测流程

sequenceDiagram
    participant C as 客户端
    participant S1 as STUN主服务器<br/>(IP1:Port1)
    participant S2 as STUN备服务器<br/>(IP2:Port2)
    
    Note over C,S2: Test 1: 基础连通性测试
    C->>S1: BINDING_REQUEST
    S1->>C: BINDING_RESPONSE<br/>(返回客户端公网地址)
    
    alt 本地地址 == 映射地址
        Note over C: 开放互联网(无NAT)
    else 地址不同
        Note over C: 存在NAT,继续测试
    end
    
    Note over C,S2: Test 2: Full Cone测试
    C->>S1: TEST_REQUEST<br/>(change IP & Port)
    S2-->>C: TEST_RESPONSE<br/>(从不同IP和端口响应)
    
    alt 收到响应
        Note over C: Full Cone NAT
    else 未收到响应
        Note over C,S2: Test 3: Restricted Cone测试
        C->>S1: TEST_REQUEST<br/>(change IP only)
        S2-->>C: TEST_RESPONSE<br/>(从不同IP响应)
        
        alt 收到响应
            Note over C: Restricted Cone NAT
        else 未收到响应
            Note over C,S2: Test 4: 对称型测试
            C->>S2: BINDING_REQUEST<br/>(连接备用服务器)
            S2->>C: BINDING_RESPONSE<br/>(返回新映射)
            
            alt 映射端口改变
                Note over C: Symmetric NAT
            else 映射端口不变
                Note over C: Port Restricted NAT
            end
        end
    end
Loading

UDP打洞原理

sequenceDiagram
    participant A as Client A<br/>NAT-A后
    participant NA as NAT-A<br/>203.0.113.1:45678
    participant NB as NAT-B<br/>203.0.113.2:56789
    participant B as Client B<br/>NAT-B后
    participant S as STUN/Signal
    
    Note over A,B: 阶段1: 地址发现
    A->>S: 获取公网地址
    S->>A: 203.0.113.1:45678
    B->>S: 获取公网地址
    S->>B: 203.0.113.2:56789
    
    Note over A,B: 阶段2: 地址交换
    A->>S: 我的地址是 203.0.113.1:45678
    B->>S: 我的地址是 203.0.113.2:56789
    S->>A: B的地址是 203.0.113.2:56789
    S->>B: A的地址是 203.0.113.1:45678
    
    Note over A,B: 阶段3: 打洞
    A->>NA: 发送到 203.0.113.2:56789
    NA->>NB: UDP包(可能被丢弃)
    Note over NA: NAT-A创建映射规则:<br/>允许从203.0.113.2:56789接收
    
    B->>NB: 发送到 203.0.113.1:45678
    NB->>NA: UDP包(可能被丢弃)
    Note over NB: NAT-B创建映射规则:<br/>允许从203.0.113.1:45678接收
    
    Note over A,B: 阶段4: 建立连接
    A->>NA: 再次发送
    NA->>NB: UDP包
    NB->>B: 转发(规则已存在)
    
    B->>NB: 回复
    NB->>NA: UDP包
    NA->>A: 转发(规则已存在)
    
    Note over A,B: P2P连接建立成功!
Loading

代码结构

udp_nat_traversal.cpp
├── 协议定义
│   ├── MessageType         # STUN/TURN消息类型
│   ├── NATType             # NAT类型枚举
│   ├── AttributeType       # 属性类型
│   └── 数据结构            # 消息头、属性等
│
├── STUN服务器 (STUNServer)
│   ├── 双套接字监听        # 主地址 + 备用地址
│   ├── BINDING请求处理     # 返回客户端公网地址
│   └── NAT类型测试支持     # CHANGE-REQUEST处理
│
├── TURN服务器 (TURNServer)
│   ├── 分配管理           # ALLOCATE请求处理
│   ├── 中继转发           # SEND/DATA指示处理
│   └── 权限控制           # 简化的权限管理
│
└── NAT检测客户端 (NATTypeDetector)
    ├── 连通性测试         # 基础STUN绑定
    ├── NAT类型判断        # 4步测试流程
    └── 穿透建议           # 根据类型给出建议

编译和运行

编译

g++ -std=c++11 udp_nat_traversal.cpp -lpthread -o nat_traversal

运行模式

1. 服务器模式

./nat_traversal
选择: 1

启动STUN服务器(端口3478, 3479)和TURN服务器(端口3480)

2. NAT类型检测

./nat_traversal
选择: 2
输入STUN服务器IP和端口

3. P2P客户端(演示)

./nat_traversal
选择: 3

实验步骤

实验1: 本地测试

  1. 在本机启动服务器模式
  2. 开新终端运行NAT检测,连接127.0.0.1:3478
  3. 观察输出的NAT类型(应该是Open Internet)

实验2: 局域网测试

  1. 在一台机器上启动服务器
  2. 在另一台机器运行NAT检测
  3. 观察不同网络环境下的NAT类型

实验3: 真实NAT测试

  1. 在公网VPS上部署服务器
  2. 在家庭/办公网络运行检测
  3. 测试不同运营商的NAT类型

底层原理详解

1. Socket绑定和地址映射

当内网客户端创建UDP socket并发送数据包到公网时:

// 客户端绑定本地地址 192.168.1.100:5000
bind(sock, local_addr);

// 发送到公网服务器
sendto(sock, data, server_addr);

// NAT设备创建映射表项:
// 内网 192.168.1.100:5000 <-> 公网 203.0.113.1:45678

2. NAT映射行为

不同NAT类型的映射行为:

  • Full Cone: 一对一固定映射,任何外部主机都可以通过公网地址访问
  • Restricted Cone: 需要内网先发送过数据包到外部IP
  • Port Restricted: 需要内网先发送过数据包到外部IP:Port
  • Symmetric: 每个目标地址使用不同的映射

3. 打洞时序

成功的打洞需要精确的时序控制:

// 双方几乎同时发送
TimeA: ClientA -> NAT_A -> [创建规则] -> NAT_B (可能被丢弃)
TimeB: ClientB -> NAT_B -> [创建规则] -> NAT_A (可能被丢弃)

// 规则创建后,后续包可以通过
TimeC: ClientA -> NAT_A -> NAT_B -> ClientB ✓
TimeD: ClientB -> NAT_B -> NAT_A -> ClientA ✓

4. TURN中继原理

当P2P打洞失败时,使用TURN服务器中继:

// 客户端A分配中继地址
TURN_Server.allocate() -> RelayAddress_A

// 客户端B分配中继地址  
TURN_Server.allocate() -> RelayAddress_B

// A发送数据到B的中继地址
A -> TURN -> RelayAddress_B -> B

// B发送数据到A的中继地址
B -> TURN -> RelayAddress_A -> A

优化建议

1. 连接保活

// 定期发送心跳包维持NAT映射
while (connected) {
    sendto(sock, "HEARTBEAT", peer_addr);
    sleep(30); // 30秒心跳
}

2. 多路径尝试

  • 同时尝试多个端口
  • 使用端口预测算法
  • 实施快速重试机制

3. 智能降级

尝试P2P -> 失败 -> 尝试打洞 -> 失败 -> 使用TURN中继

实际应用场景

  1. 视频会议: WebRTC使用ICE框架(包含STUN/TURN)
  2. 在线游戏: 低延迟P2P连接
  3. 文件传输: P2P文件共享
  4. IoT设备: 远程访问内网设备
  5. VoIP电话: SIP协议配合STUN/TURN

常见问题

Q: 为什么需要两个STUN服务器地址?

A: 用于完整的NAT类型检测。通过从不同IP和端口发送响应,可以准确判断NAT的过滤行为。

Q: 打洞成功率有多高?

A: 取决于NAT类型组合:

  • Cone to Cone: >90%
  • Cone to Symmetric: ~50%
  • Symmetric to Symmetric: <10%

Q: 如何提高打洞成功率?

A:

  • 使用多个端口同时尝试
  • 实现端口预测算法
  • 优化打洞时序
  • 准备TURN服务器作为后备

Q: IPv6是否需要NAT穿透?

A: 理论上不需要(地址充足),但实际中仍可能存在防火墙需要穿透。

扩展学习

  1. ICE (Interactive Connectivity Establishment): 完整的NAT穿透框架
  2. WebRTC: 浏览器中的P2P通信标准
  3. QUIC协议: Google的基于UDP的传输协议
  4. WireGuard: 现代VPN协议的NAT穿透实现

参考资料

许可证

MIT License - 仅供学习使用

About

educational and somewhat useless but interesting

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published