Mon premier tool-assisted speedrun (TAS) : Astérix & Obélix (Game Boy Color)

Auteur :  x0r Publié le   Nombre de commentaires : 0
Mots-clefs : tas jeu vidéo hacking

Après un peu plus de six mois de travail de longue haleine, j’ai publié mon premier tool-assisted speedrun ! Le projet a été un exercice de recherche, d’optimisation et de programmation que j'ai trouvé très enrichissant et qui m’a apporté son lot de surprises tout au long du chemin.

Le tool-assisted speedrun, ou TAS, est l’art de trouver la façon la plus rapide de terminer un jeu vidéo en supposant que le joueur soit parfait et ne fasse aucune erreur. Ma curiosité et ma fascination ont été éveillées par les vidéos de RealMyop et Coeurdevandale aux alentours de 2012, puis au fil du temps, en regardant d’autres TAS, publiés sur TASVideos.org ou présentés au cours d’événements comme Games Done Quick, j’ai eu de plus en plus envie d’en faire un moi-même. Jusqu’à décider de me lancer en août dernier.

Dans ce billet, je vous expliquerai ce que le speedrun et le tool-assisted speedrun qui s’y apparente, puis je montrerai un peu l’envers du décor et mon propre vécu pour, je l’espère, inspirer d’autres personnes à s’y mettre.

Vous disiez « tool-assisted speedrun » ?

Pour comprendre ce qu’est le tool-assisted speedrun, il faut lire le terme de droite à gauche.

Commençons par le mot « speedrun ». Faire un speedrun, c’est prendre un jeu vidéo de son choix et essayer d’y jouer du début jusqu’à la fin le plus vite possible. C’est une course contre la montre qui se fait en temps réel et toutes les astuces sont permises : tous les raccourcis sont bons à prendre et tous les bugs sont bons à exploiter. Mais toute erreur a le potentiel de contraindre le speedrunner à recommencer du début : l’exercice requiert donc beaucoup d’adresse de la part du speedrunner.

Le tool-assisted speedrun, en revanche, cherche à apporter une réponse à une question théorique : sans erreur et avec une précision parfaite, comment finirait-on le jeu le plus rapidement possible ? Pour cela, l’auteur d’un TAS se munit d’un émulateur et enregistre des séquences d’actions à la manette de la console. On peut ralentir l’action, voire revenir en arrière pour refaire un passage. On peut utiliser un débogueur et rechercher les variables intéressantes du jeu en RAM pour les visualiser. On peut aussi automatiser la recherche de solutions à l’aide de scripts fonctionnant dans l’émulateur. Et dans les cas les plus extrêmes, on exploite un bug dans le jeu qui mène à une exécution de code arbitraire : le TAS finit donc par reprogrammer le jeu et lui faire exécuter une instruction pour sauter jusqu’au générique de fin.

Concrètement, donc, le TAS se présente sous la forme d’un fichier qui décrit à quel moment appuyer sur tel bouton sur la manette, et rien d’autre. On peut télécharger le TAS de quelqu’un d’autre, puis le rejouer dans un émulateur si on a également l’image de la ROM du jeu.

Capture d’écran de l’émulateur (avec le jeu visible) et de TAStudio, l’outil qui permet d’enregistrer les actions à la manette.
Pendant que je maintiens la croix directionnelle appuyée à droite, j’ai programmé un saut à la trame no 4 520 ; huit trames plus tard, comme on peut le voir à droite, Obélix a pris son envol.

Si vous voulez en savoir plus, je vous renvoie vers une conférence de RealMyop et Coeurdevandale à ce sujet, qui montre aussi quelques exemples.

Les TAS qui m’ont marqué

Je me permets une parenthèse sur des TAS qui m’ont particulièrement impressionné quand je les ai vus pour la première fois, car j’estime qu’ils ont été pour moi une source d’inspiration.

Le premier qui me vient en tête est un TAS de Monopoly sur NES, aussi commenté par RealMyop et Coeurdevandale : c’est le premier TAS qui m’a introduit au concept de manipulation de chance. Puisque les algorithmes de génération de nombres aléatoires sont en réalité déterministes et parfois influencés par les boutons de la manette, un TAS peut provoquer des événements aléatoires plus favorables que la moyenne. Un bon exemple où la manipulation de chance est visible est un passage de ce TAS de Pokémon Vert Feuille, particulièrement impressionnant quand on connaît le jeu.

En matière d’exécution de code arbitraire, beaucoup d’exemples me viennent en tête. Le premier TAS que j’ai vu exploiter cette technique est un TAS de Super Mario Land 2 sur Game Boy. Mais cette technique peut être utilisée pour beaucoup d’autres usages humoristiques, comme ce TAS montré à l’édition 2016 d’Awesome Games Done Quick ou celui-là, qui pousse le concept jusqu’à son paroxysme.

Il existe aussi des TAS qui s’interdisent d’exploiter des bugs dans le jeu, et qui restent dans le cadre de ce qu’avaient prévu les développeurs. De bons exemples de cela sont des TAS de Pokémon qui montrent deux, voire trois jeux simultanés qui se connectent de temps en temps avec le câble Game Link pour échanger des monstres et finir entièrement le jeu comme on l’aurait fait en vrai, mais dans un temps record : notamment Bleu et Rouge ou Cristal, Argent et Jaune.

Comment je m’y suis lancé

Choisir le jeu

Pour mon premier TAS, j’avais envie de le faire sur un jeu que je connaissais plutôt bien : Astérix & Obélix, sorti sur Game Boy Color en 1999. Comme beaucoup de jeux à licence édités par Infogrames à l’époque, ce n’étaient peut-être pas des chefs-d’œuvre et certains passages pouvaient être très difficiles pour les mauvaises raisons, mais ce n’était clairement pas le pire jeu dans sa catégorie. J’apprécie particulièrement sa bande originale, composée par Alberto José Gonzalez (dont je fais remarquer l’existence d’une très sympathique introduction à son œuvre).

Je n’ai pas trouvé de TAS publié pour ce jeu : ni sur TASVideos.org, ni sur YouTube. J’étais donc tout particulièrement motivé pour combler ce vide.

Les premiers pas

Le plus difficile est de commencer : il faut trouver la ROM (je vous laisse vous débrouiller), puis installer un émulateur approprié pour faire un TAS. J’ai utilisé BizHawk, dont il suffit de télécharger la dernière version et de suivre les instructions pour l’installer. Une fois ces étapes derrière soi, les choses sérieuses peuvent commencer.

Pour un premier TAS, le tutoriel vidéo (en anglais) par The8bitbeast est une très bonne ressource (la première vidéo de la série est très importante car elle montre comment configurer correctement l’émulateur), et je conseille de le regarder pendant qu’on pratique sur son propre jeu. Je l’ai découvert trop tardivement pour pouvoir le suivre, mais j’ai pu vérifier a posteriori que mon approche était bonne tout de même.

Recherche et mise en pratique

Puisque par définition, un TAS doit faire mieux qu’un speedrun classique, on peut aussi s’inspirer de ces derniers. Dans mon cas, un speedrun sur la version Game Boy du jeu montre que le TAS devrait dans tous les cas faire mieux que 19:39 minutes. Le visionnage d’autres speedruns de ce jeu montre que ceux-ci sont toujours faits en difficulté facile, qui raccourcit certains niveaux et fait apparaître moins d’ennemis par rapport à la difficulté normale.

Il est toujours très intéressant ensuite de regarder ce qui se passe dans la mémoire du jeu pendant qu’on joue. Concrètement, on utilise des outils comme le RAM Watch et le RAM Search pour trouver les variables intéressantes dans la mémoire du jeu. Quelques exemples : les coordonnées du personnage, sa vitesse, ses points de vie, ou ceux de ses ennemis.

Une capture d’écran du RAM Watch, qui montre quelques adresses mémoire, leurs valeurs, et mes annotations.
Une partie des adresses mémoire qui m’ont semblé intéressantes à surveiller dans Astérix & Obélix.

