Kevin Bataille

Comment connecter deux instances Asterisk en trunk avec PJSIP

La VoIP (Voice over IP) révolutionne la téléphonie en utilisant Internet pour les appels. L’interconnexion de serveurs Asterisk change la donne pour les petits FAI et les entreprises multi-sites cherchant une solution téléphonique flexible et scalable.

Ce guide pratique explique comment configurer un trunk SIP avec Asterisk et PJSIP pour relier deux serveurs. Idéal pour fournir des trunks SIP à des clients ou connecter des bureaux distants. Il couvre les bases du trunking VoIP, la configuration et la gestion des endpoints SIP (utilisateurs finaux).

Qu’est-ce qu’Asterisk ?

Logo officiel d’Asterisk chez Digium

Asterisk est un logiciel open-source gratuit pour créer des systèmes de téléphonie VoIP. Développé initialement par Digium (racheté par Sangoma en 2018), il gère appels vocaux, vidéo et messagerie sur IP.

Polyvalent, il va du PBX simple (central téléphonique) au softswitch avancé ou SBC (Session Border Controller) pour gérer les flux médias. Il équipe PBX d’entreprises et centres d’appels complexes.

Personnalisable, Asterisk supporte SIP via le module PJSIP (implémentation maintenue à ce jour), XMPP pour les SMS et SMTP pour les e-mails. Pour un petit FAI, il peut centraliser les appels entre endpoints (téléphones ou clients) et les services PSTN (réseau téléphonique commuté).

Comprendre les trunks en VoIP

Un trunk est une liaison qui transporte plusieurs appels simultanés entre serveurs ou opérateurs.

Autrefois des lignes physiques en E1 (Europe) ou en T1 (USA) commercialisés par des opérateurs comme France Télécom ou AT&T, les trunks sont aujourd’hui virtuels et sur Internet grâce à la VoIP. En l’occurence, cela rend la configuration de trunk avec Asterisk accessible aux entreprises et aux administrateurs désireux d’en apprendre plus.

Sur Asterisk, considère un trunk comme une liaison qui relie votre serveur à un opérateur VoIP (OVH, Telnyx) ou autre client IPBX.

En comparaison à son homologue historique, le trunk VoIP amène des avantage non négligeables :

SIP : le cœur de la VoIP

SIP (pour Session Initiation Protocol, selon la RFC 3261) lance, maintient et arrête les sessions VoIP. Il gère la signalisation ou en d’autres termes : la création d’appels, les transferts, les fins ou encore les messages d’indisponibilité.

Remarque : SIP ne se charge pas des médias (audio et vidéo). C’est RTP qui a cette fonction.

Gestion des médias (audio et vidéo)

Si SIP orchestre les connexions, les médias (voix et vidéo) circulent séparément via RTP (Real-time Transport Protocol) en utilisant UDP.

Lors d’une communication, les requêtes SIP portent des indications quant aux média à utiliser et les terminaux se mettent d’accord sur un codec commun. Lorsqu’un appel est établi avec succès, SIP signale l’emplacement où il faut envoyer les paquets audio.

Asterisk étant un IPBX, il gère des codecs comme G.722 ou Opus pour les communications Internet HD et G.711 (alaw en Europe et ulaw aux USA) pour les appels téléphoniques. Lorsqu’un flux audio transite par Asterisk, il est en capacité de transcoder les médias pour que les terminaux et utilisateurs puissent joindre.

Présentation du lab et mise en oeuvre

Pour imager et vous permettre d’atteindre votre objectif, dans cet article j’ai fait le choix de limiter ce guide à deux instances d’Asterisk.

D’un côté nous aurons l’instance A qui sera l’instance « master«  et qui sera attachée à un trunk SIP opérateur (ITSP), qu’on peut imaginer chez OVH, Orange, SFR ou Twilio ou autre.

De l’autre, nous aurons l’instance B qui sera l’instance « slave«  qui aura un trunk SIP à destination de l’instance master.

L’instance A est donc le fournisseur et l’instance B le client. En somme ce scénario fonctionne aussi si vous êtes une entreprise avec des bureaux distants où vous souhaitez de la téléphonie fixe.

Schéma d’architecture du projet d’interconnexion des instances Asterisk

Configurer un trunk pour interconnecter deux instances Asterisk

