Tự Triển Khai STUN/TURN Server với coturn trên Linux: Giải Quyết Bài Toán NAT Traversal cho WebRTC và VoIP

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

Bối Cảnh & Lý Do: Tại Sao Kết Nối Peer Trực Tiếp Lại Thất Bại Trong Thực Tế

Trên lý thuyết, các ứng dụng giao tiếp thời gian thực như cuộc gọi video WebRTC và VoIP nền SIP đều dựa trên một kiến trúc đơn giản: peer-to-peer (P2P). Người dùng A truyền các gói tin UDP trực tiếp đến Người dùng B. Độ trễ luôn ở mức thấp, tài nguyên tính toán của server gần như không đáng kể, và chi phí băng thông xấp xỉ bằng không.

Môi trường production ngay lập tức phá vỡ thiết kế hoàn hảo này. Hầu hết các thiết bị đều nằm sau router gia đình, hệ thống CGNAT (Carrier-Grade NAT) của mạng di động, hoặc tường lửa doanh nghiệp. Laptop của bạn chỉ nhận biết một địa chỉ nội bộ như 192.168.1.105 hoặc 10.0.0.15. Với mạng internet công cộng, IP nội bộ đó hoàn toàn không thể định tuyến được.

Thách Thức NAT Traversal: STUN Đối Đầu TURN

WebRTC và VoIP sử dụng framework ICE (Interactive Connectivity Establishment – RFC 8445) để tìm ra đường truyền hoạt động được giữa các peer. ICE điều phối hai vai trò server tối quan trọng:

  • STUN (Session Traversal Utilities for NAT): STUN hoạt động giống như một chiếc gương. Client gửi gói tin đến STUN server, server sẽ phản hồi lại kèm IP công khai và port đã ánh xạ của chính client đó (ví dụ: 203.0.113.45:49152). Trong các mô hình Full-Cone, Restricted Cone, hoặc Port-Restricted NAT, hai đầu cuối có thể trao đổi các reflexive candidate này qua bước signaling và thiết lập luồng truyền media P2P trực tiếp. Vì STUN chỉ xử lý giai đoạn bắt tay tìm đường, một máy chủ VPS giá $5/tháng có thể phục vụ hàng triệu truy vấn STUN mà không gặp bất kỳ trở ngại nào.
  • TURN (Traversal Using Relays around NAT): Khoảng 10% đến 15% kết nối thực tế gặp phải Symmetric NAT—mô hình phổ biến trên mạng di động 4G/5G và Wi-Fi doanh nghiệp. Với Symmetric NAT, router gán một external port hoàn toàn mới cho mỗi IP đầu cuối từ xa khác nhau. Kỹ thuật UDP hole-punching trực tiếp hoàn toàn bó tay. Lúc này, TURN đóng vai trò làm proxy chuyển tiếp có xác thực: cả hai peer sẽ định tuyến các gói tin SRTP đã mã hóa trực tiếp qua TURN server của bạn, và server sẽ forward dữ liệu qua lại giữa hai bên.

Các endpoint STUN công cộng miễn phí như stun.l.google.com:19302 của Google đáp ứng tốt nhu cầu thử nghiệm. Tuy nhiên, TURN server công cộng miễn phí đơn giản là không tồn tại. Việc relay video 720p ở mức 1.5 Mbps qua hàng ngàn cuộc gọi sẽ tạo ra chi phí băng thông khổng lồ. Nếu ứng dụng của bạn hướng tới tỷ lệ kết nối cuộc gọi thành công trên 99%, việc tự vận hành một TURN relay chuyên dụng là bắt buộc. Trong hệ sinh thái Linux, coturn luôn là tiêu chuẩn hàng đầu.

Cài Đặt: Thiết Lập coturn trên Ubuntu và Debian

Trước khi khởi tạo instance, hãy đảm bảo bạn đã chuẩn bị hai điều kiện hạ tầng tiên quyết sau:

  • Một địa chỉ IPv4 công khai chuyên dụng. Tốt nhất là bind trực tiếp vào card mạng; nếu VPS nằm sau nhà cung cấp dùng 1:1 NAT như AWS EC2, bạn sẽ cần ánh xạ IP private với Elastic IP public của mình.
  • Một bản ghi DNS A trỏ về máy chủ (ví dụ: turn.yourdomain.com).