Fort de mon expérience de rétroingénierie du bus CAN de la Renault Clio III, j’ai pu trouver assez facilement les adresses mémoire que je souhaitais surveiller. Et je suis même allé plus loin en désassemblant le jeu : j’ai utilisé mgbdis avant de lire les listings de code assembleur quand cela me semblait indispensable. Là aussi, j’étais faux-débutant, car ce n’était pas la première fois que je décompilais du code (comme l’application Livephone ou, il y a fort longtemps, l’application Transilien pour Android) et le jeu d’instructions du CPU de la Game Boy est assez simple. Il n’y a pas forcément besoin de faire ce genre de choses pour réaliser un TAS, mais dans mon cas, cet effort s’est avéré indispensable pour optimiser un passage du jeu de fond en comble.

Les scripts

Et parfois, on développe ses propres outils. On peut lancer des scripts en Lua dans BizHawk pour accomplir diverses choses : afficher des informations supplémentaires, dessiner les hitboxes des ennemis par-dessus le jeu, ou (ré-)enregistrer des passages de manière semi-automatisée.

En chargeant par exemple le script ci-dessous dans l’émulateur, on peut visualiser la position (coordonnée x) de l’athlète romain :

while true do
  local x = memory.read_u16_le(0x0502, "WRAM") +
    memory.read_u8(0x0B7A, "WRAM") / 64
  gui.text(8, 24, string.format("x = %7.6f", x))
  emu.frameadvance()
end

Ce qui nous donne ensuite l’information superposée avec l’image :

Une capture d’écran de l’émulateur qui montre le jeu en action, avec la coordonnée x de l’adversaire par-dessus.
Le script en action.

Dans mon cas, j’ai aussi eu besoin d’écrire un script pour un besoin bien spécifique : faire ce qu’on appelle de la manipulation de chance.

Les découvertes surprenantes

Quand on fait un TAS d’un jeu, et même quand on croit bien le connaître, il y a des astuces qu’on ne peut découvrir que grâce à la connaissance intime qu’on acquiert du moteur physique du jeu.

Par exemple, tapoter la croix directionnelle à une fréquence et un rythme bien précis pendant un saut permet au personnage de se déplacer plus vite que ce qu’avaient prévu les développeurs. Quand j’avais fait cette découverte, j’avais déjà couvert la moitié du jeu, et la mise en œuvre de cette technique m’a permis d’ôter trente secondes sur un total de huit minutes et demie.

La même technique marche aussi dans un niveau particulier, Helvétie Acte 4, niveau dans lequel il faut faire sauter les coffres-forts d’une banque pour délivrer le banquier enfermé dedans. Cela me permet de pousser les barils de poudre deux fois plus vite que l’intention des développeurs.

Il en émerge un bug graphique amusant : en temps normal, le baril de poudre qui ouvre le dernier coffre explose juste au moment où on a eu le temps de le pousser devant le bon coffre. Avec la technique de mouvement du TAS, il me reste du temps pour m’en éloigner. Mais si je suis suffisamment loin, le banquier est dessiné avec le mauvais jeu de couleurs : rouge, plutôt que gris ! Décidément, le souffle de l’explosion ne lui a pas fait beaucoup de bien.

Cette cascade, quoiqu’amusante, n’a finalement pas été intégrée dans le TAS car le jeu force le personnage à revenir devant le coffre au moment où il explose. Il vaut donc mieux rester juste à côté, car sinon, je perds trop de temps.

Et il ne faut pas avoir peur non plus d’expérimenter et de voir ce qui se passe quand on presse des directions opposées simultanément : c’est ainsi que j’ai découvert qu’un appui simultané sur ↑ et ↓ à un instant très précis alors que je grimpe une échelle téléporte mon personnage tout en haut, pour un gain de temps non négligeable non plus.

Arbitrer entre rapidité et divertissement

Un bon TAS ne se contente pas seulement de finir le jeu rapidement : il doit aussi être un bon divertissement.

Par exemple, il pouvait m’arriver de prendre délibérément des coups pour aller plus vite ; mais si je parviens à trouver une stratégie alternative qui m’évite de prendre des dégâts mais qui termine un niveau avec exactement le même temps, je vais conserver la seconde stratégie car le résultat est plus agréable à regarder.

Parfois, il faut attendre. C’est plus amusant de se tortiller un peu n’importe comment durant les moments d’attente que de ne rien faire du tout. De même, dans les niveaux à défilement automatique (ou « autoscrollers »), l’occasion est parfaite pour jouer au casse-cou, comme dans ce TAS de Gradius, un jeu où les niveaux défilent automatiquement tout au long du jeu.

En revanche, il y a des situations où il vaut mieux faire quelque chose de moins optimal d’un point de vue du temps pour que le TAS ne soit pas trop ennuyeux. Par exemple, durant les Jeux olympiques, chaque épreuve est répétée trois fois. Au lieu de faire trois fois une performance parfaite, j’ai choisi de faire une fois la meilleure performance, puis une mauvaise, avant d’essayer de finir ex æquo. Le tout dans un esprit similaire à ce TAS de Track & Field sur NES. Ce passage est le seul endroit où j’ai délibérément choisi de perdre un petit peu de temps pour rendre le résultat plus divertissant.

La manipulation de chance

Alea jacta est

Comme déjà expliqué plus haut, les générateurs de nombres aléatoires (RNG) de jeux vidéo sont manipulables et il est parfois nécessaire ou intéressant d’obtenir un résultat qui aurait en temps normal été très rare, voire quasiment impossible. Il n’y a pas de technique universelle car chaque jeu est programmé différemment, mais une bonne ressource à ce sujet est le wiki de TASVideos.org.

La partie qui m’a donné de loin le plus de fil à retordre dans mon TAS était un niveau où Obélix affronte un athlète romain durant des Jeux olympiques : les performances de l’athlète adverse sont aléatoires et il fallait que les deux finissent la course pour que le jeu continue. Mon enjeu était donc de faire courir le Romain le plus vite possible en manipulant la RNG du jeu. Ces efforts ont été largement payants, car j’ai gagné 13 secondes en manipulant la chance tout au long des épreuves.

Capture d’écran de l’émulateur et plusieurs fenêtres montrant des outils divers que j’ai activés pour m’aider à manipuler la chance.
Cette vue complète de mon bureau est aussi celle que j’ai fixée le plus longtemps en attendant que le script de manipulation de chance se termine.

Dans mon cas, j’avais affaire à un générateur de nombres aléatoires qui était avancé une fois par trame et à chaque fois que le jeu a besoin d’un nombre aléatoire. L’aléa est dérivé du nombre de cycles CPU exécutés depuis le dernier rafraîchissement ; autrement dit, mon algorithme était dans la catégorie 3 de cette classification. Par conséquent, je pouvais parfois (mais pas systématiquement) influencer indirectement le résultat de la RNG en appuyant sur des boutons qui ne servent à rien.

Construire un arbre de possibilités

Puisque j’avais juste besoin de faire en sorte que le Romain coure plus vite que la normale, je n’ai qu’une seule variable à surveiller : la coordonnée x du personnage. Au départ, cette coordonnée vaut 69 et la ligne d’arrivée est à la coordonnée x = 2 720. En creusant, j’ai constaté que le jeu tirait une valeur aléatoire toutes les 4 trames (soit environ 15 fois par seconde) et que je pouvais obtenir une valeur différente en appuyant sur A, sur B ou sur A et B simultanément juste avant.

On peut donc visualiser l’ensemble des possibilités sous la forme d’un arbre, dont la racine est l’état de départ, les sommets sont les points de décision où la manette peut influencer l’aléa, et les arêtes sont les boutons appuyés. À chaque sommet correspond aussi un état du jeu qui est plus ou moins proche de sa condition de victoire.

Un arbre représentant succinctement quelques possibilités d’appuis sur des boutons susceptibles de manipuler la chance, ainsi que les coordonnées x de l’adversaire en réponse à ces actions à la manette. Sur cet extraits, deux chemins amènent l’adversaire le plus loin.

Une partie de l’arbre des possibilités à 51 trames après le début de la course (au début, l’adversaire accélère d’une vitesse de base à une vitesse de croisière et les boutons ont une influence quasi nulle à ce stade). Les chemins colorés correspondent aux deux meilleures solutions ex æquo.