Ce guide suppose d’utiliser Asterisk 18+ avec PJSIP. Si ce n’est pas le cas, je vous invite à faire une mise à jour sur les dernières versions qui apportent plus de flexibilité que le driver SIP historique du logiciel.

On suppose que l’instance A possède les numéros de 1000 à 1999 et que l’instance B les numéros de 2000 à 2999. Vous pouvez définir les numéros de votre choix pour votre cas d’usage.

Avant de commencer, on considère que :

Installer Asterisk

Asterisk est disponible dans les dépôts officiels des distribution CentOS (RIP) et Ubuntu.

La première étape est d’installer Asterisk sur les instances Linux. Le paquet est disponible de manière native sur de nombreuses distributions dont Ubuntu.

Les configuration s’ installent le dossier suivant : /etc/asterisk. Les fichiers importants pour notre usage sont pjsip.conf et extensions.conf.

Faire une sauvegarde de la configuration originale :

sudo cp /etc/asterisk/pjsip.conf /etc/asterisk/pjsip.conf.bak

Configurer serveur A : côté fournisseur

Cette partie est à effectuer sur le serveur A qui est le master. Nous allons éditer la configuration PJSIP pour déclarer l’instance B qui sera à même de s’enregistrer et s’identifier pour recevoir et émettre des appels.

Ce serveur est le fournisseur du trunk. Des téléphones peuvent également s’enregistrer sur l’instance A et communiquer entre eux dans la tranche 1000 à 1999 ou joindre la tranche 2000 à 2999 grâce au trunk qui sera créé.

Configurer PJSIP

Définissez les protocoles supportés par Asterisk pour la signalisation et les médias :

; /etc/asterisk/pjsip.conf
; Définition des transports en IPv4 et IPv6

[transport-tcp] 
type=transport ; On définit un type de transport pour la gestion du SIP
protocol=tcp ; Ici uniquement TCP
bind=0.0.0.0:5060 ; On écoute sur toutes les interfaces IPv4 en TCP 5060

[transport-udp6] 
type=transport ; On définit un type de transport pour la gestion du SIP
protocol=udp ; Ici uniquement UDP
bind=[::]:5060 ; Écoute toutes les interfaces IPv6 sur le port 5060

[transport-tcp6] 
type=transport
protocol=tcp
bind=[::]:5060

[transport-udp] 
type=transport ; On définit un type de transport pour la gestion du SIP
protocol=udp ; Ici uniquement UDP
bind=0.0.0.0:5060 ; On écoute sur toutes les interfaces IPv4 sur le port 5060 (standard SIP)

Définissez le client (à savoir le serveur B) :

; /etc/asterisk/pjsip.conf
; Définition du client (serveur B)

[trunk-customer-b]
type=endpoint
; transport=transport-udp ; On peut omettre cette ligne pour être éligible à IPv4 et IPv6 tout aussi bien en UDP qu'en TCP
context=subscriber  ; Contexte ou dialplan auquel sera affecté le client. Voir la suite de cet article.
disallow=all ; Interdit tous les codecs
allow=opus,g722,alaw,ulaw  ; Autoriser un nombre limité de codecs
auth=trunk-customer-b-auth ; Identifiants associés à cet endpoint (utilisateur)
aors=trunk-customer-b ; Adresse d'enregistrement de l'endpoint, nécessaire pour 
direct_media=no  ; Force le passage des médias audio par Asterisk. Utile dans un contexte où le client se trouve derrière un NAT.

Définissez les paramètres d’identification du serveur B :

; /etc/asterisk/pjsip.conf
[trunk-customer-b-auth]
type=auth
auth_type=userpass
username=trunkuser  ; Nom d'utilisateur du client
password=strongpass123 ; Mot de passe
; realm=asterisk ; Optionel et par défaut définit à 'asterisk'

[trunk-customer-b-aor]
type=aor ; AoR = Address of Record / Addresse d'Enregistrement
max_contacts=1 ; Autorise un seul enregistrement de l'instance B
; contact=sip:192.0.2.2:5060  ; A définir sur le client possède une IP fixe et qu'il ne s'enregistre pas sur le serveur A avec la requête REGISTER pour s'identifier. En activant cette ligne l'instance B est considérée comme stateless.

Ces lignes configurent l’instance A comme un fournisseur de trunk SIP avec une identification obligatoire pour le client (instance B). La section auth impose un nom d’utilisateur/mot de passe pour l’authentification PJSIP dans Asterisk, empêchant l’accès non autorisé.

