Ketika Video Call Carbonio Tidak Mau Nyambung Cerita Panjang Soal NAT dan UDP yang Ngambek

ICE failed.

Dua kata itu yang muncul di browser console dan langsung bikin saya duduk lebih tegak. Video call di Carbonio tidak mau nyambung. Layar loading berputar, lalu berhenti, lalu muncul pesan yang tidak ingin kalian lihat waktu sedang mencoba setup video call di server production.

Ini cerita tentang beberapa jam saya bergulat dengan WebRTC, NAT, dan iptables yang tidak mau bekerja sama.

Kenali Dulu Topologinya

Sebelum masuk ke masalahnya, penting untuk paham dulu setup yang saya hadapi. Ada dua layer di sini. Di depan ada VPS publik yang bertugas sebagai reverse proxy sekaligus NAT gateway. Di belakangnya ada server Carbonio yang tinggal di jaringan internal dengan IP 192.168.10.201.

Semua traffic HTTPS masuk dari browser ke VPS publik, lalu di-forward ke Carbonio internal. Untuk keperluan web biasa ini berjalan normal. Masalah mulai muncul begitu saya coba fitur video call.

Kesalahan Pertama: Salah Curiga ke Coturn

Reaksi pertama saya waktu WebRTC gagal adalah langsung curiga ke STUN atau TURN server. Itu logika yang wajar karena memang biasanya di situlah masalah ICE negotiation berasal. Saya mulai memeriksa konfigurasi coturn, mencari apakah servicenya berjalan, apakah portnya terbuka.

Tapi kemudian saya sadar sesuatu. Carbonio tidak menggunakan coturn.

Carbonio punya Janus WebRTC Gateway sendiri, berjalan di /opt/zextras/common/bin/janus dan dikelola oleh service bernama carbonio-videoserver. Ini yang handle semua keperluan video call, bukan coturn. Saya sudah buang waktu memeriksa service yang bahkan tidak relevan.

Cek Konfigurasi Janus

Saya buka file konfigurasi Janus di /opt/zextras/common/etc/janus/janus.jcfg dan menemukan ini sudah terkonfigurasi dengan benar:

nat_1_1_mapping = "ip-publik"
rtp_port_range = "20000-40000"

Setting nat_1_1_mapping ini yang memberitahu Janus untuk mengiklankan IP publik VPS sebagai ICE candidate ke browser. Artinya waktu browser mencoba melakukan ICE negotiation, ia akan diarahkan ke ip publik di port range 20000 sampai 40000 via UDP.

Konfigurasinya sudah benar. Janus sudah tahu harus bilang apa ke browser. Masalahnya ada di lapisan yang berbeda.

Root Cause yang Sebenarnya

Saya cek log Janus:

journalctl -f -u carbonio-videoserver

Dan di sana terlihat jelas:

[WARN] ICE failed for component 1 in stream 1, but let's give it some time...
(trickle pending, answer pending, alert not set)

ICE negotiation gagal bukan karena Janus salah konfigurasi. Tapi karena traffic UDP dari browser yang harusnya masuk ke VPS publik di port 20000-40000 tidak pernah sampai ke Carbonio internal. VPS publik menerimanya, tapi tidak ada rule yang meneruskannya ke belakang.

Janus sudah bilang ke browser: “hubungi saya di ip publik port sekian via UDP.” Browser menurut. Packet UDP-nya datang ke VPS. Tapi VPS hanya diam. Tidak ada DNAT. Tidak ada forwarding. Packet itu tidak tahu harus ke mana.

Memperbaikinya Satu per Satu

Solusinya ada beberapa langkah yang harus dikerjakan berurutan karena masing-masing saling bergantung.

Pertama, pastikan INPUT chain di VPS sudah menerima UDP di port range 20000 sampai 40000. Ini biasanya sudah ada kalau sebelumnya pernah dikonfigurasi, tapi perlu dikonfirmasi dulu.

Kedua, tambahkan FORWARD rule agar VPS mau meneruskan traffic UDP itu ke Carbonio internal:

iptables -A FORWARD -p udp -d 192.168.10.201 --dport 20000:40000 -j ACCEPT

Ketiga, tambahkan DNAT di PREROUTING agar packet yang masuk ke VPS langsung diarahkan ke Carbonio:

iptables -t nat -A PREROUTING -p udp --dport 20000:40000 -j DNAT --to-destination 192.168.10.201

Keempat, dan ini yang sering terlewat, tambahkan MASQUERADE di POSTROUTING. Tanpa ini, reply dari Carbonio internal tidak bisa balik ke client karena source IP-nya adalah IP internal yang tidak dikenal di internet:

iptables -t nat -A POSTROUTING -p udp -d 192.168.10.201 --dport 20000:40000 -j MASQUERADE

Kelima, aktifkan IP forwarding di kernel VPS. Tanpa ini semua rule di atas tidak akan berfungsi karena kernel memang tidak mengizinkan packet forwarding secara default:

echo 1 > /proc/sys/net/ipv4/ip_forward
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p

Setelah semua rule terpasang, save iptables dan restart Janus di Carbonio:

iptables-save > /etc/webmin/firewall/iptables.save
systemctl restart carbonio-videoserver

Verifikasi Apakah Sudah Benar

Saya tidak langsung percaya begitu saja. Ada beberapa cara cek yang saya lakukan untuk memastikan traffic mengalir dengan benar.

Cek rule yang aktif:

iptables -L FORWARD -n | grep 192.168.10.201
iptables -t nat -L PREROUTING -n | grep 20000
iptables -t nat -L POSTROUTING -n | grep 20000
sysctl net.ipv4.ip_forward

Dan yang paling informatif, monitor traffic UDP secara real-time waktu mencoba video call:

tcpdump -i any udp portrange 20000-40000 -n

Kalau setup sudah benar, kalian akan melihat tiga pattern sekaligus. Pertama, packet masuk dari IP client ke VPS publik. Kedua, packet yang sama di-forward dari VPS ke Carbonio internal. Ketiga, reply dari Carbonio balik ke client via MASQUERADE. Kalau salah satu dari tiga itu tidak muncul, berarti ada yang masih kurang.

Satu Hal yang Hampir Bikin Saya Gagal Lagi

Ada detail kecil yang cukup krusial soal urutan rule iptables. Rule Accept harus berada sebelum rule Drop. Kalau urutan terbalik, traffic akan di-drop sebelum sempat di-accept meskipun rule Accept-nya ada.

Saya hampir terjebak di sini karena semua rule sudah terpasang tapi video call masih gagal. Setelah dicek, ternyata rule Accept untuk UDP 20000-40000 berada di bawah rule Drop yang lebih general. Begitu urutannya diperbaiki, semuanya langsung berjalan.

Pelajaran dari Semua Ini

WebRTC di balik NAT memang tidak pernah semudah yang terlihat. Ada banyak lapisan yang harus selaras: konfigurasi Janus, iptables di VPS, IP forwarding di kernel, dan NAT yang harus jalan dua arah.

Yang paling penting dari pengalaman ini: kalau ICE gagal, jangan langsung salahkan STUN atau TURN server. Mulai dari yang paling dasar. Apakah traffic UDP-nya bahkan sampai ke tempat yang benar? Cek dengan tcpdump sebelum terlalu jauh menggali konfigurasi yang mungkin sudah benar dari awal.

Kadang masalahnya bukan di aplikasi. Tapi di jaringan yang tidak membiarkan packet-nya lewat.

Leave a Reply

Your email address will not be published. Required fields are marked *