Par exemple, en partant de l’état où l’adversaire est à la coordonnée x = 207,39, appuyer sur A ou sur A et B simultanément fait qu’au prochain point de décision 4 trames plus tard, l’adversaire se trouve à la coordonnée x = 217,79 ; mais n’appuyer sur aucun bouton l’amène à la coordonnée x = 218,26, ce qui est plus prometteur.

Dans le graphe de l’exemple ci-dessus, on peut observer l’influence de l’appui ou non sur des boutons à des moments décisifs sur la coordonnée x de l’adversaire. Ce graphe ne représente qu’une infime partie des possibilités : après hypothèses simplificatrices, l’arbre complet a une taille que j’estime à environ 2208 sommets.

Ce graphe est un instrument-clé pour nous aider à trouver la solution qui maximise la vitesse de l’athlète. On peut en effet la trouver en cherchant le plus chemin qui nous amène le plus rapidement jusqu’à l’arrivée. L’algorithme A*, un algorithme classique, peut donner des résultats optimaux dans les bonnes conditions. L’étape suivante est donc d’appliquer cet algorithme à mon arbre de possibilités, au moyen d’un script qui modifie mon TAS et examine les répercussions au fur et à mesure de la recherche.

Du A* à un A* modifié

Pour utiliser A*, il faut d’abord fournir ce qu’on appelle une fonction heuristique. Ici, on veut minimiser le temps de la course, donc la fonction heuristique doit, à tout moment, estimer le temps qu’il reste au Romain pour finir la course.

Diviser la distance restante à courir (2 720 − x) par sa vitesse maximale (206/64 pixels par trame) est une bonne heuristique, car elle ne surestime jamais le temps restant réel ; elle est donc dite admissible. C’est une bonne chose, car un A* donne un chemin toujours optimal si l’heuristique est admissible.

Le revers de la médaille, c’est qu’une heuristique admissible entraîne une exploration d’une très grande partie d’un arbre de possibilités qui n’aurait aucune chance de tenir dans la RAM de mon PC. J’ai donc dû opter pour une heuristique non admissible pour que le temps de calcul reste raisonnable. Pour cela, j’ai simplement multiplié l’heuristique par un poids constant W (paramétrable) strictement supérieur à 1. Ainsi, on trouve une solution beaucoup plus vite, mais on perd la garantie qu’elle soit optimale.

En pratique, ma pondération W était proche de 1 : j’utilisais généralement W = 206/203 ≈ 1,015 pour le 100 mètres et des valeurs de W comprises entre 1,032 et 1,040 pour le 100 mètres haies. Au moins, ainsi, je trouvais une solution en quelques dizaines d’heures. Par ailleurs, j’ai pu vérifier qu’une des solutions était tout de même optimale, en comparant le temps couru par le Romain par ce que j’avais calculé comme minimum théorique.

Je n’ai pas trouvé de terme en français pour cette adaptation de l’algorithme A*. En anglais, on dit « weighted A* search », donc je propose la traduction « recherche A* pondérée ».

En somme, le temps nécessaire pour développer et mettre au point le script de recherche, en plus du temps nécessaire pour comprendre le générateur de nombres aléatoires et comment les nombres tirés par cet algorithme sont utilisés dans le jeu, a fait passer la durée de réalisation de mon TAS de six semaines à six mois. Mais le jeu en valait largement la chandelle car j’ai la tranquillité d’esprit d’avoir trouvé un routage (quasi) optimal dans ce passage.

Conclusion

Après avoir passé au moins cinq à six semaines à relancer mon script sur les différentes épreuves des Jeux olympiques, j’ai décidé de considérer que j’avais suffisamment bien optimisé mon TAS pour tenter de la soumettre sur TASVideos.org.

Le temps d’attente entre la soumission et l’acceptation peut énormément varier suivant la disponibilité du jury, mais dans mon cas, je crois que j’ai eu de la chance car je n’ai attendu qu’une semaine avant que le TAS soit vérifié, puis admis.

L’étape suivante pourrait être d’essayer de vérifier le TAS sur console : autrement dit, rejouer le TAS sur le matériel authentique (bien qu’en substituant la manette) et vérifier qu’il fonctionne comme sur émulateur. L’émulation Game Boy a fait d’énormes progrès dans sa fidélité et la vérification sur console de jeux Game Boy se fait habituellement avec une GameCube munie d’un Game Boy Player et la cartouche originale, donc peut-être serait-ce quelque chose à tenter.

Vous savez à présent tout sur mon premier TAS. J’espère que cela vous ait permis de mieux comprendre ce qui se passe à l’écran durant le visionnage, et qui sait, peut-être cela aura-t-il inspiré certains d’entre vous à vous lancer à votre tour. Bon visionnage !

Appels Wi-Fi sur Android : quand les paquets DNS font fausse route

Auteur :  x0r Publié le   Nombre de commentaires : 0
Mots-clefs : android dns

Sur mobile, il n’y a rien de plus frustrant quand il n’y a pas de réseau. Heureusement, de nos jours, les réseaux Wi-Fi sont partout, et grâce aux appels Wi-Fi, ou VoWiFi, il est désormais possible de passer des appels et échanger des SMS là où le réseau mobile n’a pas de couverture.

Capture d’écran d’Android où l’icône correspondant à l’activation de la VoWiFi est mise en évidence.

En ce qui me concerne, la VoWiFi n’avait l’air de marcher que sur les réseaux Wi-Fi des autres : celui de mes parents, de mes amis, parfois des réseaux publics ; même sur le Wi-Fi à bord des avions et avec le mode avion activé, l’icône de la VoWiFi s’affichait sur mon téléphone. Mais sur mon réseau Wi-Fi domestique, pour une raison que je n’avais jamais creusée jusqu’à maintenant, je n’avais pas de VoWiFi. Il ne s’agissait pas d’une simple erreur de configuration de mon pare-feu, mais d’un problème beaucoup plus subtil et beaucoup plus surprenant que ça. Dans ce billet, je vous propose de regarder ensemble ce qui m’a empêché de bénéficier de la VoWiFi chez moi.

Vue d’ensemble de la VoWiFi

Passons d’abord rapidement en revue les grands principes de la VoWiFi.

Là où la bonne vieille 2G (ou GSM) avait un fonctionnement plutôt proche du RNIS et des réseaux à commutation par circuit, les réseaux mobiles modernes (4G ou 5G) sont, dans les grandes lignes, devenus des réseaux où les appels et les SMS passent en IP. Ce qui fait que la très grande majorité des usagers d’un réseau 4G ou 5G tient dans la main un client SIP sans le savoir.

Il n’y a donc plus beaucoup de différences entre un téléphone mobile connecté à un cœur de réseau d’opérateur mobile en 4G ou 5G et le même téléphone sur lequel on aurait installé un client SIP pour accéder à un serveur Asterisk en Wi-Fi. Alors, qu’est-ce qui empêcherait ce même téléphone d’accéder au cœur de réseau de son opérateur mobile en Wi-Fi plutôt qu’en 4G ou 5G ?

Du point de vue de l’opérateur téléphonique, un réseau Wi-Fi n’est pas un réseau de confiance : l’administrateur d’un tel réseau pourrait faire ce qu’il veut, comme espionner le trafic entre les téléphones et le cœur de réseau, et peut-être récupérer des identifiants et ainsi compromettre le réseau de l’opérateur par ce biais. Pour que la VoWiFi se fasse en toute sécurité, la solution est donc de faire appel à un VPN avec une authentification forte.

Et c’est ce qui se passe dans la pratique : pour mettre en place la VoWiFi, l’équipement utilisateur (user equipment ou UE, le jargon de la 3GPP par lequel on désigne un téléphone) monte un tunnel IPSec entre lui et le cœur de réseau, au moyen d’une passerelle appelée Evolved Packet Data Gateway (ePDG) servant de point d’entrée dans le cœur de réseau de l’opérateur. Ensuite, une fois le tunnel IPSec monté, l’UE s’inscrit auprès de la fonction téléphonie du cœur de réseau utilisant le protocole SIP et sa méthode REGISTER. L’authentification SIP ne se fait non pas par un nom d’utilisateur et par un mot de passe comme on le ferait d’habitude, mais par une méthode propre qui implique des secrets stockés dans la carte SIM inséré dans l’UE.

