Résumé : les logiciels de téléphonie indiquent souvent une mauvaise adresse IP à leur interlocuteur, surtout quand ils sont utilisés avec un VPN partiel / split tunnel / VPN sans route par défaut. Les VPN peuvent bricoler les paquets SIP contre la volonté de l'administrateur systèmes et réseaux. La fonctionnalité d'apprentissage RTP du commutateur téléphonique Asterisk est plutôt chatouilleuse aux interférences. Certaines versions de logiciels de téléphonie résistent plus ou moins bien aux magouilles d'un VPN. Pour toutes ces raisons, si tu dois fournir de la téléphonie d'entreprise à des personnels en télétravail et que ça foire de manière aléatoire, ajoute les lignes nat=force_rport,comedia
et directmedia=no
dans la définition de chaque ligne SIP dans le fichier sip.conf
d'Asterisk ou dans les paramètres (paramètres avancés pour « directmedia ») d'une ligne SIP dans XiVo. La première réécrit l'adresse IP contenue dans le paquet SIP/SDP avec l'adresse IP source de ce paquet, garantissant ainsi que l'IP est la bonne. La deuxième fait circuler les flux audio via le commutateur téléphonique plutôt qu'en pair-à-pair, ce qui évite les ratés de l'apprentissage RTP, les bidouilles d'un VPN et la sensibilité des softphones.
Rappel sur le fonctionnement de la téléphonie sur Internet. D'un côté, il y a la signalisation (je veux appeler untel, il a décroché, il met en attente, il reprend l'appel, etc.) avec le protocole SIP. Cet échange se fait via un commutateur téléphonique. La voix transite via le protocole RTP (et son protocole de contrôle RTCP). Il y a un flux audio par interlocuteur. Les flux audio sont échangés en pair-à-pair, sans passer par le commutateur téléphonique. L'adresse IP, le port UDP ("envoie ton flux RTP ici") et les codecs à utiliser sont négociés via le protocole SDP lui-même relayé par SIP, donc via le commutateur.
J'ai déjà exposé notre architecture de téléphonie. Notre commutateur téléphonique est donc un Asterisk empaqueté dans produit avec interface web, API, etc. nommé XiVo. On dira qu'il a l'adresse IP 192.0.2.1 avec une seule route par défaut vers le routeur global de notre organisation. Nous avons des téléphones IP de la marque SNOM. Ils sont dans le réseau 198.51.100.0/24. Il y a donc du routage / du niveau 3 entre nos téléphones et le commutateur téléphonique. Nous avons également quelques rares softphones / logiciels de téléphonie Linphone (choisi car un des seuls logiciels libres encore maintenus + interface agréable depuis la version 4.X). On utilise ça depuis des années sans problème.
Depuis environ six mois, nous avons quelques logiciels de téléphonie utilisés depuis l'extérieur (mise en place progressive du télétravail) à travers un VPN, car notre XiVo n'est pas joignable depuis l'extérieur (filtrage volontaire). Il s'agit d'un VPN partiel / split tunnel : il ne donne pas accès à Internet, seulement aux réseaux internes de l'organisation. Il n'installe donc pas une route par défaut sur le client, mais une route pour chacun de nos réseaux internes. Tous les clients sont dans un même réseau, disons 203.0.113.0/24. Notre VPN ne fait ni filtrage ni NAT. Donc, le commutateur téléphonique voit l'IP VPN d'un client. Exemple : « Registered SIP 'bczhcigi' at 203.0.113.42:62633 ». 203.0.113.42 est bien l'IP que le client VPN a sur son interface réseau VPN. Nos clients VPN peuvent se parler entre eux : un netcat
TCP et UDP entre deux clients VPN fonctionne. Notre VPN est un Cisco ASA. Ces personnels en télétravail ne nous ont pas signalé de problème.
Avec le Covid-19, il a fallu généraliser le télétravail, donc fournir une ligne de téléphone et un logiciel de téléphonie à chaque personnel. Et là ça ne fonctionne plus. Enfin, si, mais pas pour tout le monde ni tout le temps ! On est donc sur un problème aléatoire, les plus chiants à diagnostiquer…
Entre un poste téléphonique SNOM du bureau (ou un numéro externe 0[1-9]) et un Linphone à domicile, l'appel a lieu mais la personne à domicile entend rien. Entre deux Linphones, chacun dans un domicile différent, personne s'entend.
Ça, c'est le signe classique d'un filtrage ou d'un NAT. Mais, comme écrit ci-dessus, notre VPN ne NAT pas et il ne filtre pas. Il n'y a pas d'autres filtrages en interne. Il n'y a pas de filtrage aux extrémités puisqu'on a désactivé les pare-feux.
Si l'on effectue une capture réseau avec tcpdump
(sur le commutateur téléphonique) et wireshark
(sur l'ordinateur de télétravail), on constate que, dans la négociation SDP, Linphone ne communique pas l'adresse IP de l'interface réseau VPN (vpn0, disons), mais celle de l'interface eth0 (ou wlan0), c'est-à-dire celle sur le réseau local du domicile du personnel (genre 192.168.0.X). Forcément, l'interlocuteur ne peut pas émettre un flux audio vers cette destination.
Parfois, Linphone communique la """"bonne"""" adresse IP, celle de l'interface réseau VPN, et dans ce cas-là, tout fonctionne. Cela peut, en partie, expliquer pourquoi nos personnels en télétravail depuis six mois ont rien signalé.
On pourrait penser qu'il suffit de lancer Linphone après avoir établi le VPN, mais non, car, parfois, Linphone échoue même quand il a été démarré après l'établissement du VPN. De plus, on ne peut pas attendre de nos personnels qu'ils se préoccupent de lancer tel logiciel après tel autre.
On peut configurer Linphone pour forcer la communication de l'adresse IP de l'interface réseau VPN. C'est l'option « nat gateway ». Les appels fonctionnent alors dans 100 % des cas. Mais cette option a disparu dans la version 4.X, celle que l'on utilise (parce que son interface est agréable). De toute façon, tous les logiciels qui permettent de configurer l'adresse IP (microSIP, Linphone, etc.) ne conviennent pas : sur notre VPN, nous distribuons des IPs de manière dynamique : à chaque connexion, l'adresse IP du client VPN change. On ne peut pas demander à nos personnels de configurer leur téléphone à chaque fois.
On notera que, dans le cadre de webRTC, les navigateurs web sont mieux équipés que les logiciels de téléphonie ! Dans la config' avancée de Firefox, la clé « media.peerconnection.ice.force_interface » permet de sélectionner l'interface à utiliser, par exemple. Donc ça fonctionnerait impec avec les IP dynamiques de notre VPN. Ça a aussi ses inconvénients : quelqu'un qui voudrait faire de la téléphonie professionnelle (supposons avec VPN) et personnelle (sans VPN) devrait en changer la valeur en permanence…
On peut utiliser ICE, une méthode qui consiste à échanger tous les couples IP+port possibles lors de la négociation SDP et à les tester un par un. J'ai activé ICE dans Linphone et sur le commutateur (même si ça a aucun sens puisqu'il a une seule adresse IP) : ça ne fonctionne pas. D'après mes connaissances, c'est très curieux, mais c'est ainsi.
On peut utiliser STUN, un serveur qui se contente de répondre "voici l'adresse IP source du paquet IP avec lequel tu viens de me causer". On ne peut pas utiliser l'un des serveurs STUN public, car il retournerait l'adresse IP publique de la box Internet du personnel. En revanche, si l'on installe un serveur STUN en interne, la communication avec ce serveur sera contrainte, par le routage, de passer par le VPN. Donc l'adresse IP retournée sera toujours celle de l'interface réseau VPN.
Je ne veux pas trop toucher à notre commutateur téléphonique afin de rien casser (surtout qu'avec le confinement, on ne reviendra pas au bureau avant longtemps, donc on ne saura pas que l'on a cassé l'existant). De plus, nous avons un contrat d'assistance (support) et de maintenance pour notre commutateur et on n'a pas envie de l'invalider avec des modifications trop conséquentes. Pour installer un serveur STUN, j'ai utilisé le logiciel coturn
. Avec Debian GNU/Linux, un simple sudo apt-get install coturn ; sudo systemctl start coturn
suffit.
STUN fonctionne parfaitement. Mais il y a des limites.
Une meilleure solution est de demander au commutateur téléphonique, Asterisk, de réécrire l'adresse IP contenue dans les messages SDP avec l'adresse IP source des paquets IP (on est sûr qu'il s'agit de celle de l'interface réseau VPN car elle a été choisie par le système d'exploitation en fonction de l'interface de sortie).
Avec Asterisk, cela se fait en ajoutant une ligne nat=force_rport,comedia
pour chaque ligne téléphonique de télétravail dans le fichier sip.conf
. Dans XiVo, cela se fait dans l'onglet « Général » de chaque ligne de télétravail (note : XiVo recharge automatiquement la configuration d'Asterisk quand on modifie une ligne ;) ). On peut aussi appliquer cette option sur l'ensemble du parc, mais je ne le fais pas pour les raisons sus-mentionnées : ne pas casser l'existant et ne pas invalider notre contrat d'assistance / maintenance.
Des personnels continuent de ne pas entendre leur interlocuteur. wireshark
/ tcpdump
montre des messages SDP parfaitement valables qui contiennent la """"bonne IP"""" de l'interlocuteur (celle sur l'interface réseau VPN, donc). Si l'on regarde, il y a le début de l'appel (SIP INVITE, SIP TRYING, SIP RINGING, SIP ACK, etc.) puis un premier message SDP est émit par le commutateur téléphonique contenant son adresse IP, donc Linphone émet un flux audio RTP à destination du commutateur puis il reçoit le SDP émis par l'interlocuteur (et réécrite par le commutateur, voir chapitre précédent). Il cesse alors d'émettre le flux RTP. Pourquoi Linphone ne respecte-t-il pas la dernière négociation SDP ?
Je précise que changer la destination des flux RTP en cours de route est parfaitement normal. C'est comme ça que fonctionne la mise en attente ou le fait de se rendre muet : en renégociant en SDP.
Activons le journal de Linphone. Menu -> « Préférences » -> « Avancé » -> « Logs activés ». Le dossier qui contiendra le journal peut également être consulté / changé.
Lisons ce journal. Linphone reçoit bien le dernier SDP, l'analyse et le consigne : « Change audio stream destination: RTP=203.0.113.1:7078 RTCP=203.0.113.1:7079 ». L'adresse IP est la """"bonne"""". Pourquoi Linphone l'ignore-t-il ? Je n'aurai pas la réponse.
Dans le journal du commutateur, Asterisk, je remarque qu'à chaque fois que le problème constaté côté Linphone survient, l'apprentissage RTP ne va pas à son terme. L'apprentissage RTP ou RTP learning ou RTP autolearning est une tambouille interne d'Asterisk qui fait un peu de la magie dans le but d'identifier la """"bonne"""" IP à utiliser.
Quand tout fonctionne bien, le journal consigne une ligne « Strict RTP learning after remote address set to 203.0.113.1:7078 » pour chaque IP de chaque interlocuteur puis une unique ligne « Strict RTP learning complete - Locking on source address 203.0.113.1:7078 » pour chaque interlocuteur.
Quand l'un des interlocuteurs ne s'entend pas et qu'un Linphone n'émet plus de flux RTP en contradiction avec le dernier SDP, je remarque que, soit il n'y a pas de « Strict RTP learning complete […] », juste des « Strict RTP learning after remote address set […] » pour chaque interlocuteur, soit il y a un seul « Complete », pour un seul des interlocuteurs.
Pourquoi l'apprentissage RTP d'Asterisk échoue-t-il ? Comment le désactiver afin d'être sûr qu'il est bien l'origine du problème ? Aucune idée.
Quand les deux interlocuteurs s'entendent, il est possible qui cela coupe après 32 ou 36 secondes de communication. Cela dépend de qui téléphone. Si A téléphone à B, ça peut couper alors que ça ne coupera pas si c'est B qui téléphone à A. Ce point est constant : c'est toujours dans le même sens que cela foire. Cela semble dépendre de la version de Linphone. Si une 4.1.1 téléphone à une 3.11 / 3.12, alors ça coupera. Pas l'inverse.
Avec wireshark
, on voit un message SIP BYE avec un entête « X-Asterisk-HangupCause: no user responding » destiné au Linphone 3.X. Dans le journal d'Asterisk, on lit « Packet timed out after 32000ms with no response ».
Ajouter session-timers=refuse
dans la définition d'une ligne téléphonique dans sip.conf
ou dans l'onglet « Avancé » des paramètres d'une ligne téléphonique dans XiVo, n'aide pas.
Qui peut bien être responsable de tout ce qui précède ? Le seul qu'on n'a pas encore remis en cause est le VPN ASA.
Dès le début du diagnostic, nous avons désactivé son ALG (« no inspect sip ») et constaté que les timeouts UDP et SIP étaient déjà de plusieurs minutes. Pour savoir ce qu'est un ALG et les emmerdes pratiques que cela pose parfois, je te renvoie vers cet article : Le lulz de la VOIP - Tome 3 : Numericable.
Dans un coin, j'ai un PFSense (routeur, pare-feu, VPN, etc. en logiciel libre pour lequel on peut acheter des appliances, de la prestation d'intégration, de l'assistance, etc.) de test avec un OpenVPN de test déjà configuré. Niveau 3 (TUN). Tous les clients dans un même réseau /24. Aucun filtrage. Pas de NAT. Il a donc la même architecture que notre VPN ASA.
Je décide de tester. Ça fonctionne moyen. D'un côté, certains couples de testeurs qui ne s'entendaient pas s'entendent, ce qui est un gain. De l'autre côté, la coupure après 32 / 36 secondes d'appel en fonction des clients demeure. De même, le RTP autolearning d'Asterisk continue de foirer quelques fois. Mais, quand ça arrive, la communication pair-à-paire fonctionne systématiquement.
Du coup, le VPN ASA est en cause sur l'un des trois problèmes (communication pair-à-pair dysfonctionnelle). Néanmoins, ce n'est pas lui qui fait parfois échouer le RTP autolearn ni qui provoque une coupure après 32 / 36 secondes d'appel.
Ne sachant pas désactiver le RTP autolearn d'Asterisk, j'ai décidé de faire transiter les flux audio par le commutateur téléphonique. Ainsi, le résultat de l'apprentissage RTP aura peu d'importance, donc il n'y aura pas de bascule vers une communication pair-à-pair qui pose parfois problème avec notre VPN Cisco ASA.
Pour ce faire, il faut ajouter une ligne directmedia=no
dans la définition de chaque ligne SIP de télétravail dans le fichier sip.conf
d'Asterisk ou dans l'onglet « Avancé » d'une ligne téléphonique dans XiVo. Comme le reste, on peut activer ça pour tous les clients SIP, mais on ne le veut pas afin de ne pas détraquer le reste du parc qui fonctionne très bien et de ne pas rendre caduc notre contrat d'assistance / maintenance.
Ça fonctionne parfaitement. Tous les appels aboutissent. Aucun se termine après 32 / 36 secondes.
VICTOIRE \o/
Si tu ne veux pas charger ton commutateur téléphonique (d'après les pros de FRnOG, c'est relatif, c'est """"juste"""" de la copie de paquets RTP ) tout en obtenant le même résultat, tu peux le décharger en faisant transiter tes flux audio via un serveur TURN interne.
Que retenir de tout ça ?
nat=force_rport,comedia
et une autre ligne directmedia=no
dans la définition de chaque ligne SIP dans le fichier sip.conf
d'Asterisk ou dans les paramètres (onglet « Avancé » pour directmedia=no) d'une ligne dans XiVo. La première réécrit l'IP contenue dans le paquet SDP avec l'adresse IP source de ce paquet, garantissant ainsi que l'IP transmise à l'interlocuteur est la bonne. La deuxième fait circuler les flux audio via le commutateur téléphonique plutôt qu'en pair-à-pair, ce qui évite les ratés de l'apprentissage RTP, les bidouilles de l'ASA et la sensibilité de certains softphones.Lors d'un diagnostic de VoIP, ne prend pas les problèmes un par un, séparément, sinon tu ne vas pas t'en sortir. Si tu commences à noter que telle version de tel logiciel de téléphonie ne fonctionne pas ou que telle version a un comportement différent sous winwin et GNU/Linux ou que tel logiciel fonctionnait et ne fonctionne plus, tu ne vas pas t'en sortir car il y a trop d'éléments variants. Essaye de penser global, de revenir aux bases de la VoIP.
Soit vigilant à l'environnement de test : un testeur qui lance deux instances du VPN et, vlam, la """"mauvaise"""" IP se retrouve dans le paquet SDP ; un testeur qui ré-active le pare-feu winwin et, pouf, tes conditions de test changent ; un testeur lance un appel entre deux Jitsi sur deux ordinateurs situés dans le même LAN et constate que ça fonctionne très bien… car le flux ne passe pas par le VPN, mais par le LAN, etc.
Merci aux gens qui fréquentent FRnOG, une liste de discussion entre opérateurs de réseaux / téléphonie, pour leurs coups de main. :)