Le fichier complet pour le serveur A est le suivant :

; /etc/asterisk/pjsip.conf
; Définition des transports en IPv4 et IPv6

[transport-tcp] 
type=transport ; On définit un type de transport pour la gestion du SIP
protocol=tcp ; Ici uniquement TCP
bind=0.0.0.0:5060 ; On écoute sur toutes les interfaces IPv4 en TCP 5060

[transport-udp6] 
type=transport ; On définit un type de transport pour la gestion du SIP
protocol=udp ; Ici uniquement UDP
bind=[::]:5060 ; Écoute toutes les interfaces IPv6 sur le port 5060

[transport-tcp6] 
type=transport
protocol=tcp
bind=[::]:5060

[transport-udp] 
type=transport ; On définit un type de transport pour la gestion du SIP
protocol=udp ; Ici uniquement UDP
bind=0.0.0.0:5060 ; On écoute sur toutes les interfaces IPv4 sur le port 5060 (standard SIP)

; Définition du client (serveur B)

[trunk-customer-b]
type=endpoint
; transport=transport-udp ; On peut omettre cette ligne pour être éligible à IPv4 et IPv6 tout aussi bien en UDP qu'en TCP
context=subscriber  ; Contexte ou dialplan auquel sera affecté le client. Voir la suite de cet article.
disallow=all ; Interdit tous les codecs
allow=opus,g722,alaw,ulaw  ; Autoriser un nombre limité de codecs
auth=trunk-customer-b-auth ; Identifiants associés à cet endpoint (utilisateur)
aors=trunk-customer-b ; Adresse d'enregistrement de l'endpoint, nécessaire pour 
direct_media=no  ; Force le passage des médias audio par Asterisk. Utile dans un contexte où le client se trouve derrière un NAT.

[trunk-customer-b-auth]
type=auth
auth_type=userpass
username=trunkuser  ; Nom d'utilisateur du client
password=strongpass123 ; Mot de passe
; realm=asterisk ; Optionel et par défaut définit à 'asterisk'

[trunk-customer-b-aor]
type=aor ; AoR = Address of Record / Addresse d'Enregistrement
max_contacts=1 ; Autorise un seul enregistrement de l'instance B
; contact=sip:192.0.2.2:5060  ; A définir sur le client possède une IP fixe et qu'il ne s'enregistre pas sur le serveur A avec la requête REGISTER pour s'identifier. En activant cette ligne l'instance B est considérée comme stateless.

Configurer le plan de numérotation

Je définis ici un plan de numérotation simple. Mon objectif est de permettre à l’instance B de pouvoir joindre les téléphones de l’instance A et inversement. Sur les deux instances, le trunk sera assigné au contexte subscriber tout comme les téléphones qui y seront enregistrés.

Le plan de numérotation est dans le fichier : /etc/asterisk/extensions.conf.

Créer un contexte et y indiquer les actions à mener pour permettre le routage des appels :

; /etc/asterisk/extensions.conf

[subscriber]

; Routage à destination du serveur B
exten => 2XXX,1,NoOp(Incoming from request in subscriber going to instance B) ; Indique dans les logs un appel à destination du serveur B
same => n,Dial(PJSIP/${EXTEN}@trunk-customer-b) ; Ici on redirge la requête du téléphone à destination de l'instance Asterisk B. 
same => n,Hangup()

; Règle générale valide pour tous les numéros
exten => _X.,1,NoOp(Incoming from request in subscriber context) 
same => n,Dial(PJSIP/${EXTEN},30,tTr)  ; Permet au client d'appeler un utilisateur dans le même contexte avec 5 sonneries (30 secondes) et le suivi de l'état de la sonnerie.
same => n,Hangup()

Ce plan de numérotation fait deux choses : la première redirger les numéros de la tranche 2000 à 2999 vers l’instance B et la seconde, contacter les téléphones localement enregistrés.

Configuration du serveur B : côté client

Le serveur B est celui sous la responsabilité par le client. Cette instance Asterisk s’enregistre sur le serveur A qui est le fournisseur du trunk. En somme des téléphones peuvent également s’enregistrer sur l’instance B pour communiquer entre eux (2000 à 2999) ou pour passer des appels à destination de l’instance A (1000 à 1999) voir à l’extérieur grâce au trunk opérateur de l’instance A.