Le nom d’hôte de l’ePDG est préalablement configuré par l’opérateur téléphonique : par exemple, chez Orange ou Sosh, l’ePDG est epdg.epc.mnc001.mcc208.pub.3gppnetwork.org. Sitôt l’UE connecté à un réseau Wi-Fi, la première chose à faire pour mettre en place la VoWiFi est donc de résoudre ce nom de domaine par des requêtes DNS de type A ou AAAA. Dans le cas d’Orange, à date, on obtient deux adresses IPv4 et zéro adresse IPv6.

Et c’est justement la résolution DNS du nom de l’ePDG qui ne fonctionnait pas chez moi.

J’ai rapidement exclu un surblocage dû à un pare-feu, car toutes les autres résolutions DNS effectuées par le système Android fonctionnent correctement. En outre, le trafic sortant utilisant le protocole ESP et vers les ports 500 et 4500 en UDP (respectivement pour ISAKMP et l’encapsulation d’ESP dans UDP), requis pour IPSec, est autorisé sur mon réseau. La cause du non-fonctionnement des appels Wi-Fi n’était donc pas du fait du réseau…

Le problème

Il se trouve que le réseau Wi-Fi typique d’un particulier possède une passerelle qui fait tout : borne Wi-Fi, switch, routeur, serveur DHCP, mais surtout, résolveur DNS. Or cela fait plus de dix ans que le résolveur DNS est distinct du routeur sur mon réseau domestique ; j’ai même repris récemment l’architecture de ce réseau pour que le service de résolution DNS soit fourni par deux jails sur deux machines FreeBSD distinctes pour éviter les points de défaillance unique sur le DNS. Ce qui donne une architecture qui se rapproche davantage d’un réseau d’entreprise que de celui d’un particulier, comme illustré sur l’exemple de la figure ci-dessous.

Schéma d’un réseau où les appels Wi-Fi ne marchent pas.

Pour dépanner la VoWiFi sur mon réseau, j’avais fait un tcpdump sur la passerelle. C’est là où j’ai vu passer du trafic DNS venant du téléphone, destiné à un des deux résolveurs DNS du réseau local… mais capturé par le routeur.

Or il m’a fallu un moment pour remarquer un détail très sournois : j’avais capturé sur mon routeur des paquets DNS dont l’adresse IP destinataire était celui du résolveur DNS… mais dont l’adresse MAC destinataire était celle de la passerelle par défaut, et non pas celle du résolveur. En toute logique, ces paquets étaient rejetés par le routeur, car l’adresse IP de destination ne correspondait pas à une adresse configurée sur l’interface Ethernet.

C’est donc comme si la table ARP était fausse. Sauf que toutes les autres résolutions DNS fonctionnaient correctement sur le mobile, donc la table ARP du système n’était pas en cause. Le problème est masqué sur les réseaux où la passerelle (donc la box Internet) fait aussi office de résolveur DNS.

Le même schéma qu’avant, mais avec un paquet DNS qui fait fausse route.

Le plus bizarre, c’est que ce bug n’est pas nouveau. Un commentaire sur un ticket ouvert chez Google, ouvert en avril 2021, rejette la faute sur Qualcomm et précise qu’il s’agit d’un bug connu chez eux. Qualcomm est entre autres le fabricant d’une gamme de processeurs de bande de base (ou basebands), circuits intégrés mettant en œuvre la pile de protocoles radio nécessaires aux communications mobiles.

L’affirmation que le bug soit chez Qualcomm me paraît plausible : puisque les secrets nécessaires pour l’authentification SIP se trouvent dans la carte SIM, il est tout à fait concevable que le processeur de bande de base les garde jalousement et soit le seul à y avoir accès.

Enfin, puisque mon téléphone contient un chipset Qualcomm, j’étais sans doute affecté par ce même bug.

Un contournement possible

Étant donné que je souhaitais très fortement faire que les appels Wi-Fi fonctionnent chez moi, il me fallait un moyen de contourner ce bug. Voilà comment j’ai procédé.

J’ai commencé par démarrer un serveur DNS sur le routeur, configuré pour rediriger toute requête vers l’un des deux jails faisant office de résolveurs DNS officiels de mon réseau.

Ensuite, il faudrait modifier la liste des serveurs DNS poussés par DHCP pour que le seul serveur DNS soit celui de la passerelle. Je considère cela comme un honteux bidouillage et il est souhaitable de le restreindre aux machines sous Android, de sorte à ne pas perturber les autres systèmes d’exploitation.

Heureusement, avec ISC DHCPD, on peut pousser des éléments de configuration de manière conditionnelle. Et pour cibler Android, on vérifie si l’option vendor-class-identifier envoyée dans le DHCPDISCOVER ou DHCPREQUEST commence par la chaîne android-dhcp-. Comme ceci :

subnet 192.0.2.0 netmask 255.255.255.0 {
  range 192.0.2.100 192.0.2.200;
  option routers 192.0.2.254;
  option domain-name "home.arpa";
  option domain-search "home.arpa";

  if substring(option vendor-class-identifier, 0, 13) = "android-dhcp-" {
    option domain-name-servers 192.0.2.254;
    /* Alternative :
     * option domain-name-servers 192.0.2.254, 192.0.2.10, 192.0.2.11; */
  } else {
    option domain-name-servers 192.0.2.10, 192.0.2.11;
  }
}

Après avoir redémarré le serveur DHCP, je me suis déconnecté, puis reconnecté à mon réseau Wi-Fi sur le téléphone. Au bout de cinq à dix secondes, l’icône symbolisant la VoWiFi est apparue. Victoire ! Pendant ce temps, les autres machines continuent de recevoir par DHCP la liste inchangée des résolveurs DNS.

Une méthode alternative aurait été de désigner les téléphones sous Android par leurs adresses MAC. Mais ce n’est à mon avis pas une bonne idée : la plupart des téléphones utilisent des adresses MAC aléatoires par défaut, ce qui ne se désactive que par des manipulations directement sur le terminal. Et je ne souhaite pas non plus recenser les adresses MAC des terminaux des amis qui seraient de passage chez moi. Ce serait fastidieux à gérer. C’est pour cela que j’estime qu’il vaut mieux différencier suivant le vendor-class-identifier.

Conclusion

Vous savez maintenant pourquoi, dans certains cas, les appels Wi-Fi ne fonctionnent pas sur Android et comment contourner le bug dans une situation analogue à la mienne.

Le code erroné est déployé dans ce que je présume être des processeurs de bande de base qui ont été fabriqués dans de très grandes séries. Je serais donc curieux de savoir à quel point ce bug a été observé ailleurs. Mes recherches au moment où je cherchais à résoudre le problème ne m’ont en effet donné que très peu d’indices. Alors, puisse ce billet de blog être un indice de plus.

Configurer une passerelle FXO AudioCodes avec Asterisk

Auteur :  x0r Publié le   Nombre de commentaires : 6
Mots-clefs : voip asterisk audiocodes

Mise à jour du 5 janvier 2024 : correction de quelques erreurs de configuration détaillées en annexe à la fin du billet.

Cela fait environ neuf ans maintenant que j’ai mis en place un serveur Asterisk pour gérer ma téléphonie domestique. Depuis, la partie qui a été de loin la plus compliquée a été l’accès au réseau téléphonique commuté (RTC) : avec une fibre chez Orange, et donc une ligne téléphonique fixe dont l’accès passe obligatoirement par leur Livebox, un matériel qui n’a pas été conçu pour être raccordé à autre chose qu’un téléphone à touches, cette contrainte a été quelque peu frustrante parfois.

Après avoir attiré les foudres d’Orange quand j’ai essayé de parler directement en SIP à leur infrastructure IMS, quelles solutions me reste-t-il pour profiter de mon serveur Asterisk sans prendre de ligne VoIP chez un opérateur tiers ?

Dans ce billet, qui sera l’opportunité pour moi de mettre à jour des articles que j’ai rédigés précédemment sur le sujet, je vous présente la solution que j’utilise actuellement : passer par un boîtier de conversion SIP vers analogique (FXS/FXO) comme la gamme MP-1xx de chez AudioCodes. J’ai eu récemment l’occasion d’aider un de mes lecteurs à configurer le sien et ce billet, que j’espère pouvoir être utile à d’autres, est le fruit d’un long échange de courriels qui lui a permis lui aussi de profiter de la souplesse d’Asterisk.