1. Cài Đặt coturn và Kích Hoạt Daemon

Cập nhật danh sách gói phần mềm và cài đặt package coturn:

sudo apt update
sudo apt install coturn -y

Các bản phân phối nền Debian thường vô hiệu hóa coturn theo mặc định sau khi cài. Hãy bật cờ khởi động trong file /etc/default/coturn:

sudo sed -i 's/#TURNSERVER_ENABLED=1/TURNSERVER_ENABLED=1/' /etc/default/coturn

2. Tạo Khóa Bí Mật Dùng Chung (Shared Authentication Secret)

Tránh hardcode username và password tĩnh trong môi trường production. Các kiến trúc WebRTC hiện đại áp dụng tiêu chuẩn TURN REST API (RFC 5766 / draft-uberti-behave-turn-rest) để cấp phát thông tin đăng nhập tạm thời. Hãy tạo một chuỗi khóa bí mật mã hóa 32-byte bằng OpenSSL:

openssl rand -hex 32

Hãy lưu lại chuỗi hex 64 ký tự trả về. Cả coturn và dịch vụ xác thực backend của bạn sẽ dùng chung khóa bí mật này.

Cấu Hình: Thắt Chặt Bảo Mật turnserver.conf

File cấu hình mặc định chứa hàng trăm dòng tùy chọn được comment lại. Hãy sao lưu nó để bắt đầu với một cấu hình sạch sẽ:

sudo mv /etc/turnserver.conf /etc/turnserver.conf.bak

Tạo file cấu hình mới tại /etc/turnserver.conf:

sudo nano /etc/turnserver.conf

Dán nội dung cấu hình bên dưới vào. Thay thế YOUR_PUBLIC_IP, turn.yourdomain.com, và YOUR_STATIC_AUTH_SECRET bằng các giá trị thực tế của bạn. Nếu server đặt sau 1:1 NAT (chẳng hạn như AWS EC2 với IP nội bộ là 172.31.10.5), hãy định dạng dòng external-ip thành external-ip=YOUR_PUBLIC_IP/172.31.10.5.

# Các port lắng nghe tiêu chuẩn
listening-port=3478
tls-listening-port=5349

# Ràng buộc IP
listening-ip=0.0.0.0
external-ip=YOUR_PUBLIC_IP

# Domain & Realm
realm=turn.yourdomain.com
server-name=turn.yourdomain.com

# Thông tin xác thực động qua REST API
use-auth-secret
static-auth-secret=YOUR_STATIC_AUTH_SECRET

# Dải port chuyển tiếp (UDP)
min-port=49152
max-port=65535

# Kiểm thực gói tin cho WebRTC ICE
fingerprint

# Thắt chặt bảo mật và chống lạm dụng
no-cli
no-multicast-peers
no-loopback-peers
stale-nonce=600

# Tùy chọn: Chứng chỉ TLS cho TURNS (port 5349)
# cert=/etc/letsencrypt/live/turn.yourdomain.com/fullchain.pem
# pkey=/etc/letsencrypt/live/turn.yourdomain.com/privkey.pem

# Ghi log
log-file=/var/log/turnserver/turnserver.log
simple-log
verbose

Chúng ta chặn các luồng relay loopback và multicast ở đây nhằm ngăn chặn các client bên ngoài thăm dò các máy chủ nội bộ trong VPC thông qua server chuyển tiếp.

3. Thiết Lập Quy Tắc Firewall

Hệ thống TURN relay yêu cầu các port điều khiển tiêu chuẩn và một dải UDP rộng để chuyển tiếp các luồng media. Trên UFW, hãy mở các port bằng các lệnh sau:

# Báo hiệu & khám phá
sudo ufw allow 3478/tcp
sudo ufw allow 3478/udp
sudo ufw allow 5349/tcp
sudo ufw allow 5349/udp

# Dải port cao chuyển tiếp media
sudo ufw allow 49152:65535/udp

# Áp dụng cấu hình
sudo ufw reload

Nếu dùng AWS, Google Cloud, hoặc Azure, bạn cần cập nhật thêm Security Groups hoặc Virtual Network rules để khớp với các port này. Quên mở firewall trên hạ tầng cloud sau khi đã chỉnh UFW là nguyên nhân phổ biến nhất khiến các bài test relay thất bại.