Configurer PJSIP

A comme client trunk. Dans /etc/asterisk/pjsip.conf :

; /etc/asterisk/pjsip.conf
; Définition des transports en IPv4 et IPv6

[transport-tcp] 
type=transport ; On définit un type de transport pour la gestion du SIP
protocol=tcp ; Ici uniquement TCP
bind=0.0.0.0:5060 ; On écoute sur toutes les interfaces IPv4 en TCP 5060

[transport-udp6] 
type=transport ; On définit un type de transport pour la gestion du SIP
protocol=udp ; Ici uniquement UDP
bind=[::]:5060 ; Ecoute toutes les interfaces IPv6 sur le port 5060

[transport-tcp6] 
type=transport
protocol=tcp
bind=[::]:5060

[transport-udp] 
type=transport ; On définit un type de transport pour la gestion du SIP
protocol=udp ; Ici uniquement UDP
bind=0.0.0.0:5060 ; On écoute sur toutes les interfaces IPv4 sur le port 5060 (standard SIP)

; Définition du serveur A : fournisseur du trunk

[trunk-isp-a]
type=endpoint
; transport=transport-udp ; Ouvert à tous les transports
context=from-instance-a  ; Context for calls from Server A
disallow=all
allow=opus,g722,alaw,ulaw
auth=trunk-isp-a-auth
aors=trunk-isp-a-aor
outbound_auth=trunk-isp-a-auth
; from_user=trunkuser
; from_domain=192.0.2.1

[trunk-isp-a-auth]
type=auth
auth_type=userpass
username=trunkuser
password=strongpass123
; realm=192.0.2.1

[trunk-isp-a-aor]
type=aor
max_contacts=1
; contact=sip:192.0.2.1:5060

Le serveur B s’authentifie auprès du Serveur A, activant de fait trunking VoIP bidirectionnel.

Configurer le plan de numérotation

Voici le plan de numérotation :

; /etc/asterisk/extensions.conf

; Pour les téléphones internes
[internal]

; Routage à destination du serveur A
exten => 1XXX,1,NoOp(Incoming from request in internal) ; Indique dans les logs un appel du contexte internal. Routage interne sans intervention du trunk

same => n,Dial(PJSIP/${EXTEN}@trunk-isp-a) ; Ici on redirge la requête à destination de l'instance A
same => n,Hangup()

exten => _X.,1,NoOp(Incoming from request in subscriber context) 
same => n,Dial(PJSIP/${EXTEN},30,tTr)  ; Permet au client d'appeler un utilisateur dans le même contexte avec 5 sonneries (30 secondes) et le suivi de l'état de la sonnerie.
same => n,Hangup()

; Pour les appels entrants depuis le trunk
[from-instance-a] 
exten => _X.,1,NoOp(Incoming from ISP trunk) 
same => n,Dial(PJSIP/${EXTEN}@internal,30,tTr)  ; Redirige les appels vers les téléphones du contexte internes
same => n,Hangup()

Valider le bon fonctionnement

Maintenant que les deux instances sont configurées, testez le service en enregistrant des téléphones (ou softphones) sur les deux instances et essayez de passer des appels aux numéros respectifs que vous aurez créé.

Une autre façon de tester le bon fonctionnement est de faire sonner un téléphone en utilisant la CLI d’Asterisk. Pour cela, lancez Asterisk en mode CLI : sudo asterisk -rvvv

Pour voir les téléphones, clients et partenaires et trunks enregistrés :

> pjsip show endpoints

Lancer un appel vers un poste local sur l’instance A :

channel originate PJSIP/1000@subscriber application Playback hello-world

Exécuter un appel vers un poste local sur l’instance B :

channel originate PJSIP/2000@internal application Playback hello-world

Exécuter un appel au travers du trunk depuis l’instance A :

channel originate PJSIP/2000@trunk-customer-b application Playback hello-world

Exécuter un appel au travers du trunk depuis l’instance B :

channel originate PJSIP/1000@trunk-isp-a application Playback hello-world

Le mot de la fin

Ce guide quoi qu’un peu complexe, livre un solution pour interconnecter deux instances Asterisk. L’interconnexion est pour le moment statique, cependant on peut imaginer dans le futur y ajouter une ligne et une API (en Go ou en Python) qui permettrait de créer à la volée de configuration trunks dynamiques.

Quitter la version mobile