Boîtier AudioCodes MP-114.
Un AudioCodes MP-114 en fonctionnement.

Pourquoi un boîtier séparé

Mon installation téléphonique a donc un certain historique sur lequel il peut être intéressant de revenir dessus en préambule. Au début, en 2014, mon serveur Asterisk tournait sur une machine sous Gentoo Linux dans laquelle j’avais enfiché une carte OpenVox A400P, une carte PCI fournissant jusqu’à quatre interfaces FXO (à raccorder à des lignes analogiques) ou FXS (à raccorder à des téléphones analogiques). Environ deux ans plus tard, j’ai remplacé la machine par une nouvelle, plus puissante, ce qui a été l’occasion de passer à FreeBSD, mais j’utilisais toujours cette même carte.

J’ai d’ailleurs toujours continué à utiliser cette carte pendant que j’expérimentais avec siproxd_orange. En temps normal, ce proxy SIP que j’avais concocté pour interfacer mon Asterisk directement avec l’infrastructure téléphonique d’Orange n’était pas en production chez moi, mais il m’arrivait ponctuellement de faire de petites campagnes d’essai. Je tombais alors sur d’étranges bugs qui empêchaient souvent le bon fonctionnement des appels entrants, comme en témoignent les billets consacrés aux versions 0.2 ou 0.2.1 de siproxd_orange. Puis après la fin du projet, je suis revenu à la carte OpenVox.

Cette solution, quoique peu élégante à mon goût, fonctionnait bien pendant quelques années jusqu’au milieu de l’année 2020, quand mon serveur s’est mis à avoir des kernel panics de manière assez aléatoire et inexpliquée. Cela arrivait souvent pendant un appel téléphonique. Nous étions en plein confinement à ce moment-là, et un de ces plantages est survenu en plein milieu d’une réunion client ; c’était très embarrassant. J’ai fini par obtenir la preuve que le fautif était le portage FreeBSD de DAHDI, le pilote de la carte FXO, qui n’avait plus l’air maintenu. Du côté Linux, le même pilote, mieux tenu à jour, avait carrément retiré la prise en charge de mon modèle de carte analogique : signe qu’il était temps de trouver une autre solution.

Je me suis donc tourné vers un adaptateur téléphonique sous la forme d’un boîtier à part. Le principe est simple : munis d’une Ethernet et d’une prise FXO, ces boîtiers font la conversion entre la signalisation et les signaux analogiques historiques d’une part, et SIP et RTP d’autre part. Il suffit généralement de paramétrer le routage à effectuer quand un INVITE arrive du côté SIP ou quand une sonnerie arrive côté FXO.

Donc, fin 2020, j’ai investi dans un adaptateur Grandstream HT-813, puis j’ai retiré la carte analogique. Les plantages se sont arrêtés, mais cette solution de remplacement ne me plaisait guère. Le son était gâché par un grésillement à 60 Hz en permanence. Il y avait aussi parfois des ratés dans l’extraction du numéro de l’appelant. Mais ce qui m’a contraint à abandonner cette solution est que le boîtier ne détectait pas toujours les sonneries correctement : parfois, un appel entrant était détecté comme un premier appel, abandonné, puis un second arrivant très vite après, alors qu’en fait c’était le même train de sonneries. Peut-être des problèmes qui ont été corrigés par des mises à jour de firmware depuis, mais je n’avais guère envie de jouer les béta-testeurs pour un produit qui m’a coûté pas loin de cent euros.

Alors, début 2021, j’ai saisi une occasion sur eBay : un AudioCodes MP-114 d’occasion pour seulement 30 USD, dans sa configuration à deux prises FXS et deux prises FXO. Ce matériel me donne pleinement satisfaction : il tourne maintenant depuis un peu plus de deux ans sans aucune défaillance ni bug. L’appareil a beaucoup plus de possibilités et de souplesse que le GrandStream, ce qui a pour inconvénient d’en rendre le paramétrage difficile. Mais après avoir pris le temps de lire le manuel d’administration, j’ai fini par comprendre sa logique. Ce faisant, j’ai commis une grossière erreur : celle de ne pas documenter les paramètres que j’ai changés par rapport aux réglages usine.

Heureusement, une conversation récente par e-mail avec un de mes lecteurs a été l’occasion pour moi de revisiter la marche à suivre pour configurer un serveur Asterisk et un boîtier AudioCodes pour permettre à Asterisk d’accéder au RTC via une Livebox. Je partage donc avec vous, dans ce billet, la marche à suivre pour une configuration minimale viable d’Asterisk et d’un AudioCodes MP-1xx à cet effet. Merci à Benjamin, sans qui ce type de billet aurait été plus fastidieux à préparer pour moi !

Prérequis

Avant de commencer, il faut recueillir les adresses IPv4 du serveur Asterisk et de l’AudioCodes (ces boîtiers-là ne prennent pas en charge IPv6, hélas).

Ici, nous supposerons que :

  • le serveur Asterisk a pour adresse IPv4 192.168.10.10 et l’AudioCodes 192.168.10.20 ;
  • Asterisk et tous les postes téléphoniques ont des URI SIP qui finissent en @tel.example ;
  • le nom d’hôte complet de l’AudioCodes est audiocodes.tel.example ;
  • le numéro attribué à la ligne externe est le 09 00 11 22 33, ce qui s’exprime au format E.164 comme +33900112233.

Mon utilisation de noms de domaine dans les URI SIP n’est qu’une question d’élégance ; ce n’est pas indispensable. On peut très bien s’en passer ; dans ce cas, il faut simplement substituer l’adresse IP d’Asterisk à tel.example et l’adresse IP de l’AudioCodes à audiocodes.tel.example.

La configuration que je donne est valable pour un AudioCodes MP-114_FXS_FXO. Ses ports FXS sont numérotés 1 et 2 et ses ports FXO sont les ports 3 et 4. Sur un MP-118_FXS_FXO, les ports FXS sont les ports 1 à 4 et les ports 5 à 8 sont les FXO.

Enfin, côté Asterisk, je pars du principe que la pile SIP utilisée est celle de chan_pjsip et non pas chan_sip, qui est dépréciée.

Configuration d’Asterisk

Du côté d’Asterisk, il faut déclarer l’existence du boîtier AudioCodes. La configuration est similaire à celle d’un « trunk » SIP ordinaire.

Pour les appels sortants, il faut déclarer un endpoint et un AOR. J’ai nommé les deux audiocodes. La configuration est la suivante :

[audiocodes-common-settings](!)
type=endpoint

; À remplacer par le contexte servant de point d'entrée aux appels
; entrants
context=incoming_from_audiocodes

; À remplacer par le nom d'un transport sur lequel l'AudioCodes est
; joignable depuis Asterisk, et vice-versa.
transport=transport-udp-ipv4

; On n'autorise que le codec G.711 loi A : ça nous donnera la meilleure
; qualité. Les autres codecs sont strictement inutiles.
disallow=all
allow=alaw

; Mettre la langue des annonces et les tonalités en français.
language=fr
tone_zone=fr

; L'AudioCodes transmet le numéro de l'appelant tel que reçu par la
; Livebox, et on peut lui faire confiance
trust_id_inbound=yes

; Éléments de routage
from_domain=tel.example

[audiocodes](audiocodes-common-settings)
type=endpoint
aors=audiocodes

[audiocodes]
type=aor
max_contacts=1
remove_existing=yes

; Modifier le nom de domaine, mais garder « audiocodes@ ».
contact=sip:audiocodes@audiocodes.tel.example

Pour les appels entrants, l’AudioCodes envoie à Asterisk des INVITE dont l’en-tête From a une URI qui contient le numéro de l’appelant. Pour que cela fonctionne correctement avec Asterisk, il faut déclarer un endpoint anonymous qui rejette tous les appels venant d’inconnus (si ce n’est pas déjà le cas), ainsi qu’un endpoint pour gérer le cas d’un appel entrant venant de l’AudioCodes.

; Endpoint utilisé pour les appels entrants.
; Garder le « anonymous@ » et adapter le nom de domaine
[anonymous@audiocodes.tel.example](audiocodes-common-settings)
type=endpoint
deny=0.0.0.0/0
deny=::/0
permit=192.168.10.20              ; à adapter