4. Khởi Tạo Thông Tin Xác Thực Tạm Thời trên Backend

Client tuyệt đối không được biết secret master của bạn. Thay vào đó, API của bạn sẽ cấp phát các token ngắn hạn (thường có hiệu lực từ 12 đến 24 giờ). Username kết hợp timestamp hết hạn UNIX với một định danh, còn password là chữ ký HMAC-SHA1 của username đó. Dưới đây là hàm xử lý bằng Python sẵn sàng cho production:

import base64
import hashlib
import hmac
import time

def get_turn_credentials(secret: str, username_prefix: str, ttl_seconds: int = 86400):
    # Tạo timestamp hết hạn (ví dụ: thời gian hiện tại + 24 giờ)
    expiry_time = int(time.time()) + ttl_seconds
    username = f"{expiry_time}:{username_prefix}"
    
    # Tính toán mã băm HMAC-SHA1 bằng khóa bí mật dùng chung
    digest = hmac.new(
        secret.encode('utf-8'),
        username.encode('utf-8'),
        hashlib.sha1
    ).digest()
    
    password = base64.b64encode(digest).decode('utf-8')
    
    return {
        "iceServers": [
            {"urls": "stun:turn.yourdomain.com:3478"},
            {
                "urls": [
                    "turn:turn.yourdomain.com:3478?transport=udp",
                    "turn:turn.yourdomain.com:3478?transport=tcp"
                ],
                "username": username,
                "credential": password
            }
        ]
    }

Kiểm Thử: Xác Minh Hoạt Động Của Server Từ Đầu Đến Cuối

Khởi động lại service systemd của coturn để nạp cấu hình mới:

sudo systemctl restart coturn
sudo systemctl status coturn

1. Kiểm Tra Các Socket Đang Lắng Nghe

Kiểm tra xem daemon đã lắng nghe đúng cổng chưa bằng lệnh ss:

sudo ss -ulpn | grep turnserver

Bạn sẽ thấy turnserver được bind vào 0.0.0.0:3478 và 0.0.0.0:5349.

2. Kiểm Tra Candidates Thông Qua Trickle ICE

Đừng phỏng đoán xem hệ thống đã chạy hay chưa—hãy kiểm tra trực tiếp với WebRTC stack gốc của trình duyệt:

  1. Truy cập vào công cụ kiểm thử WebRTC Trickle ICE chính thức.
  2. Xóa cấu hình STUN server mặc định của Google.
  3. Thêm STUN URI của bạn: stun:turn.yourdomain.com:3478 và nhấn Gather candidates. Một candidate có type là srflx (Server Reflexive) sẽ xuất hiện, hiển thị IP công khai của client bạn đang dùng.
  4. Chạy script Python để tạo một cặp token mới. Thêm TURN URI của bạn: turn:turn.yourdomain.com:3478?transport=udp, cùng với username và password tạm thời vừa tạo.
  5. Nhấn Gather candidates một lần nữa và quan sát danh sách candidate. Trong vòng 2 đến 3 giây, bạn phải thấy một mục có type là relay. Điều này chứng minh coturn đã xác thực thành công chữ ký HMAC và mở cổng truyền media trong dải 49152-65535.

3. Giám Sát Lưu Lượng Relay Đang Hoạt Động

Khi cuộc gọi bị ngắt quãng hoặc mất âm thanh, hãy kiểm tra log của coturn để xem các sự kiện xác thực và binding:

sudo tail -f /var/log/turnserver/turnserver.log

Một phiên cấp phát (allocation) thành công sẽ hiển thị log tương tự như sau:

session 000000000000000001: new, realm <turn.yourdomain.com>, username <1760000000:user1>
session 000000000000000001: IPv4. UDP allocate request received
session 000000000000000001: allocation created, relay addr 198.51.100.12:51234

Nếu gặp lỗi 401 Unauthorized liên tục, hãy kiểm tra xem HMAC secret ở phía backend có khớp từng ký tự với static-auth-secret hay không. Ngoài ra, hãy đảm bảo server có đồng bộ thời gian NTP qua chrony hoặc systemd-timesyncd; sự sai lệch đồng hồ giữa API server và coturn sẽ khiến timestamp bị từ chối trước cả khi cuộc gọi kịp kết nối.

Share: