แอป WebRTC ของคุณใช้ได้บน Wi-Fi ของคุณ แต่ล้มเหลวกับลูกค้าคนเดียวที่โทรมาจากโรงแรม สื่อแบบ peer-to-peer ต้องมีเส้นทางระหว่างสองฝั่ง เมื่อทั้งคู่อยู่หลัง NAT แบบเข้มงวด หรือไฟร์วอลล์ทิ้ง UDP ก็จะไม่มีเส้นทาง และสายก็แค่ไม่ติด ไม่มีใครเห็นข้อผิดพลาด มีแต่ “กำลังเชื่อมต่อ…” ไม่รู้จบ
เซิร์ฟเวอร์ TURN คือทางสำรองสำหรับสายแบบนั้น ทั้งสองฝั่งส่งสื่อไปที่มัน แล้วมันส่งต่อให้ ถ้าคุณกำลังใส่วิดีโอหรือเสียงลงในแอป รัน Matrix หรือ Jitsi ให้ทีม หรือทำแชตเสียงสำหรับเกม คุณต้องมีสักตัว coturn คือตัวมาตรฐานแบบโอเพนซอร์ส และเบามาก
เลือกแพ็กเกจไหน
TURN ใช้ CPU และหน่วยความจำน้อย สิ่งที่มันใช้คือแบนด์วิดท์ ทุกสตรีมที่ส่งต่อจะผ่านเซิร์ฟเวอร์สองครั้ง ทั้งเข้าและออก ทราฟฟิกบนแพ็กเกจของเราไม่จำกัดที่ 1 Gbit/s คำถามจึงอยู่ที่ว่ามีสายที่ส่งต่อพร้อมกันกี่สาย ไม่ใช่ว่าจะโดนเรียกเก็บเท่าไร
| จำนวนสายที่ส่งต่อพร้อมกัน | แพ็กเกจ |
|---|---|
| ไม่กี่สาย ทีมหนึ่งทีม แอปเล็ก ๆ | Nano-IP: 2 vCPU, 1 GB, 15 GB ($8/เดือน) |
| หลายสิบสาย | Micro-IP: 2 vCPU, 2 GB, 25 GB ($10/เดือน) |
| มากกว่านั้น หรือวาง TURN ข้างเซิร์ฟเวอร์แอป | Small-IP: 4 vCPU, 4 GB, 35 GB ($16/เดือน) |
ต้องเป็นแพ็กเกจที่มี IPv4 ของตัวเอง เพราะ TURN คือการเชื่อมต่อขาเข้าบนหลายพอร์ต และแพ็กเกจ NAT รับจากภายนอกได้แค่ SSH บนแพ็กเกจที่มี IP ของตัวเองของเรา ที่อยู่สาธารณะจะอยู่บนอินเทอร์เฟซของเซิร์ฟเวอร์โดยตรง coturn จึงหาเจอเองโดยไม่ต้องมีบรรทัด external-ip วางเซิร์ฟเวอร์ใกล้ผู้ใช้ของคุณ เยอรมนีหรือฟินแลนด์ ดูได้ว่าว่างหรือไม่ตอนสั่งซื้อ ชำระด้วยคริปโตได้ ไม่ต้องทำ KYC ถ้าเรื่องนี้สำคัญกับคุณ
พอร์ตที่ต้องเปิด: 3478 UDP และ TCP, 49152–65535 UDP สำหรับสื่อที่ส่งต่อ และ 5349 TCP ถ้าคุณเพิ่ม TLS
ติดตั้งและตั้งค่า
ในฐานะ root บน Ubuntu 24.04 หรือ Debian 12:
apt install -y coturn
ip -4 addr # IP สาธารณะของคุณควรอยู่ในรายการของอินเทอร์เฟซ
เขียนไฟล์ตั้งค่าพร้อมความลับที่ใช้ร่วมกันชุดใหม่:
SECRET=$(openssl rand -hex 32)
cat > /etc/turnserver.conf <<EOF
listening-port=3478
fingerprint
use-auth-secret
static-auth-secret=$SECRET
realm=turn.example.com
min-port=49152
max-port=65535
no-cli
no-multicast-peers
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
EOF
echo "$SECRET" # แบ็กเอนด์ของคุณต้องใช้ค่านี้
systemctl restart coturn
ufw allow 22/tcp && ufw allow 3478 && ufw allow 49152:65535/udp && ufw enable
อย่าข้ามบรรทัด denied-peer-ip ถ้าไม่มี ใครก็ตามที่มีข้อมูลล็อกอินถูกต้องจะใช้ relay ของคุณเข้าถึงที่อยู่ส่วนตัวได้ รวมถึงบริการที่อยู่ฝั่งภายในของเซิร์ฟเวอร์เอง เราตรวจแล้ว เมื่อมีบรรทัดเหล่านี้ relay ไปยังที่อยู่ส่วนตัวจะถูกปฏิเสธด้วย “403 Forbidden IP”
ข้อมูลล็อกอินที่หมดอายุได้
เมื่อใช้ use-auth-secret coturn จะรับชื่อผู้ใช้ที่มีเวลาหมดอายุอยู่ในตัว และรหัสผ่านที่เป็น HMAC ของชื่อนั้น แบ็กเอนด์ของคุณเป็นผู้สร้าง ความลับไม่เคยออกจากฝั่งเซิร์ฟเวอร์ ใน Node.js:
const crypto = require("crypto");
// ชื่อผู้ใช้/รหัสผ่าน TURN ชั่วคราว (รูปแบบ TURN REST API ที่ coturn เข้าใจผ่าน use-auth-secret)
function turnCredentials(secret, user, ttlSeconds = 3600) {
const username = `${Math.floor(Date.now() / 1000) + ttlSeconds}:${user}`;
const credential = crypto.createHmac("sha1", secret).update(username).digest("base64");
return { username, credential };
}
console.log(JSON.stringify(turnCredentials(process.env.TURN_SECRET, "alice")));
ในเบราว์เซอร์ ขอคู่ใหม่จากแบ็กเอนด์แล้วส่งให้ RTCPeerConnection:
const { username, credential } = await fetch("/api/turn-credentials").then((r) => r.json());
const pc = new RTCPeerConnection({
iceServers: [
{ urls: "stun:turn.example.com:3478" },
{
urls: ["turn:turn.example.com:3478?transport=udp", "turn:turn.example.com:3478?transport=tcp"],
username,
credential,
},
],
});
ทดสอบ
จากเซิร์ฟเวอร์ ใช้สูตรเดียวกันด้วย openssl (ได้รหัสผ่านตรงกับโค้ด Node.js ทุกตัวอักษร เราเทียบแล้ว):
U="$(( $(date +%s) + 3600 )):test"
P=$(echo -n "$U" | openssl dgst -binary -sha1 -hmac "$SECRET" | base64)
turnutils_uclient -y -n 10 -u "$U" -w "$P" YOUR_SERVER_IP
ตอนท้ายคุณควรเห็น “Total lost packets 0” ถ้ารหัสผ่านผิด ไคลเอนต์จะหยุดที่ “Cannot complete Allocation” นั่นคือเซิร์ฟเวอร์ทำหน้าที่ของมัน
เรายังทดสอบจาก Chromium จริงด้วย เมื่อใช้ iceTransportPolicy: "relay" และข้อมูลล็อกอินใหม่ เบราว์เซอร์ได้ relay candidate จากเซิร์ฟเวอร์ ส่วนรหัสผ่านผิดไม่ได้เลยสักตัว ถ้าอยากทดสอบระบบของคุณแบบเดียวกัน ให้เปิดตัวเลือกนี้ชั่วคราว มันบังคับให้ทุกสายวิ่งผ่าน TURN
TLS ถ้าจำเป็น
บางเครือข่ายบล็อก UDP และจู้จี้กับ TCP ธรรมดา TURN ผ่าน TLS จะทะลุได้มากกว่า ขอใบรับรองสำหรับ turn.example.com (ใช้ certbot ก็ได้) ไฟล์ของมันอ่านได้เฉพาะ root ขณะที่ coturn รันในนามผู้ใช้ turnserver จึงต้องให้ coturn มีสำเนาของตัวเอง:
mkdir -p /etc/coturn
cp /etc/letsencrypt/live/turn.example.com/fullchain.pem /etc/letsencrypt/live/turn.example.com/privkey.pem /etc/coturn/
chown turnserver: /etc/coturn/*.pem && chmod 640 /etc/coturn/privkey.pem
cat >> /etc/turnserver.conf <<'EOF'
tls-listening-port=5349
cert=/etc/coturn/fullchain.pem
pkey=/etc/coturn/privkey.pem
EOF
systemctl restart coturn && ufw allow 5349/tcp
เพิ่ม turns:turn.example.com:5349?transport=tcp ลงในรายการ urls ใบรับรองมีอายุสั้น จึงควรใส่ขั้นตอนคัดลอกไว้ใน deploy hook ของ certbot ไม่อย่างนั้น TLS จะพังเงียบ ๆ ตอนต่ออายุครั้งแรก
ควรรู้
- TURN ไม่ได้ทำให้การเชื่อมต่อแย่ ๆ ดีขึ้น มันเพิ่มอีกหนึ่ง hop จึงควรวางเซิร์ฟเวอร์ใกล้คนที่ต้องใช้
- ถ้าคุณรัน SFU เช่น videobridge ของ Jitsi นั่นเป็นอีกส่วนหนึ่ง TURN ช่วยไคลเอนต์ที่เข้าถึงมันโดยตรงไม่ได้
- โมเดลความปลอดภัยทั้งหมดอยู่ที่ความลับ เก็บให้ห่างจากฟรอนต์เอนด์ และเปลี่ยนทันทีถ้ามันรั่ว
ที่เกี่ยวข้อง
- VPS สำหรับ Jitsi Meet: วิดีโอคอลบนเซิร์ฟเวอร์ของคุณเอง
- โฮสต์ Matrix Synapse เอง: การโทรใน Matrix ก็ต้องใช้เซิร์ฟเวอร์ TURN
- VPS สำหรับเว็บไซต์บน IP ของตัวเอง: แอปที่แจกข้อมูลล็อกอิน
- วิธีตั้งค่าไฟร์วอลล์ ufw
ความคิดเห็น
ยังไม่มีความคิดเห็น เป็นคนแรกสิ