IoTLabs

Nghiên cứu, Sáng tạo và Thử nghiệm

Series ESP-WebRTC: NAT Traversal Thực Tế — STUN TURN Cho ESP32 Sau Router

ICE thất bại là nguyên nhân phổ biến nhất khiến WebRTC không kết nối được. Bài này giải thích tại sao, và cách giải quyết bằng TURN server tự deploy.

Tại Sao ICE Thất Bại?

Trong bài 01 đã giải thích ICE thử các loại candidate theo thứ tự ưu tiên. Vấn đề nằm ở loại NAT của router.

Các Loại NAT

NAT TypeMô TảP2P Khả Thi?
Full ConeBất kỳ ai biết IP:port đều kết nối được✅ Có
Restricted ConeChỉ IP đã từng liên lạc mới kết nối được✅ Thường được
Port RestrictedChỉ IP:port đã từng liên lạc✅ Thường được
SymmetricMỗi lần gửi ra IP:port khác nhau → STUN vô dụng❌ Không thể P2P

Router nhà dân ở Việt Nam (TP-Link, ASUS) phần lớn là Symmetric NAT sau khi 4G/cáp quang. ISP (Viettel, VNPT, FPT) thêm một lớp NAT nữa (CGNAT) → hai lớp Symmetric NAT → STUN hoàn toàn không hoạt động.

Triệu Chứng

I (xxxxx) webrtc: ICE gathering complete
I (xxxxx) webrtc: ICE checking... (30 giây)
E (xxxxx) webrtc: ICE connection failed

Lỗi này xảy ra khi tất cả direct candidates đều fail và không có TURN server.

📷 [Hình minh hoạ: Diagram: Symmetric NAT — ESP32 gửi ra port 1234, nhưng STUN server thấy port 5678 khác với port 9012 khi gửi đến peer]

STUN: Giải Thích Tại Sao Không Đủ

STUN cho ESP32 biết IP:port public của nó — nhưng với Symmetric NAT, ESP32 gửi packet đến peer bằng một port khác với port đã báo cho STUN. Peer nhận được IP:port từ ICE candidate nhưng gửi lại thì bị NAT drop.

Kết quả: hai bên đều biết IP của nhau nhưng không ai nghe thấy ai → ICE fail.

TURN: Giải Pháp Relay

TURN server đứng ở internet, nhận traffic từ cả hai phía và relay qua:

ESP32 → TURN Relay ← Browser
ESP32 ← TURN Relay → Browser

Không cần P2P — traffic đi qua server. Nhược điểm: latency tăng thêm RTT đến TURN server. Ưu điểm: kết nối được 100%.

Deploy coturn (TURN Server Mã Nguồn Mở)

coturn là TURN/STUN server phổ biến nhất, chạy tốt trên VPS giá rẻ (5 USD/tháng đủ dùng).

Cài Trên Ubuntu 22.04

sudo apt update
sudo apt install -y coturn

# Cấu hình
sudo nano /etc/turnserver.conf

Nội dung /etc/turnserver.conf:

# Listening
listening-port=3478
tls-listening-port=5349

# IP public của VPS
external-ip=VPS_PUBLIC_IP

# Auth
lt-cred-mech
user=esp32:password123    # Đổi thành user/pass thực

# Realm
realm=iotlabs.vn

# Log
log-file=/var/log/coturn/turnserver.log
verbose
# Bật service
sudo systemctl enable coturn
sudo systemctl start coturn

# Kiểm tra
sudo systemctl status coturn

Mở Firewall

# UDP ports cho TURN relay
sudo ufw allow 3478/udp
sudo ufw allow 3478/tcp
sudo ufw allow 5349/tcp
sudo ufw allow 49152:65535/udp   # TURN relay ports

Test TURN Server

Test bằng WebRTC Trickle ICE tool (trang web public) hoặc bằng turnutils_uclient:

turnutils_uclient -u esp32 -w password123 -p 3478 VPS_PUBLIC_IP
# Kết quả thành công: "Allocated relay: ..."

Cấu Hình TURN Trong esp-webrtc

esp_webrtc_ice_server_t ice_servers[] = {
    {
        // STUN server
        .url = "stun:stun.l.google.com:19302",
    },
    {
        // TURN server
        .url        = "turn:VPS_PUBLIC_IP:3478",
        .username   = "esp32",
        .credential = "password123",
    },
};

esp_webrtc_cfg_t cfg = {
    .ice_servers     = ice_servers,
    .ice_server_num  = 2,
    // ...
};

Ưu Tiên ICE Candidates

WebRTC tự động ưu tiên P2P trước relay. Chỉ dùng TURN khi P2P thất bại. Thứ tự thử:

1. Host candidates (IP private local) — 0ms overhead
2. Server reflexive (qua STUN) — vài ms overhead
3. Relay (qua TURN) — 20-100ms overhead tùy vị trí VPS

Bạn không cần can thiệp — ICE agent trong esp-webrtc tự xử lý.

TURN Server Nên Đặt Ở Đâu?

Để latency thấp nhất, TURN server nên đặt gần ESP32 và browser:

Trường HợpĐề Xuất
ESP32 + User cùng Việt NamVPS Singapore (DigitalOcean, Vultr)
User khắp nơiVPS Singapore + Frankfurt
Test localChạy coturn trên máy tính trong cùng mạng LAN

Latency từ Việt Nam đến Singapore ~20–40ms — với TURN relay thêm 2×RTT = 40–80ms cho mỗi packet. Acceptable cho video call.

Security: Không Dùng Anonymous TURN

Không cấu hình TURN cho phép anonymous access — ai cũng dùng relay của bạn được, tốn bandwidth:

# SAI — không làm
no-auth

# ĐÚNG — bắt buộc auth
lt-cred-mech
user=USERNAME:PASSWORD

Dùng time-limited credentials cho production (coturn hỗ trợ HMAC-based temporary credentials).

Bài tiếp theo: Dự Án Video Doorbell — Chuông Cửa Video Với ESP32-P4