; Endpoint utilisé pour les appels entrants masqués.
; Ici, il faut que ce soit « anonymous@anonymous.invalid ».
[anonymous@anonymous.invalid](audiocodes-common-settings)
type=endpoint
deny=0.0.0.0/0
deny=::/0
permit=192.168.10.20              ; à adapter

; anonymous
; utilisé pour jeter tous les autres
[anonymous]
type=endpoint
language=fr
tone_zone=fr
deny=0.0.0.0/0
deny=::/0 

C’est tout du côté de la configuration de PJSIP.

La suite se passe dans extensions.conf. Nous allons créer un contexte minimal, incoming_from_audiocodes, qui fait sonner les deux téléphones SIP Tel1 et Tel2 simultanément :

[incoming_from_audiocodes]

exten => +33900112233,1,NoOp()
 same => n,Dial("PJSIP/Tel1&PJSIP/Tel2", 20)
 same => n,Hangup() 

Pour les appels sortants, il ne reste plus qu’à modifier le contexte utilisé par les téléphones internes pour que les appels sortants passent par un appel à Dial() comme :

Dial("PJSIP/audiocodes/sip:${EXTEN}@audiocodes.tel.example")

Configuration du boîtier AudioCodes

Ci-après figure une longue liste de paramètres à modifier sur le boîtier AudioCodes. Sur l’interface d’administration Web, dans le mode « Configuration », il y a dans la colonne de gauche la possibilité d’afficher les réglages de base ou les réglages complets. Pour cette liste, il faut au préalable cliquer sur la case « Full ».

Compatibilité électrique et électronique

Les paramètres par défaut des boîtiers AudioCodes conviennent pour une compatibilité électrique et électronique avec les réseaux téléphoniques états-uniens. Les caractéristiques des lignes téléphoniques françaises étant différentes, nous allons commencer par effectuer les réglages de compatibilité ci-après.

  • VoIP > Media > Fax/Mode/CID Settings :

    • Caller ID TypeStandard ETSI
  • VoIP > Coders and Profiles > Coders :

    Tout supprimer. Ne laisser que le codec « G.711A-law », qui donne la meilleure qualité audio durant les communications. Laisser « Packetization Time » à 20 ms et « Silence Suppression » à « Disabled ».

  • VoIP > GW and IP to IP > Analog Gateway > FXO Settings

    • Dialing ModeOne Stage
    • Answer SupervisionNo
    • Rings before Detecting Caller ID0
    • Disconnect Call on Busy Tone Detection (CAS)Enable
    • Disconnect On Dial ToneEnable
    • FXO Double AnswerDisable

    Le paramètre « Rings before Detecting Caller ID » doit être mis à 1 si, à chaque appel entrant, l’AudioCodes signale d’abord un appel masqué avant de l’annuler et signaler un nouvel appel entrant avec le numéro du correspondant. La valeur 0 convient pour la Livebox 4 ; la valeur 1 convient pour la Livebox 6.

  • VoIP > Media > Analog Settings

    • Min. Hook-Flash Detection Period [msec]100
    • Max. Hook-Flash Detection Period [msec]500
    • FXS Coefficient TypeEurope
    • FXO Coefficient TypeEurope

    Après avoir modifié ces paramètres, l’AudioCodes devra redémarrer pour les appliquer ; le bouton « Burn » permet de sauvegarder la configuration actuelle.

  • VoIP > SIP Definitions > Advanced Parameters

    • Polarity ReversalDisable
    • Current DisconnectDisable

J’ai également été amené à jouer avec le paramètre « Voice Volume », sur la page VoIP > Media > Voice Settings, pour obtenir un niveau d’écoute satisfaisant. De mon côté, j’ai choisi de positionner ce volume à +6 dB.

Pour finir, il y a un paramètre qui nécessite d’exporter le fichier de configuration au format .ini, l’éditer puis le réimporter. Il s’agit d’ajouter le paramètre ETSICallerIDTypeOneSubStandard, qu’il faut positionner à 2 afin que l’AudioCodes détecte le signal correspondant à l’identité de l’appelant au bon moment à la réception d’un appel entrant. Le fichier de configuration devrait comporter une section comme ceci :

[Analog Params]

MinFlashHookTime = 100
FlashHookPeriod = 500
ETSICallerIDTypeOneSubStandard = 2
CountryCoefficients = 66
FXSCountryCoefficients = 66
EnableFXOCurrentLimit = 1

Le sous-standard ainsi sélectionné désigne la transmission de l’identité de l’appelant par une pulsation de sonnerie juste avant la première sonnerie, ce qui est désigné par la norme ETSI EN 300 659-1 par le sigle « RP-AS » (Ringing Pulse Alerting Signal). Le mode de transmission est détaillé dans le § 6.1.2 de cette norme.

Si vous comptez utiliser vos prises FXS avec des téléphones à cadran comme le Socotel S63, vous pouvez aussi ajouter le paramètre EnablePulseDialDetection = 1 dans le fichier de configuration, dans la même section.

Interopérabilité avec Asterisk

Les paramètres ci-dessous permettent au boîtier AudioCodes et à Asterisk de fonctionner correctement ensemble.

  • VoIP > GW and IP to IP > Analog Gateway > Caller Display Information

    S’assurer que pour tous les ports FXO (donc les ports 3 et 4 sur un MP-114), le paramètre « Presentation » soit positionné à « Restricted » et non pas « Allowed ». Ceci permet de signaler les appels masqués comme tels.

  • VoIP > GW and IP to IP > Analog Gateway > Caller ID Permissions

    Pour chaque port FXO, mettre « Caller ID » à « Enable ». Ceci permettra à l’AudioCodes de recevoir le numéro de l’appelant de la Livebox pour le transmettre ensuite à Asterisk.

  • VoIP > SIP Definitions > General Parameters

    • Asserted Identity ModeAdd P-Asserted-Identity

    Ceci devrait faire en sorte que l’AudioCodes transmette bien à Asterisk le numéro de l’appelant tel qu’il l’a reçu de la Livebox, si ce n’était pas déjà le cas.

  • VoIP > GW and IP to IP > Analog Gateway > FXO Settings

    • Waiting for Dial ToneNo
  • VoIP > SIP Definitions > General Parameters

    • Enable Early MediaEnable

Routage

