STUN 服务详解
1. STUN 概述
1.1 什么是 STUN
STUN (Session Traversal Utilities for NAT) 是一种网络协议,用于帮助位于 NAT (Network Address Translation) 后面的设备发现自己的公网 IP 地址和端口。
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ STUN 在 WebRTC 中的作用 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
内网设备 A STUN 服务器 内网设备 B
(192.168.1.100) (公网 IP) (192.168.2.200)
│ │ │
│ 1. 发送 STUN Binding Request │ │
│ 源地址: 192.168.1.100:5000 │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ 2. STUN 服务器看到的是 NAT 映射后的地址 │ │
│ 公网地址: 203.0.113.10:12345 │ │
│ │ │
│ 3. 返回 STUN Binding Response │ │
│ 包含: MAPPED-ADDRESS = 203.0.113.10:12345 │ │
│ <───────────────────────────────────────────────│ │
│ │ │
│ 4. 设备 A 知道了自己的公网地址 │ │
│ 可以通过 ICE 告知对方 │ │
│ │ │
│ │ 5. 设备 B 也通过 STUN 获取公网地址
│ │ │
│ ═════════════════════════════════════════════════════════════════════════════════
│ 通过交换 ICE Candidates,双方尝试建立 P2P 连接 │
│ ═════════════════════════════════════════════════════════════════════════════════
│ │ │
│ <══════════════════════════════ P2P 连接建立 ═══════════════════════════════> │
│ │ │
1.2 为什么需要 STUN
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ NAT 穿透问题 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
问题场景:
┌─────────────────┐ ┌─────────────────┐
│ 内网设备 A │ │ 内网设备 B │
│ 192.168.1.100 │ │ 192.168.2.200 │
└────────┬────────┘ └────────┬────────┘
│ │
│ 设备 A 只知道自己的内网地址 │
│ 设备 B 也只知道自己的内网地址 │
│ │
│ 问题: 双方无法直接通信,因为: │
│ 1. 不知道对方的公网地址 │
│ 2. 即使知道公网地址,NAT 可能阻止入站连接 │
│ │
STUN 解决方案:
┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐
│ 内网设备 A │──────│ STUN 服务器 │──────│ 内网设备 B │
│ 192.168.1.100 │ │ (公网可达) │ │ 192.168.2.200 │
└────────┬────────┘ └─────────────┘ └────────┬────────┘
│ │
│ 1. 通过 STUN 获取公网地址 │
│ 2. 通过信令服务器交换地址信息 │
│ 3. 尝试建立 P2P 连接 │
│ │
2. NAT 类型详解
2.1 NAT 类型分类
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ NAT 类型及其穿透难度 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
1. Full Cone NAT (完全圆锥型) - 最容易穿透
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 内网地址 NAT 映射 外网 │
│ 192.168.1.100:5000 ────────> 203.0.113.10:12345 ────────> 任何外部地址都可以发送数据 │
│ │
│ 特点: 一旦内部地址映射到外部地址,任何外部主机都可以通过该映射发送数据 │
│ 穿透难度: ★☆☆☆☆ (容易) │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
2. Restricted Cone NAT (限制圆锥型)
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 内网地址 NAT 映射 外网 │
│ 192.168.1.100:5000 ────────> 203.0.113.10:12345 ────────> 只有内网主机曾经发送过数据的外部│
│ IP 才能发送数据 │
│ │
│ 特点: 外部主机 IP 必须匹配内部主机曾经发送过的目标 IP │
│ 穿透难度: ★★☆☆☆ (较容易) │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
3. Port Restricted Cone NAT (端口限制圆锥型)
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 内网地址 NAT 映射 外网 │
│ 192.168.1.100:5000 ────────> 203.0.113.10:12345 ────────> 只有内网主机曾经发送过数据的外部│
│ IP:Port 才能发送数据 │
│ │
│ 特点: 外部主机 IP 和端口都必须匹配 │
│ 穿透难度: ★★★☆☆ (中等) │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
4. Symmetric NAT (对称型) - 最难穿透
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 内网地址 NAT 映射 外网 │
│ 192.168.1.100:5000 ────────> 203.0.113.10:12345 ────────> 目标 A (不同目标不同映射) │
│ 192.168.1.100:5000 ────────> 203.0.113.10:12346 ────────> 目标 B │
│ │
│ 特点: 每个目标地址都会创建新的映射,STUN 获取的地址无法用于其他目标 │
│ 穿透难度: ★★★★★ (困难,通常需要 TURN) │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
2.2 NAT 类型检测流程
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ NAT 类型检测 (RFC 3489) │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 STUN 服务器 (IP1:Port1) STUN 服务器 (IP2:Port2)
│ │ │
│ 1. Binding Request (从 IP1:Port1 获取映射) │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ 2. Binding Response │ │
│ MAPPED-ADDRESS = PublicIP:PortA │ │
│ <───────────────────────────────────────────────│ │
│ │ │
│ 3. Binding Request (请求从 IP2:Port2 响应) │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ 4. Binding Response (从 IP2:Port2 返回) │ │
│ <───────────────────────────────────────────────┼───────────────────────────────────────│
│ │ │
│ 5. Binding Request (改变 IP 和 Port) │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ 6. 分析响应判断 NAT 类型: │ │
│ │ │
│ ┌─────────────────────────────────────────────┤ │
│ │ 如果映射地址与本地地址相同 → 无 NAT │ │
│ │ 如果两次映射相同 → Cone NAT │ │
│ │ 如果两次映射不同 → Symmetric NAT │ │
│ │ 如果只有特定 IP 能响应 → Restricted Cone │ │
│ │ 如果只有特定 IP:Port 能响应 → Port Restricted│ │
│ └─────────────────────────────────────────────┘ │
│ │ │
▼ ▼ ▼
3. STUN 协议详解
3.1 STUN 消息格式
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ STUN 消息头格式 (20 字节) │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
┌─────────────────────────────────────────────────────────────────┐
│0 0| STUN Message Type (14 bits) │ Message Length (16) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Magic Cookie (32 bits) │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ │
│ Transaction ID (96 bits) │
│ │
│ │
└─────────────────────────────────────────────────────────────────┘
字段说明:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ STUN Message Type (2 字节): │
│ - 0x0001: Binding Request (绑定请求) │
│ - 0x0101: Binding Response (绑定响应) │
│ - 0x0111: Binding Error Response (绑定错误响应) │
│ │
│ Message Length (2 字节): 消息体的长度(不包括 20 字节的头部) │
│ │
│ Magic Cookie (4 字节): 固定值 0x2112A442,用于标识 STUN 消息 │
│ │
│ Transaction ID (12 字节): 唯一标识符,用于匹配请求和响应 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
3.2 STUN 属性 (Attributes)
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 常用 STUN 属性 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
属性格式:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
┌───────────────────────┬───────────────────────┬───────────────┐
│ Type (16 bits) │ Length (16 bits) │ Value ... │
└───────────────────────┴───────────────────────┴───────────────┘
常用属性类型:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 属性名称 │ 类型值 │ 说明 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ MAPPED-ADDRESS │ 0x0001 │ NAT 映射后的公网地址和端口 │
│ XOR-MAPPED-ADDRESS │ 0x0020 │ XOR 加密后的映射地址(推荐使用) │
│ CHANGE-REQUEST │ 0x0003 │ 请求服务器从不同地址/端口响应 │
│ SOURCE-ADDRESS │ 0x0004 │ 响应消息的源地址 │
│ CHANGED-ADDRESS │ 0x0005 │ 服务器的备用地址 │
│ ERROR-CODE │ 0x0009 │ 错误代码和原因 │
│ SOFTWARE │ 0x8022 │ 软件名称和版本 │
│ FINGERPRINT │ 0x8028 │ 消息指纹,用于验证消息完整性 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
3.3 STUN 交互示例
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ STUN Binding 请求/响应示例 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
Binding Request (客户端 → STUN 服务器):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ STUN Header: │
│ Message Type: 0x0001 (Binding Request) │
│ Message Length: 0x0000 (无属性) │
│ Magic Cookie: 0x2112A442 │
│ Transaction ID: 0xB7E7A701BC34DA99563F1209 (随机生成) │
│ │
│ 实际数据 (十六进制): │
│ 00 01 00 00 // Type + Length │
│ 21 12 A4 42 // Magic Cookie │
│ B7 E7 A7 01 BC 34 DA 99 // Transaction ID (前 8 字节) │
│ 56 3F 12 09 // Transaction ID (后 4 字节) │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
Binding Response (STUN 服务器 → 客户端):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ STUN Header: │
│ Message Type: 0x0101 (Binding Response) │
│ Message Length: 0x0018 (24 字节属性) │
│ Magic Cookie: 0x2112A442 │
│ Transaction ID: 0xB7E7A701BC34DA99563F1209 (与请求相同) │
│ │
│ XOR-MAPPED-ADDRESS 属性: │
│ Type: 0x0020 │
│ Length: 0x0008 │
│ Family: 0x01 (IPv4) │
│ Port: 0x3039 (XOR 后 = 12345) │
│ Address: 0xCB00710A (XOR 后 = 203.0.113.10) │
│ │
│ 解析结果: 公网地址 = 203.0.113.10:12345 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
4. STUN 服务器搭建
4.1 本项目 STUN 服务器实现
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 项目 STUN 服务器架构 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
服务器配置 (config.json):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ { │
│ "stun": { │
│ "name": "stun服务器", │
│ "enabled": true, │
│ "server": "47.106.189.19", // 服务器公网 IP │
│ "port": 3478 // STUN 服务端口 │
│ }, │
│ "turn": { │
│ "name": "turn服务器", │
│ "enabled": true, │
│ "server": "47.106.189.19", // 服务器公网 IP │
│ "port": 3479, // TURN 服务端口 │
│ "username": "videocall", // TURN 认证用户名 │
│ "password": "turn123456" // TURN 认证密码 │
│ } │
│ } │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
服务器代码 (stun_turn.go):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 使用 pion/turn 库实现 STUN/TURN 服务器: │
│ │
│ 1. 解析公网 IP: │
│ publicIP, err := resolvePublicIP(gConfig.Stun.Server) │
│ │
│ 2. 创建 STUN 服务器: │
│ stunListener, _ := net.ListenPacket("udp", "0.0.0.0:3478") │
│ stunServer, _ := turn.NewServer(turn.ServerConfig{ │
│ PacketConnConfigs: []turn.PacketConnConfig{{ │
│ PacketConn: stunListener, │
│ RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ │
│ RelayAddress: net.ParseIP(publicIP), │
│ Address: "0.0.0.0", │
│ }, │
│ }}, │
│ }) │
│ │
│ 3. 创建 TURN 服务器 (带认证): │
│ turnServer, _ := turn.NewServer(turn.ServerConfig{ │
│ Realm: "videocall", │
│ AuthHandler: func(username, realm string, srcAddr net.Addr) ([]byte, bool) { │
│ if username == gConfig.Turn.Username { │
│ return turn.GenerateAuthKey(username, realm, gConfig.Turn.Password), true │
│ } │
│ return nil, false │
│ }, │
│ ... │
│ }) │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
4.2 公网 STUN 服务器列表
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 常用公共 STUN 服务器 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 提供商 │ STUN 服务器地址 │ 端口 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ Google │ stun.l.google.com │ 19302 │
│ Google │ stun1.l.google.com │ 19302 │
│ Google │ stun2.l.google.com │ 19302 │
│ Google │ stun3.l.google.com │ 19302 │
│ Google │ stun4.l.google.com │ 19302 │
│ Twilio │ stun:stun.twilio.com │ 3478 │
│ Nextcloud │ stun.nextcloud.com │ 443 │
│ OpenVPN │ stun.openvpn.net │ 3478 │
│ Vodafone │ stun.vodafone.net │ 3478 │
│ StunProtocol.org │ stun.stunprotocol.org │ 3478 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
使用建议:
1. 生产环境建议搭建自己的 STUN 服务器,避免依赖公共服务
2. 可以配置多个 STUN 服务器作为备份
3. 公共 STUN 服务器可能存在不稳定或被限制的情况
5. ICE 候选者 (ICE Candidates)
5.1 ICE 候选者类型
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ ICE 候选者类型详解 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
1. Host Candidate (主机候选者) - 优先级最高
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 特点: 设备本地的 IP 地址 │
│ 格式: candidate:1 1 UDP 2122260223 192.168.1.100 5000 typ host │
│ │
│ ┌─────────────────┐ │
│ │ 内网设备 A │ Host: 192.168.1.100:5000 │
│ │ 192.168.1.100 │ │
│ └─────────────────┘ │
│ │
│ 适用场景: 同一局域网内的设备可以直接使用 Host Candidate 通信 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
2. Server Reflexive Candidate (srflx) - 通过 STUN 获取
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 特点: NAT 映射后的公网地址,通过 STUN 服务器获取 │
│ 格式: candidate:2 1 UDP 1686052607 203.0.113.10 12345 typ srflx raddr 192.168.1.100 ... │
│ │
│ ┌─────────────────┐ ┌─────────────┐ │
│ │ 内网设备 A │──────│ STUN 服务器 │ │
│ │ 192.168.1.100 │ │ │ │
│ └─────────────────┘ └─────────────┘ │
│ │ │
│ │ NAT 映射 │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 公网地址 │ srflx: 203.0.113.10:12345 │
│ │ 203.0.113.10 │ │
│ └─────────────────┘ │
│ │
│ 适用场景: 不同 NAT 后的设备尝试通过公网地址通信 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
3. Peer Reflexive Candidate (prflx) - 动态发现
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 特点: 在 ICE 连接检查过程中动态发现的地址 │
│ 格式: candidate:3 1 UDP 1686052606 198.51.100.5 54321 typ prflx raddr 203.0.113.10 ... │
│ │
│ 场景: 当 Symmetric NAT 导致 STUN 获取的地址与实际通信地址不同时发现 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
4. Relay Candidate (relay) - 通过 TURN 中继
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 特点: 通过 TURN 服务器中继的地址,优先级最低但成功率最高 │
│ 格式: candidate:4 1 UDP 41885439 198.51.100.10 60000 typ relay raddr 198.51.100.10 ... │
│ │
│ ┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ 内网设备 A │──────│ TURN 服务器 │──────│ 内网设备 B │ │
│ │ 192.168.1.100 │ │ 198.51.100.10│ │ 192.168.2.200 │ │
│ └─────────────────┘ └─────────────┘ └─────────────────┘ │
│ │
│ 适用场景: P2P 连接失败时,通过 TURN 服务器中继所有流量 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
5.2 ICE 候选者优先级
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ ICE 候选者优先级排序 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
优先级从高到低:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 1. Host Candidate (主机候选者) │
│ - 同一局域网内通信 │
│ - 延迟最低,带宽最大 │
│ - 不经过 NAT,最稳定 │
│ │
│ 2. Peer Reflexive Candidate (对等反射候选者) │
│ - ICE 检查过程中动态发现 │
│ - 通常比 srflx 更可靠 │
│ │
│ 3. Server Reflexive Candidate (服务器反射候选者) │
│ - 通过 STUN 获取的公网地址 │
│ - 依赖 NAT 类型 │
│ │
│ 4. Relay Candidate (中继候选者) │
│ - 通过 TURN 服务器中继 │
│ - 延迟最高,消耗服务器带宽 │
│ - 但成功率最高,兼容所有 NAT 类型 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
ICE 连接建立流程:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 1. 收集所有类型的候选者 │
│ 2. 按优先级排序 │
│ 3. 成对检查 (Connectivity Check) │
│ 4. 选择最高优先级的有效候选者对 │
│ 5. 如果所有候选者对都失败,使用 TURN 中继 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
6. STUN 与 TURN 对比
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ STUN 与 TURN 对比 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 特性 │ STUN │ TURN │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 全称 │ Session Traversal │ Traversal Using Relay │
│ │ Utilities for NAT │ around NAT │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 主要功能 │ 获取公网地址 │ 中继媒体数据 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 数据流向 │ 仅信令交互 │ 所有媒体数据经过服务器 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 服务器负载 │ 低 (仅处理少量请求) │ 高 (处理所有媒体流量) │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 带宽消耗 │ 几乎无 │ 大量消耗服务器带宽 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ NAT 穿透成功率 │ 约 86% (无法穿透 Symmetric NAT)│ 100% (所有 NAT 类型) │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 延迟 │ 低 (P2P 直连) │ 较高 (经过服务器中继) │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 需要认证 │ 否 │ 是 (用户名/密码) │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 部署成本 │ 低 │ 高 (需要更多带宽和计算资源) │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 使用场景 │ 首选方案 │ 备选方案 (STUN 失败时) │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
选择建议:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 1. 始终配置 STUN 服务器 │
│ - 成本低,能解决大部分 NAT 穿透问题 │
│ │
│ 2. 配置 TURN 服务器作为备选 │
│ - 确保 100% 连接成功率 │
│ - 对于企业级应用必不可少 │
│ │
│ 3. 配置多个 STUN/TURN 服务器 │
│ - 提高可用性 │
│ - 负载均衡 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
7. 调试与测试
7.1 STUN 服务器测试工具
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ STUN 测试工具 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
1. 使用 stunclient 命令行工具:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # 安装 (Ubuntu/Debian) │
│ sudo apt-get install stuntman-client │
│ │
│ # 测试 STUN 服务器 │
│ stunclient stun.l.google.com 19302 │
│ │
│ # 输出示例: │
│ Local address: 192.168.1.100:5000 │
│ Mapped address: 203.0.113.10:12345 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
2. 使用 turnutils_uclient 测试 TURN:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # 安装 coturn │
│ sudo apt-get install coturn │
│ │
│ # 测试 TURN 服务器 │
│ turnutils_uclient -u videocall -p turn123456 47.106.189.19 -p 3479 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
3. 使用 Python 测试:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ import socket │
│ import struct │
│ │
│ def test_stun(stun_host, stun_port): │
│ # 创建 STUN Binding Request │
│ msg_type = 0x0001 # Binding Request │
│ msg_len = 0x0000 │
│ magic_cookie = 0x2112A442 │
│ transaction_id = b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c' │
│ │
│ request = struct.pack('>HHI12s', msg_type, msg_len, magic_cookie, transaction_id) │
│ │
│ # 发送请求 │
│ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) │
│ sock.sendto(request, (stun_host, stun_port)) │
│ │
│ # 接收响应 │
│ response, addr = sock.recvfrom(1024) │
│ print(f"Received response from {addr}") │
│ │
│ # 解析响应 │
│ resp_type = struct.unpack('>H', response[0:2])[0] │
│ print(f"Response type: {hex(resp_type)}") │
│ │
│ test_stun('stun.l.google.com', 19302) │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
7.2 WebRTC ICE 连接调试
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ WebRTC ICE 调试日志 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
Android 端日志示例:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ // ICE 候选者收集日志 │
│ [webrtc][observer] 收到ICE候选: 0 │
│ [webrtc] ICE Candidate: candidate:1 1 UDP 2122260223 192.168.1.100 5000 typ host │
│ [webrtc] ICE Candidate: candidate:2 1 UDP 1686052607 203.0.113.10 12345 typ srflx │
│ │
│ // ICE 连接状态变化 │
│ [webrtc][observer] ICE连接状态变化: CHECKING │
│ [webrtc][observer] ICE连接状态变化: CONNECTED │
│ [webrtc][observer] ICE收集状态变化: COMPLETE │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
服务器端日志示例:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ // STUN 服务器启动 │
│ STUN server started on 0.0.0.0:3478 (public: 47.106.189.19:3478) │
│ │
│ // TURN 服务器启动 │
│ TURN server started on 0.0.0.0:3479 (public: 47.106.189.19:3479) │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
7.3 常见问题排查
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 常见问题及解决方案 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
问题 1: ICE 连接一直处于 CHECKING 状态
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 可能原因: │
│ - STUN/TURN 服务器不可达 │
│ - 防火墙阻止 UDP 流量 │
│ - NAT 类型不支持 P2P │
│ │
│ 解决方案: │
│ 1. 检查 STUN/TURN 服务器是否正常运行 │
│ 2. 确认防火墙允许 UDP 端口 3478/3479 │
│ 3. 配置 TURN 服务器作为中继 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
问题 2: 只能获取 Host Candidate,无法获取 srflx
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 可能原因: │
│ - STUN 服务器地址配置错误 │
│ - 网络无法访问 STUN 服务器 │
│ │
│ 解决方案: │
│ 1. 检查 STUN 服务器配置是否正确 │
│ 2. 使用 stunclient 测试 STUN 服务器 │
│ 3. 尝试使用其他公共 STUN 服务器 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
问题 3: ICE 连接频繁断开重连
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 可能原因: │
│ - 网络不稳定 │
│ - NAT 映射超时 │
│ │
│ 解决方案: │
│ 1. 启用 ICE Continual Gathering │
│ 2. 增加心跳频率 │
│ 3. 配置 TURN 服务器保持连接 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
问题 4: TURN 认证失败
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 可能原因: │
│ - 用户名或密码错误 │
│ - Realm 配置不匹配 │
│ │
│ 解决方案: │
│ 1. 检查 TURN 用户名和密码配置 │
│ 2. 确认服务器端 Realm 配置 │
│ 3. 使用 turnutils_uclient 测试认证 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
8. 安全考虑
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ STUN/TURN 安全配置 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
1. TURN 认证配置:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ // 服务器端配置 │
│ AuthHandler: func(username, realm string, srcAddr net.Addr) ([]byte, bool) { │
│ // 验证用户名和密码 │
│ if username == gConfig.Turn.Username { │
│ // 生成认证密钥 │
│ key := turn.GenerateAuthKey(username, realm, gConfig.Turn.Password) │
│ return key, true │
│ } │
│ return nil, false │
│ } │
│ │
│ // 客户端配置 │
│ PeerConnection.IceServer.builder("turn:server:port") │
│ .setUsername("videocall") │
│ .setPassword("turn123456") │
│ .createIceServer() │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
2. 网络安全:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ - 使用 DTLS 加密 WebRTC 媒体流 │
│ - 限制 TURN 服务器的带宽使用 │
│ - 配置防火墙规则,只允许必要的端口 │
│ - 定期更新 TURN 认证凭据 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
3. DDoS 防护:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ - 限制单个 IP 的连接数 │
│ - 实现请求频率限制 │
│ - 监控异常流量模式 │
│ - 配置 IP 黑名单 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
9. 性能优化
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ STUN/TURN 性能优化 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
1. ICE 候选者收集优化:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ // 配置持续收集策略 │
│ rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY
│ │
│ // 启用 TCP 候选者 (作为 UDP 的备选) │
│ rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.ENABLED │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
2. 连接建立优化:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ - 预先收集 ICE 候选者 │
│ - 使用 ICE Renomination 提高连接质量 │
│ - 配置多个 STUN/TURN 服务器提高可用性 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
3. 服务器部署优化:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ - STUN/TURN 服务器部署在靠近用户的数据中心 │
│ - 使用 Anycast IP 实现就近接入 │
│ - 配置负载均衡分担流量 │
│ - 监控服务器性能指标 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
10. 参考资料
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 相关 RFC 文档 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
- RFC 5389: STUN - Session Traversal Utilities for NAT
- RFC 5766: TURN - Traversal Using Relays around NAT
- RFC 5245: ICE - Interactive Connectivity Establishment
- RFC 3489: STUN - Simple Traversal of User Datagram Protocol (已废弃,被 RFC 5389 取代)
开源项目:
- pion/turn: Go 语言实现的 STUN/TURN 服务器
- coturn: 功能完整的 TURN/STUN 服务器
- libnice: GLib 实现的 ICE 库