NAT, STUN, TURN, ICE — la cause #1 des "pas d'audio"¶
Si vous ne deviez retenir qu'un chapitre de cette formation, c'est celui-ci. 80% des tickets "ça marche pas en VoIP" sont, à la racine, un problème de NAT mal traversé. Le reste, c'est juste des cas particuliers de ce chapitre.
Le problème : le NAT casse SIP¶
Rappel NAT¶
Votre routeur fait du NAT (Network Address Translation) : votre PC en LAN sur 192.168.1.50:5060 est vu de l'extérieur comme l'IP publique de votre box (ex: 81.250.196.76) sur un port différent (ex: 38117).
Pour HTTP/HTTPS ça ne pose aucun problème : la requête sort, la réponse revient via la même mapping. Le navigateur s'en fiche de "qui je suis", seul le serveur a besoin d'une IP routable.
Pourquoi SIP casse¶
SIP a un défaut de naissance : les messages contiennent l'adresse IP du client à l'intérieur du payload, pas seulement dans l'en-tête IP du paquet. Concrètement :
Contact:dit "renvoie-moi les futurs messages à 192.168.1.50:5060"- Le SDP dit "envoie-moi le RTP à 192.168.1.50:11780"
Le serveur lit ces adresses et les utilise tel-quel. Mais 192.168.1.50 n'existe pas pour lui ! Il va envoyer le RTP dans le vide → pas d'audio.
C'est ça, le problème. Tout le reste (STUN, TURN, ICE) est inventé pour le résoudre.
Solution 1 — Le réécriture côté serveur (NAT helper)¶
La solution la plus ancienne : Asterisk peut être configuré pour ignorer ce que dit le téléphone et utiliser l'IP source réelle du paquet IP.
Côté PJSIP, ça s'appelle :
Ces 3 options disent : "ignore ce qu'il y a dans Contact: et SDP, utilise l'IP+port d'où le paquet arrive vraiment".
Ça marche pour la signalisation (le serveur sait où renvoyer les
messages SIP). Mais pour le RTP, c'est plus subtil : Asterisk doit
attendre le premier paquet RTP du téléphone pour apprendre la mapping
NAT, puis lui répondre sur cette même mapping. C'est rtp_symmetric.
Limite : ça marche uniquement pour le scénario téléphone derrière NAT ↔ serveur sur IP publique. Si les deux côtés sont derrière NAT (deux téléphones derrière des box différentes), aucun n'a d'IP publique, et le RTP direct entre eux est impossible.
Solution 2 — STUN¶
STUN (Session Traversal Utilities for NAT, RFC 5389) est le moyen pour un client de découvrir sa propre IP publique.
Le client envoie un paquet UDP à un serveur STUN public (ex:
stun.l.google.com:19302), le serveur répond "voici l'IP:port d'où ton
paquet est arrivé chez moi" (= l'IP publique du NAT + le port mappé).
Le client peut alors mettre cette IP:port dans son SDP au lieu de 192.168.1.50:11780.
Téléphone A
──── STUN binding request ────► Serveur STUN
◄──── reflexive address ───── (= IP publique de la box A)
→ Téléphone met cette IP:port dans son SDP
→ Téléphone B essaye de lui parler en RTP sur cette IP:port
STUN suffit pour les NAT cone (la plupart des box résidentielles) : le NAT garde la même mapping pour toute destination. L'audio circule direct entre les deux téléphones, sans passer par un serveur média.
STUN ne marche pas pour les NAT symétriques (typiquement les NAT d'opérateur 4G, certains firewalls d'entreprise). Dans un NAT symétrique, la mapping change selon la destination → l'IP:port appris via STUN ne sert à rien pour parler à un autre peer.
Solution 3 — TURN¶
TURN (Traversal Using Relays around NAT, RFC 5766) est le filet de sécurité : un serveur média qui relaie le RTP entre les deux peers.
Téléphone A TURN server Téléphone B
──── RTP ─────────► (relay) ────── RTP ────►
◄──── RTP ──────── (relay) ◄────── RTP ────
Le téléphone A envoie son RTP au TURN server, qui le forward au B (et inverse). Le serveur agit comme un proxy média.
Avantages : - Marche dans tous les scénarios NAT, y compris symétrique côté opérateur 4G. - Authentification (login/password ou tokens HMAC) — pas n'importe qui peut utiliser ton TURN.
Inconvénients : - Coûte de la bande passante au serveur TURN (chaque appel double : upload+download). - Ajoute de la latence (un hop de plus). - Si TURN tombe, plus aucun appel WebRTC ne fonctionne (côté techno du SDK Wazo, l'app était full-relay obligatoire).
Logiciel courant : coturn (open-source, ce qu'on utilisait sur wazo-prod-01).
Solution 4 — ICE — la stratégie qui combine les 3¶
ICE (Interactive Connectivity Establishment, RFC 8445) est l'algorithme qui orchestre tout ça. Plutôt que de choisir d'avance entre direct, STUN ou TURN, ICE essaye les 3 en parallèle et utilise celui qui marche.
Le concept de candidat¶
Chaque endpoint annonce une liste de candidates dans son SDP :
| Type | Description | Coût |
|---|---|---|
| host | "essaye-moi en direct sur mon IP locale" — marche si même LAN | gratuit |
| srflx | "essaye-moi sur mon IP publique reflexive (apprise via STUN)" | gratuit |
| prflx | peer-reflexive, découvert pendant ICE | gratuit |
| relay | "essaye-moi via mon allocation TURN" | facturé en BP TURN |
L'autre côté reçoit la liste et lance des connectivity checks : pour chaque paire local-distant, un STUN binding request + réponse. La première paire qui répond avec succès devient la candidate sélectionnée — c'est par cette paire que le RTP va circuler.
Stratégies ICE¶
iceTransportPolicy: 'all': essaye tous les candidates, choisit le meilleur. Idéal en LAN ou NAT cone. Peut échouer en NAT symétrique si le serveur TURN n'est pas correctement annoncé ou si le timeout est trop court.iceTransportPolicy: 'relay': ne propose que les candidates TURN — force le passage par TURN. Plus lent, mais déterministe. C'est ce qu'on a fini par forcer dans technotrement-phone build 38 et 41 :
Cas Technotrement bug audio inbound : on a observé que
'all'échouait sur l'inbound (audio caller→app KO). Hypothèse retenue : le candidatehostcôté Asterisk pointait vers une IP non joignable depuis le LTE du mobile, et ICE choisissait ce mauvais candidat avant de tester le relay. Forcerrelaycontournait le problème mais introduisait peut-être un autre bug (le coturn co-localisé avec Asterisk = "loopback peer" filtré par défaut).
NAT en environnement cloud — le piège du serveur¶
Tout ce qu'on a vu jusqu'ici concernait le téléphone derrière NAT. Mais quand votre serveur Asterisk est sur un VPS public cloud (IK, OVH, AWS), il a souvent une IP publique qui n'est PAS sur son interface réseau.
Exemple sur Public Cloud Infomaniak :
- Interface enp3s0 : 83.228.225.119/24
- Mais pour certaines configs, le routage public passe par une
Floating IP attachée → l'IP publique vue de l'extérieur peut être
différente de l'IP de l'interface
Asterisk doit savoir cette IP pour la mettre dans son SDP. Sinon il met l'IP de l'interface, qui peut être correcte... ou pas selon la topologie.
Le paramètre clé :
[transport-udp]
type = transport
protocol = udp
bind = 0.0.0.0:5060
external_media_address = 83.228.225.119
external_signaling_address = 83.228.225.119
local_net = 192.168.0.0/16
local_net = 10.0.0.0/8
external_media_address = ce qu'Asterisk met dans le SDP quand il parle
à un peer hors local_net. Pour un peer dans local_net, il
met l'IP de l'interface (utile pour les softphones LAN).
Cas typique non vu :
external_media_addressmal configuré → le serveur annonce une IP non routable au mobile → le mobile envoie son RTP dans le vide → audio unidirectionnel. Symptôme : "j'entends mon correspondant mais lui ne m'entend pas".
Le piège du coturn loopback¶
Un piège qu'on a touché du doigt sur wazo-prod-01 :
Quand coturn et Asterisk tournent sur la même machine, et que le
relay TURN doit envoyer le RTP du client vers Asterisk, on a un
"loopback peer" — coturn doit relayer vers une IP que lui-même héberge.
Par défaut, coturn refuse les peers loopback (no-loopback-peers
implicite) pour des raisons de sécurité (anti-relai abusif).
Solution propre : séparer coturn et Asterisk sur deux instances distinctes. C'est la recommandation à graver pour les futurs déploiements clients qui voudront du WebRTC.
Solution rapide : ajouter dans /etc/turnserver.conf :
Et ne pas activer no-loopback-peers.
Lecture d'un trace ICE¶
Un SDP ICE-enabled ressemble à :
m=audio 49170 RTP/AVP 0
a=ice-ufrag:F7gI
a=ice-pwd:x9cl/YkneCftKwpFEZS37r
a=candidate:1 1 UDP 2130706431 192.168.1.50 49170 typ host
a=candidate:2 1 UDP 1694498815 81.250.196.76 38117 typ srflx raddr 192.168.1.50 rport 49170
a=candidate:3 1 UDP 16777215 turn.technotrement.com 49350 typ relay raddr 81.250.196.76 rport 38117
3 candidates : host (LAN), srflx (NAT mapping vu via STUN), relay (TURN).
Pendant les connectivity checks, on verra des STUN binding requests
échangés. En pcap Wireshark, filtre stun les isole.
Récap décisionnel¶
| Scénario | Solution |
|---|---|
| Tous les téléphones en LAN avec serveur en LAN | Rien (host candidates suffisent) |
| Téléphone IP fixe ou DECT en LAN, serveur cloud | NAT helper (rewrite_contact + rtp_symmetric) côté serveur |
| Softphone mobile sur 4G/WiFi public, serveur cloud | STUN/TURN obligatoire — préférer ICE en 'all' puis fallback 'relay' |
| Multi-utilisateur WebRTC | TURN obligatoire, séparé du serveur média si possible |
À retenir absolument¶
- SIP/RTP est cassé par défaut en présence de NAT, parce que les IPs sont dans le payload, pas (que) dans l'en-tête.
- Sur le serveur,
external_media_address+local_netdoivent être juste, sinon ça ne marche pas. - Sur le client, ICE est l'algo moderne —
relayonly est le filet de sécurité qui marche partout au prix d'une latence accrue. - Coturn et Asterisk ne doivent PAS tourner sur la même IP en prod WebRTC sérieuse.
- Pour debugger : tcpdump RTP sur le serveur + pcap Wireshark côté client. Voir où s'arrêtent les paquets.
Suivant : Sécurité de transport