Les paramètres de configuration ci-après définissent ce que l’AudioCodes doit faire lorsqu’il reçoit un appel côté analogique ou côté SIP :

  • VoIP > Control Network > IP Group Table

    Cliquer sur « Add », puis saisir les paramètres suivants :

    • Index1
    • DescriptionRTC
    • Proxy Set ID1
    • SIP Group Nameaudiocodes.tel.example
    • Contact Useraudiocodes
    • Local Host Nameaudiocodes.tel.example
    • Media Realm Name(vide)
    • IP Profile ID0
  • VoIP > Control Network > Proxy Sets Table

    Sélectionner le Proxy Set ID 1, puis saisir :

    • Proxy Address 1asterisk.tel.example
    • Transport Type 1UDP
  • VoIP > GW and IP to IP > Hunt Group > Endpoint Phone Number

    Dans une ligne du tableau, configurer les éléments suivants :

    • Channel(s)5-8
    • Phone Numberif-fxo5
    • Hunt Group ID1
    • Tel Profile ID0

    Le paramètre « Channel(s) » dépend du matériel : sur un MP-114_FXS_FXO, ce sera le port 3 ; sur un MP-118_FXS_FXO, ce sera le port 5.

    Sur certains modèles, il se peut que les numéros de canaux soient numérotés à partir de 0 plutôt qu’à partir de 1. Au cas où la configuration ne marcherait pas, essayez en retranchant 1 au numéro de port dans le champ « Channel(s) ».

    Le « Phone Number » de ce Hunt Group est obligatoire, mais peut être alphanumérique à condition de se terminer par un chiffre. Une possibilité est de mettre « if-fxo » suivi du numéro du port de la première prise FXO (comme « if-fxo3 »). Ainsi, les prises FXO peuvent être adressés par des URI SIP de la forme sip:if-fxoN@audiocodes.tel.example, où N désigne un numéro de port.

  • VoIP > GW and IP to IP > Hunt Group > Hunt Group Settings

    Cliquer en haut à droite sur « Advanced Parameter List » pour afficher les paramètres avancés.

    Dans une ligne du tableau, configurer les éléments suivants :

    • Hunt Group ID1
    • Channel Select ModeCyclic Ascending
    • Registration Mode(laisser vide)
    • Serving IP Group ID1
    • Gateway Nameaudiocodes.tel.example
    • Contact Useraudiocodes
  • VoIP > GW and IP to IP > Routing > Tel to IP Routing

    Dans une ligne du tableau, saisir :

    • Src. Hunt Group ID1
    • Dest. Phone Prefix*
    • Source Phone Prefix*
    • Dest. IP Address(vide)
    • Port(vide)
    • Transport TypeNot Configured
    • Dest. IP Group ID1
    • IP Profile ID0
    • Cost Group IDNone
  • VoIP > GW and IP to IP > Routing > IP to Hunt Group Routing

    Afficher les paramètres avancés.

    Dans une ligne du tableau, saisir :

    • Dest. Phone Prefix[+,0,1,3,*,#]
    • Source Phone Prefix(vide)
    • Source IP address(adresse IP du serveur Asterisk)
    • Hunt Group ID1
    • Source IP Group ID−1
    • IP Profile ID0
  • VoIP > GW and IP to IP > Analog Gateway > Automatic Dialing

    Pour chaque port FXO, mettre « +33900112233 » et mettre « Auto Dial Status » à « Enable ». Ceci indique à l’AudioCodes que sur une sonnerie, il doit prendre la ligne immédiatement et rediriger l’appelant vers Asterisk.

Cette configuration représente le minimum nécessaire pour passer des appels depuis et vers l’extérieur. Faites un test dans les deux sens ; et si cela ne fonctionne pas, faites une capture réseau avec tcpdump sur la machine hébergeant Asterisk, récupérez le .pcap ainsi obtenu et examinez-le dans Wireshark sur un poste de travail.

Conclusion

Dans ce billet, je vous ai montré comment configurer de façon adéquate un boîtier AudioCodes afin de pouvoir profiter d’un serveur Asterisk derrière une ligne Livebox sans peine.

J’ai mis ces paramètres de configuration sur ce billet de blog dans l’espoir que cela puisse être utile à d’autres qui seraient dans une situation analogue. Bien entendu, cela n’est qu’un point de départ et il est possible de faire quelque chose de beaucoup plus sophistiqué si on souhaite adresser des points de détail comme le format des URI échangés entre Asterisk et l’AudioCodes.

Mise à jour du 5 janvier 2024

Les modifications suivantes ont été apportées :

  • Ajout d’une photo d’un boîtier AudioCodes en guise d’illustration ;
  • Côté AudioCodes :
    • Paramètre « Dest. Phone Prefix » : [+,0,1,3] devient [+,0,1,3,*,#] (pour pouvoir accéder aux services téléphoniques comme le renvoi d’appel),
    • Ajout d’un commentaire sur le paramètre « Rings before Detecting Caller ID » qui a parfois besoin d’être mis à 1 au lieu de 0 suivant la situation,
    • Ajout d’explications supplémentaires concernant le paramètre ETSICallerIDTypeOneSubStandard,
    • Le paramètre « Disconnect On Dial Tone » doit être mis à « Enable ». Cela afin de gérer correctement le cas d’un appel entrant décroché moins de quelques secondes après l’abandon de l’appel par l’appelant. Cela se manifestait le plus souvent par des messages vocaux laissés sur le répondeur consistant uniquement en une tonalité d’invitation à numéroter (tonalité continue),
    • Le paramètre « Caller Display Information » doit être mis à « Restricted » pour les ports FXO. Il s’agit en effet de ce qui est présenté par défaut en cas d’appel entrant sans présentation du numéro. Dans ce cas, il est souhaitable de faire apparaître cela comme un appel masqué. Avec le paramètre « Allowed », les appels masqués s’affichent comme venant de « 1 » ;
  • Côté Asterisk : adaptation de la configuration pour gérer correctement le cas des appels masqués après modification de la configuration côté AudioCodes. Sans cela, les appels masqués sont rejetés.

monrer.fr : une couverture complète grâce à la nouvelle API d’Île-de-France Mobilités

Auteur :  x0r Publié le   Nombre de commentaires : 23
Mots-clefs : trains monrer.fr prim

Le 28 février dernier, j’ai poussé un peu plus tôt que prévu en production une nouvelle version de monrer.fr. Presque dix ans depuis sa mise en service, monrer.fr couvre désormais toutes les lignes RER et Transilien dans leur intégralité. Oui, même les lignes A et B !

Capture de monrer.fr montrant les horaires temps réel à la gare d’Auber, une gare du RER A dans une partie exploitée par la RATP.
Un exemple ici à la gare d’Auber, sur le RER A, en zone RATP.

J’aurais voulu faire un peu plus de tests la mise en production, mais l’actualité, marquée par un mouvement social qui avait toutes les chances d’être bien suivie, m’a poussé à accélérer le tempo. Jusqu’ici, le pari semble réussi car je ne crois pas avoir détecté de problèmes majeurs. Il y a peut-être des bugs d’affichage çà et là, mais je les corrigerai quand j’aurai un peu plus de temps.

J’ai accompli cet exploit simplement en changeant de source de données pour les horaires temps réel. L’API temps réel Transilien, que j’ai utilisée depuis plus de neuf ans, va être décomissionnée à la fin du mois de mars 2023. Et celle de la RATP, que je comptais intégrer à monrer.fr aussi (mais j’ai fini par abandonner car elle était fastidieuse à tester et à exploiter), a été décommissionnée le 30 septembre 2022.

Ces deux sources de données cèdent toutes les deux la place à une nouvelle, gérée par la nouvelle Plateforme Île-de-France Mobilités (PRIM) : l’API Prochains passages (requête unitaire). Un vrai soulagement pour moi pour de nombreuses raisons. Premièrement, le quota d’appels à cette API est beaucoup plus élevé : au lieu de 20 appels par minute chez la SNCF, j’ai désormais droit à 1 million d’appels par jour. Je n’ai donc plus aucune raison de me soucier des problèmes de quota que j’ai pu avoir par le passé. Deuxièmement, cette API agrégeant les données de plusieurs transporteurs, je n’ai plus besoin de faire ce travail moi-même. Troisièmement, les informations sont plus complètes : l’API retourne les numéros de voie (pour la plupart des gares) et, en fonction du transporteur, aussi les trains terminus ou sans arrêt.

Avec cette nouvelle version, c’est pour moi un rêve qui devient réalité : une couverture complète grâce à des jeux de données de qualité, en Open Data et dont l’accès est gratuit. Je consacrerai un billet ultérieur à une discussion technique de la nouvelle API, pour les curieux et pour partager mon retour d’expérience détaillé. En attendant, profitez-en !

Une méthode alternative pour configurer Wireguard sous FreeBSD

Auteur :  x0r Publié le   Nombre de commentaires : 0
Mots-clefs : freebsd wireguard

Mise à jour du 6 janvier 2024 : adaptation des instructions pour ne plus tenir compte des spécificités de FreeBSD 12, désormais en fin de vie. Désinstallez les paquets wireguard-tools et wireguard-kmod, puis veillez à exécuter /usr/bin/wg et non plus /usr/local/bin/wg dans les scripts /etc/start_if.<interface>.

J’ai eu l’occasion d’expérimenter récemment avec Wireguard sous FreeBSD. Globalement, je suis plutôt satisfait du mode de fonctionnement, mais aussi de la façon dont cela a été packagé de manière générale sous FreeBSD.

Pour mon usage, j’avais besoin de créer un VPN point à point entre deux machines, une derrière une Livebox et l’autre chez un hébergeur. Je voulais que la seconde ait accès à des services hébergés sur la première, comme Postfix et nginx. Ces services sont configurés sur la première machine pour écouter sur une liste explicite d’adresses IP.

Mais au redémarrage de la machine chez l’hébergeur, Postfix et nginx refusaient de démarrer, car l’interface wg0 créée par Wireguard n’existait pas encore. Ce problème est dû au lancement trop tardif du script /etc/rc.d/wireguard à l’initialisation du système.

Comment faire, alors, pour que les VPN Wireguard soient montés plus tôt ? La solution consiste à les démarrer en même temps que les autres interfaces réseau du système, via le script /etc/rc.d/netif et des éléments de configuration dans /etc/rc.conf. La méthode que je vais vous montrer est en production chez moi depuis quelques mois, et je n’ai pas rencontré de problème particulier jusqu’à maintenant.

Prérequis

Il faudra avant toute chose choisir un plan d’adressage pour le VPN et générer des clefs et un secret partagé.

Adressage

Pour mon VPN, j’ai choisi d’adresser les deux extrémités en IPv6, en utilisant les adresses locales uniques (Unique Local Addresses, ULA), dans la plage fc00::/7. Pour avoir la quasi-garantie d’une plage unique, il faut utiliser un générateur pour obtenir un /48 privé. J’obtiens par exemple fdd9:835d:6e41::/48.

Puisqu’il n’y a que deux machines dans ce réseau, alors d’après la RFC 6164, il est tout à fait possible (et souhaitable) de se restreindre à un /127 pour adresser les deux côtés. Mais alors, quelles valeurs choisir pour les 64 bits les plus à droite de l’adresse ?

Au début, j’avais choisi fdd9:835d:6e41::1 et fdd9:835d:6e41::2. Mais ce choix est mauvais. Sauriez-vous pourquoi ? Rassurez-vous, il m’a fallu plusieurs semaines pour comprendre.

On pourrait alors choisir fdd9:835d:6e41:: et fdd9:835d:6e41::1 à la place. Là aussi, le choix est mauvais, car la première des deux adresses a ses 64 bits de poids faible à zéro et leur usage en tant qu’adresse unicast est déconseillé. Pour la même raison, l’intervalle fdd9:835d:6e41::ffff:ffff:ffff:ff7f à fdd9:835d:6e41::ffff:ffff:ffff:ffff est déconseillé également.

J’ai donc fini par choisir fdd9:835d:6e41::a et fdd9:835d:6e41::b. Ainsi, pour la suite, j’appellerai les deux côtés « A » et « B », d’après l’identifiant d’interface que je leur donnerai dans le /127 susmentionné.

Dans mon cas, le côté B est celui qui initie la connexion avec le côté A. Les deux côtés peuvent initier la connexion, tout comme avec IPSec, mais dans mon cas, le côté B se trouve derrière un NAT et son adresse IP publique peut changer, alors que le côté A a une adresse IPv4 publique fixe.

Génération des clefs

Ensuite, il faut créer les clefs publiques et privées de chaque côté. La commande ci-dessous génère deux fichiers contenant des clefs représentés en base64.

# umask 077
# wg genkey | tee clef_privee | wg pubkey > clef_publique

Puis, sur l’un ou l’autre des côtés, générez un secret partagé entre les deux stations, qui se trouvera également représenté en base64 dans un fichier :

root@A:~# umask 077
root@A:~# wg genpsk > secret_partage

Nous pouvons ensuite passer à la configuration proprement dite.

Configuration

La page man de rc.conf(5) explique que, pour chaque interface à démarrer, netif cherche un script nommé /etc/start_if.<interface>. S’il existe, alors il est exécuté avant de configurer les adresses. Nous allons donc créer de chaque côté un script /etc/start_if.wg0, destiné à l’interface créée pour le VPN Wireguard. Cette interface est configurée en passant par l’utilitaire wg(8).

Côté B (initiateur)

Ainsi, sur le côté B, qui initie la connexion, le script /etc/start_if.wg0 est le suivant :

#!/bin/sh

/usr/bin/wg setconf wg0 /dev/stdin <<EOF
[Interface]
ListenPort = 51820
PrivateKey = clef privée de B

[Peer]
AllowedIPs = fdd9:835d:6e41::a/128
PublicKey = clef publique de A
PreSharedKey = secret partagé
Endpoint = adresse ou nom d’hôte de A:51820
PersistentKeepAlive = 30
EOF

Ici, j’ai choisi d’ajouter l’option PersistentKeepAlive, car il s’agit du côté qui initie la connexion et que je dois traverser des NAT IPv4. Et dans ce script, il faut utiliser le chemin complet vers l’utilitaire wg, car /usr/local/bin ne se trouve pas dans le PATH du shell qui l’exécute.

Étant donné que le fichier contient des secrets, celui-ci doit impérativement être chmod 700 ou la confidentialité de votre VPN sera compromise.

On peut ensuite ajouter les éléments de configuration suivants au rc.conf du côté B :

cloned_interfaces="wg0"  # si cette variable existe déjà, ajoutez "wg0"
ifconfig_wg0_ipv6="inet6 fdd9:835d:6e41::b prefixlen 127"

C’est tout !

Côté A

Pour le côté A, l’idée est la même, mais le contenu du script /etc/start_if.wg0 est légèrement différent :

#!/bin/sh

/usr/bin/wg setconf wg0 /dev/stdin <<EOF
[Interface]
ListenPort = 51820
PrivateKey = clef privée de B

[Peer]
AllowedIPs = fdd9:835d:6e41::b/128
PreSharedKey = secret partagé
PublicKey = clef publique de A

Là encore, pensez à faire un chmod 700 sur ce fichier pour éviter de compromettre votre VPN.

Les éléments à ajouter dans rc.conf sont similaires :

cloned_interfaces="wg0"  # si cette variable existe déjà, ajoutez "wg0"
ifconfig_wg0_ipv6="inet6 fdd9:835d:6e41::a prefixlen 127"

Démarrage du VPN

Enfin, de chaque côté, il ne reste plus qu’à démarrer l’interface wg0…

# service netif start wg0

…et de tester en « pingant » l’autre côté :

root@A:~# ping fdd9:835d:6e41::b
PING6(56=40+8+8 bytes) fdd9:835d:6e41::a --> fdd9:835d:6e41::b
16 bytes from fdd9:835d:6e41::b, icmp_seq=0 hlim=64 time=60.610 ms
16 bytes from fdd9:835d:6e41::b, icmp_seq=1 hlim=64 time=79.763 ms
16 bytes from fdd9:835d:6e41::b, icmp_seq=2 hlim=64 time=59.891 ms
16 bytes from fdd9:835d:6e41::b, icmp_seq=3 hlim=64 time=59.442 ms
^C
--- fdd9:835d:6e41::b ping6 statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 59.442/64.926/79.763/8.576 ms

Conclusion

Je vous ai donc présenté une méthode pour lancer un VPN Wireguard le plus tôt possible au démarrage de FreeBSD.

Cette méthode ne se limite pas qu’aux VPN point à point, mais il se trouve que dans mon cas d’usage, un lien entre deux machines suffisait. Si nous avions eu plusieurs machines initiatrices de connexions à un seul et même point central, les configurations de chaque initiateur serait identique (à la clef privée près) et le point central aurait une déclaration par initiateur autorisé.

Il y a aussi quelques opportunités de mettre les choses un peu plus au propre dans ce système. Tout d’abord, le script /etc/start_if.wg0 contient toute la configuration pour l’interface wg0, dans un seul fichier. Je peux comprendre que ce choix puisse déranger certains. Mais adapter ce script pour qu’il lise des secrets ou un fichier de configuration complet dans /usr/local/etc/wireguard est tout à fait possible.

De même, le nom wg0 est codé en dur dans le fichier ; mais là encore, il est possible de modifier le script en examinant le nom sous lequel il est appelé (variable $0) et d’en dériver le nom de l’interface à démarrer (par la transformation ${0##*.}). Ainsi, s’il faut démarrer un autre VPN Wireguard, en imaginant que l’interface s’appelle wg1, on pourrait finir par juste pouvoir s’en sortir en faisant de /etc/start_if.wg1 un lien symbolique vers /etc/start_if.wg0.

Dans tous les cas, je vous ai montré qu’il est tout à fait possible de passer par netif, pour démarrer les VPN Wireguard en même temps que les autres interfaces réseau, dès l’initialisation du système. Ainsi, les interfaces wg se comportent beaucoup plus comme des interfaces natives du système, ce qui est élégant et appréciable.