I. Aperçu du protocole SIP (Session Initiation Protocol)

I-A. Principes du protocole SIP

Avertissement

Il convient d'abord de bien mettre en avant que SIP n'est pas un protocole transportant de la voix. SIP est utilisé lors de l'établissement de connection (téléphonique, mais peut être utilisé pour d'autres types de connections), qui elles, véhiculeront de la voix, de la vidéo, ou de la messagerie instantannée...

SIP a été conçu dans l'idée de fournir une interface de connection à partir d'identifiants exogènes (numéros de téléphone, adresse email, etc), sur un protocole de transport ne supportant pas de tels identifiants (TCP, UDP ou SCTP, sur IP). Par exemple, un procédé de type ENUM [1] basé sur les enregistrements DNS NAPTR [2] peut associer au numéro de téléphone appelé 6.6.3.4.0.2.4.1.6.3.3 les enregistrements suivants :

Image non disponible

indiquant que la personne pointée par cet identifiant est joignable de préférence par SIP à l'URI sip:falco@falco.bz (nous verrons plus loin comment est routé l'appel), puis par mail à falco@falco.bz ou par téléphone au numéro « usuel ».

SIP est donc un protocole de couche applicative, véhiculé essentiellement sur l'UDP et parfois sur le TCP (dans ce cas, souvent sur du TLS), selon les politiques de sécurité en vigueur. D'autres protocoles de transport tels le SCTP peuvent tout-à-fait être utilisés.

Le client SIP, pour initier une connection ToIP (ou autre) avec un correspondant identifié par, par exemple, un numéro de téléphone, va effectuer une requête au serveur SIP qui donnera la disponibilité du correspondant et alertera le correspondant, le cas échéant. La communication de la voix ou de la vidéo, quant à elle, sera véhiculée indépendamment de SIP, une fois la connection point à point établie à travers internet (IP) entre les deux correspondants, en général en RTP.

I-B. Format d'un message SIP

SIP a fait l'objet de plusieurs publications dans les RFC, à commencer par un draft en 1997. La première véritable RFC ayant vu le jour sur SIP est la RFC 2543 [3] en avril 1999. Celle-ci a été remplacée en juillet 2000 par la RFC 3261 [4], qui apporte plusieurs extensions au protocole. L'ouvrage Understanding the SIP de Johnston [5] fournit une documentation complète sur l'état de l'art actuel.

SIP est un protocole texte. Pour celui qui connaît déjà les protocoles HTTP ou SMTP, le protocole SIP sera très aisé à assimiler. Le message SIP se compose d'une commande (équivalente aux GET/POST HTTP), d'une entête (équivalente aux entêtes SMTP ou HTTP) et d'un corps de message optionnel. Les commandes, entêtes et corps sont séparés comme en SMTP par des retours-chariots (« CR » « LF », x0D x0A), une ligne vide sépare les entêtes du corps de message, et la fin d'un message est matérialisée par une ligne vide également, terminée par un retour-chariot (ce qui prend la force de x0D x0A x0D x0A).

On classe les commandes en deux types, les demandes (request) et le statut (status).

I-C. Requests :

Les lignes de commande se composent de la méthode à exécuter, suivie d'un espace, puis suivie d'une suite d'arguments dépendants de cette commande. Les commandes les plus fréquentes (du moins celles que nous utiliserons ici) sont REGISTER, INVITE, ACCEPT, BYE, et ACK.

La méthode REGISTER est suivie de l'URI représentant serveur auquel le client SIP souhaite s'enregistrer (lancement de l'application, branchement du téléphone, etc). Ensuite vient le protocole utilisé, comme en HTTP. Ce qui donne par exemple :

 
Sélectionnez

REGISTER sip:falco.bz SIP/2.0

Les autres méthodes sont suivies de l'URI représentant le correspondant que l'on souhaite contacter :

 
Sélectionnez

INVITE sip:martins@enst.fr SIP/2.0

I-D. Statuts :

Les statuts sont identifiés par la présence d'une ligne de commande commençant par le numéro de version du protocole (SIP/2.0). Vient ensuite un code numérique d'état équivalent au SMTP, suivi d'un court texte précisant la signification du code :

1xx = Opération normale

2xx = OK

3xx = Continuer...

4xx = Erreur temporaire

5xx = Erreur permanente

6xx = Erreur impérative (refus) (non utilisé en SMTP)

On trouvera donc très souvent :

 
Sélectionnez

SIP/2.0 100 trying (...)
SIP/2.0 200 OK
SIP/2.0 401 Unauthorized
SIP/2.0 404 Not Found

I-E. Entêtes particulières

Le format texte des entêtes permet une compréhension aisée de leur signification. Citons néanmoins les entêtes suivantes :

Cseq: identifie la transaction question-réponse en cours (il peut y avoir plus de deux messages avec le même Cseq: , dans le cas de messages 1xx de continuation). Rappelons que SIP est souvent véhiculé sur UDP et dans ce cas, il n'y a pas de suivi de connection, et rien ne garantit l'ordonnancement des paquets à l'arrivée.

Via: indique par quelle(s) machine(s) (et avec quels protocoles de transport) est déjà passé le paquet SIP, nous nous en servirons avec les routeurs SIP. C'est l'équivalent du Received: de SMTP. Les entêtes Via: sont ajoutées à chaque fois qu'un routeur transmet une requête. Lors de la génération d'un message de réponse à une transaction, toutes les entêtes Via: correspondant à la transaction sont initialement écrites, et elles sont peu à peu dépilées au passage de chaque routeur SIP. Ceci permet la transmission de la réponse SIP aux routeurs successifs ayant transmis précédemment la requête, mais bien sûr dans l'ordre inverse. Le client ayant initié la requête ne reçoit donc plus qu'une seule entête Via: : c'est celle qu'il avait précédemment inscrite lors de la génération de la requête initiale.

Record-Route: est légèrement différent de Via: . Son rôle est de mémoriser les machines et les protocoles de transport par lesquels passe la transaction SIP identifiée par Cseq: . Il est défini une fois pour toutes lors du premier échange de message et figure ensuite dans tous les messages SIP de la transaction, et est inchangé entre chaque routeur. Il n'est pas dépilé et permet donc au client ayant initié la connection de connaître l'ensemble des routeurs sur le trajet du paquet SIP.

From: , To: , Call-ID: , User-Agent: , sont les équivalents respectifs des From:, To:, Message-ID: , et X-Mailer: en SMTP. Call-ID: permet de suivre une « session » SIP tout au long des messages échangés.

Lorsqu'une réponse est reçue correspondant à une session expirée ou inexistante, le serveur renvoie l'erreur :

 
Sélectionnez

481 Call Leg/Transaction Does Not Exist

Les autres champs sont souvent facultatifs, spécifiques à certaines méthodes, ou explicites (comme Content-Length: ) .

II. Déroulement d'une procédure typique d'utilisation de SIP

II-A. Enregistrement d'un client SIP

Avant de pouvoir recevoir un appel, il faut s'enregistrer auprès d'un serveur SIP qui gère son domaine. Cette opération n'est pas nécessaire lorsque l'on souhaite se comporter en tant que client.

Voyons ici les messages échangés lors d'un enregistrement avec demande d'authentification.

Nous avons un client SIP qui cherche à contacter le serveur SIP responsable du domaine enst.fr . Prenons un cas simple où il n'utilise pas de proxy SIP. Supposons qu'il soit configuré pour essayer le TCP par défaut, puis l'UDP en cas d'échec. Supposons que le TCP va échouer. Notons que dans le cas général, les clients SIP sont configurés pour utiliser l'UDP en premier, et échouer définitivement en cas d'échec.

Lorsque le client est lancé, il écoute sur la machine cliente sur un port non privilégié (proche de 5060 en général) en UDP, et en TCP si l'option TCP a été choisie.

Le client recherche d'abord l'enregistrement DNS de type SRV pour _sip._tcp.enst.fr, qui comporte une ou plusieurs entrées textes avec pour chacune une priorité, une limite de charge indicative, et un port. La RFC 2052 [6] ainsi que la documentation de SER [7] détaillent l'utilisation de l'enregistrement SRV.

Dans notre exemple, le client SIP trouve une seule entrée : sipmob.enst.fr 0 0 5060 . Il va donc contacter en TCP le serveur sipmob sur son port 5060 (il s'agit du port usuel pour SIP).

S'il n'avait pas trouvé d'entrée DNS SRV, le client SIP aurait utilisé les enregistrements A associés au nom enst.fr , s'ils existent.

En cas d'échec sur le TCP, le client recommence avec _sip._udp.enst.fr et en UDP.

 
Sélectionnez

REGISTER sip:enst.fr SIP/2.0 \
Via: SIP/2.0/TCP 137.194.192.139:5062;branch=z9hG4bK5A198141;alias
CSeq: 4691 REGISTER
To: "Philippe Martins" <sip:martins@enst.fr>
Expires: 900
From: "Philippe Martins" <sip:martins@enst.fr>
Call-ID: 275712798@137.194.192.139
Content-Length: 0
User-Agent: kphone/4.1.1
Event: registration
Allow-Events: presence
Contact: "Philippe Martins" <sip:martins@137.194.192.139:5062;transport=
tcp>;methods="INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK,
REFER"
 
Sélectionnez

SIP/2.0 401 Unauthorized
Via: SIP/2.0/TCP 137.194.192.139:5062;branch=z9hG4bK5A198141;alias
CSeq: 4691 REGISTER
To: "Philippe Martins"
<sip:martins@enst.fr>;tag=f4cfb6052c9942946d4f26d8a5b79cad.6a20
From: "Philippe Martins" <sip:martins@enst.fr>
Call-ID: 275712798@137.194.192.139
WWW-Authenticate: Digest realm="enst.fr",
nonce="4399c690c4ad8ca5cc6b9d5a01989f5d1986dd42"
Server: Sip EXpress router (0.9.4 (i386/linux))
Content-Length: 0
Warning: 392 137.194.192.139:5060 "Noisy feedback tells: pid=12818 req_src_ip=
137.194.192.139 req_src_port=4471 in_uri=sip:enst.fr out_uri=sip:enst.fr
via_cnt==1"
 
Sélectionnez

REGISTER sip:enst.fr SIP/2.0
Via: SIP/2.0/TCP 137.194.192.139:5062;branch=z9hG4bK3B422852;alias
CSeq: 4692 REGISTER
To: "Philippe Martins" <sip:martins@enst.fr>
Authorization: Digest username="martins", realm="enst.fr",
nonce="4399c690c4ad8ca5cc6b9d5a01989f5d1986dd42", uri="sip:enst.fr",
cnonce="abcdefghi", nc=00000001, response="87ab3284e121e6b8cc862dc676ecc2f7",
opaque="", algorithm="MD5"
Expires: 900
From: "Philippe Martins" <sip:martins@enst.fr>
Call-ID: 275712798@137.194.192.139
Content-Length: 0
User-Agent: kphone/4.1.1
Event: registration
Allow-Events: presence
Contact: "Philippe Martins" <sip:martins@137.194.192.139:5062;transport=
tcp>;methods="INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK,
REFER"
 
Sélectionnez

SIP/2.0 200 OK
Via: SIP/2.0/TCP 137.194.192.139:5062;branch=z9hG4bK3B422852;alias
CSeq: 4692 REGISTER
To: "Philippe Martins"
<sip:martins@enst.fr>;tag=f4cfb6052c9942946d4f26d8a5b79cad.1ef8
From: "Philippe Martins" <sip:martins@enst.fr>
Call-ID: 275712798@137.194.192.139
Contact: <sip:martins@137.194.192.139:5062;transport=tcp>;expires=900
Server: Sip EXpress router (0.9.4 (i386/linux))
Content-Length: 0
Warning: 392 137.194.192.139:5060 "Noisy feedback tells: pid=12818 req_src_ip=
137.194.192.139 req_src_port=4471 in_uri=sip:enst.fr out_uri=sip:enst.fr
via_cnt==1"

Pourquoi deux allers-retours ?

La première fois, le client demande à s'enregistrer auprès du serveur en tant que martins@enst.fr . Le serveur est configuré pour n'accepter que les utilisateurs authentifiés par un couple login / mot de passe. L'algorithme utilisé ici est de type challenge-response de manière à ne pas pouvoir rejouer la connection a posteriori : le serveur choisi son challenge et l'envoie au client en même temps que la réponse d'erreur temporaire 401. Le serveur envoie également le type d'authentification souhaitée

A cette étape, le client demande à l'utilisateur un mot de passe, puis envoie une seconde demande de connection, avec un hash MD5 calculé à partir du challenge, du mot de passe, et du login. Dans notre utilisation de SER, le challenge est simplement un timestamp. Les détails d'implémentation, ainsi que d'autres méthodes basées sur un challengeresponse, font l'objet d'une proposition de RFC [8].

Enfin, cet échange question-réponse sera effectué périodiquement (environ toutes les 900 secondes) afin de s'assurer que le client est toujours en vie.

II-B. Etablissement d'un appel

Dorénavant, une autre personne va tenter d'appeler martins@enst.fr . Que cette personne passe où non par un proxy SIP, c'est le serveur SIP du domaine enst.fr , représenté par l'enregistrement SRV _sip._udp.enst.fr , qui va être contacté. L'appelant n'a pas besoin d'être enregistré sur le serveur, mais le serveur doit savoir qui est martins@enst.fr (c'est-à-dire à quelle adresse IP le joindre).

Nous allons décrire les différents cas de figure possibles selon le comportement de l'utilisateur.

Appel décliné par l'appelé

falco@falco.bz appelle martins@enst.fr Le serveur SIP joue le rôle de proxy pour martins.enst.fr , il sait où contacter l'appelé martins@enst.fr (quit pourrait tout aussi bien se déplacer sur une autre machine). Le serveur SIP réécrit toutes les requetes, complète l'entête Via: , voire d'autres au besoin, et les envoie aux correspondants. Ici le poste appelé annonce qu'il informe l'utilisateur d'un appel entrant (« Ringing »).

Peu de temps après, l'appelé choisit de refuser l'appel (« Decline »).

Le même champ CSeq identifie l'échange depuis le « INVITE » jusqu'au « ACK ».

Image non disponible

Appel décliné par l'appelant

Ici c'est l'appelant qui choisit d'interrompre l'appel en cours d'établissement (« Cancel »).

Appel accepté et terminé par l'appelant Ici, le correspondant accepte l'appel (« OK »), puis l'appelant interrompt l'appel (« BYE ») après une phase de conversation (non représentée, car véhiculée sur un autre protocole).

Le même champ CSeq identifie l'échange depuis le « INVITE » jusqu'au « ACK ».

L'échange « BYE »+ « 200 OK » s'effectue donc sous un autre CSeq.

Image non disponible

Appel accepté et terminé par l'appelé

Enfin, ici c'est l'appelé qui interromp l'appel.

Notons que dans le cas d'un raccrochement après avoir accepté l'appel, le client SIP utilisé ici (kphone) ouvre un nouveau socket réseau pour communiquer (ce n'est pas le même CSeq, et le précédent socket a été fermé). Lorsque c'est martins@enst.fr qui interromp l'appel, le paquet (UDP ou TCP) est envoyé au routeur SIP sipmob.enst.fr , qui envoie un paquet (UDP ou TCP) à falco.bz . Or le client de falco.bz est derrière un réseau NAT : comme la connection entrant sur le routeur NAT est une connection inconnue, elle n'est pas transmise au client SIP et nous avons donc un problème.

C'est pourquoi kphone (au moins) permet l'utilisation d'un serveur STUN (Simple Traversal of UDP through NATs, voir RFC 3489 [9]).

III. Emploi du serveur SER (SIP Express Routeur)

Introduction

Le serveur SER est un logiciel de iptel [10] , proposé en versions binaires pour plusieurs systèmes Unix ou Linux. Il s'agit d'un service de routage pour le protocole SIP, doublé d'un service d'enregistrement d'utilisateurs et d'authentification. Dans la configuration de base, le routeur effectue de manière transparente le routage d'un message SIP vers les services du type _sip._udp.domain.tld pour le domaine destination (contenu dans la ligne de commande SIP), sauf si bien sûr le domaine destination correspond au domaine du routeur lui-même ou à un alias.

III-A. Installation

Nous l'avons installé sur deux machines Linux, l'une sous la distribution Debian (sarge) et l'autre sous Gentoo. A part pour l'installation, les configurations ne changent pas selon la distribution Linux utilisée.

Sous Gentoo l'installation ne pose aucun problème, SER est disponible dans l'arbre portage. Sous Debian nous avons utilisé les sources de la version 0.9.4 d'après le site iptel.org, pour construire un paquet Debian avec l'outil dpkg-buildpackage (issu du paquet dpkg-dev).

Nous utilisons SER avec le support SQL pour l'authentification et pour le suivi des utilisateurs. Dans tous les cas, nous devons donc avoir un serveur SQL (par défaut sur le même serveur que SER, mais il est envisageable de l'exporter sur une autre machine).

De plus, sous debian, comme nous compilons nous-même le paquet, il ne faut pas oublier d'installer les librairies de développement pour mysqlclient (libmysqlclient*-dev). Au pire, dpkg-buildpackage se plaint des librairies manquantes.

Les modules compilés par défaut sont nombreux mais nous ne les utilisons pas tous. En particulier, dpkg-buildpackage se plaint de l'absence des librairies de développement pour le module d'authenfication Raduis et pour le module de base de données PostgreSQL :

 
Sélectionnez

|sipmob:~/sip_router.0.9.4| fakeroot
[sipmob:~/sip_router.0.9.4] dpkg-buildpackage
dpkg-buildpackage: source package is ser
dpkg-buildpackage: source version is 0.9.4-0.1
dpkg-buildpackage: source maintainer is Andrei Pelinescu-Onciul
<andrei@iptel.org>
dpkg-buildpackage: host architecture is i386
dpkg-checkbuilddeps: Unmet build dependencies: libradiusclient-ng-dev
libpq-dev
dpkg-buildpackage: Build dependencies/conflicts unsatisfied; aborting.
dpkg-buildpackage: (Use -d flag to override.)

Ces deux librairies ne sont pas utiles, et n'existent pas en tant que paquets officiels dans la debian Sarge, mais dans l'unstable. Comme nous n'utilisons ni Radius ni PostGre, ce n'est pas gênant.

Si d'autres librairies indispensables manquent, il faut les installer.

On compile donc les paquets Debian avec l'option -d. Cela fournit plusieurs paquets .deb dans le répertoire en amont, on peut tous les installer à l'aide de la commande « dpkg -i ». Les éventuels problèmes de dépendances manquantes sont signalés par dpkg.

Installation de la structure SQL.

Le paquet SER fournit un très bon script /usr/sbin/ser_mysql.sh, permettant d'initialiser la base « ser » et les utilisateurs « ser » et « serro » (read-only), qui demande le mot de passe root SQL. Sur des configurations originales, par exemple si l'utilisateur racine de SQL ne s'appelle pas « root » et « admin », ou bien si le serveur SQL n'est pas sur localhost, il faut au préalable modifier les premières ligne du script /usr/sbin/ser_mysql.sh pour modifier les variables d'environnement.

Dans un environnement de production, il est également vivement préférable de modifier les mots de passe SQL.

 
Sélectionnez

#################################################################
# config vars
#################################################################
DBNAME=ser
DBHOST=localhost sqlserv.domain.tld
USERNAME=ser sip-user
DEFAULT_PW=heslo sIp4l1fe
ROUSER=serro sip-readonly
RO_PW=47serro11 st0pH3re
SQL_USER="root" "admin"
CMD="mysql -h $DBHOST -u$SQL_USER "
DUMP_CMD="mysqldump -h $DBHOST -u$SQL_USER -c -t "
BACKUP_CMD="mysqldump -h $DBHOST -u$SQL_USER -c "
TABLE_TYPE="TYPE=MyISAM

Important : il faut au préalable intialiser la variable d'environnement SIP_DOMAIN à la valeur du domaine qui sera utilisé (ou bien la spécifier manuellement au début du script).

 
Sélectionnez

[sipmob:~] export SIP_DOMAIN="enst.fr"
[sipmob:~]ser_mysql.sh
usage: ser_mysql.sh create
ser_mysql.sh drop (!!entirely deletes tables)
ser_mysql.sh reinit (!!entirely deletes and than re-creates tables
ser_mysql.sh backup (dumps current database to stdout)
ser_mysql.sh restore <file> (restores tables from a file)
ser_mysql.sh copy <new_db> (creates a new db from an existing one)
ser_mysql.sh reinstall (updates to a new SER database)
if you want to manipulate database as other MySql user than
root, want to change database name from default value "ser",
or want to use other values for users and password, edit the
"config vars" section of the command ser_mysql.sh
[sipmob:~] ser_mysql.sh create
MySql password for root:

Notes :

Pour une nouvelle installation de SQL, rappelons que la plupart des implémentations de SQL fournissent un script (réservé à l'utilisateur root unix) permettant d'initialiser la base « mysql » contenant un premier utilisateur aux droits d'administrateur et son mot de passe. Il est en pratique impossible de « deviner » le mot de passe administrateur SQL si on l'a oublié, même en ayant accès en lecture à la table mysql.user . En étant root sur une machine, il faut donc redéfinir un nouveau mot de passe administrateur SQL, en lançant mysqld avec l'option -skip-grant-tables.

Entre les version de SIP (par exemple entre 0.8 et 0.9), il y a souvent des différences dans la structure des tables SQL, il peut être donc utile de les regénérer lors d'une mise à jour.

III-B. Configuration de SER

Pour toute la documentation relative au fichier ser.cfg, se référer à : http://www.iptel.org/ser/doc/seruser/seruser.html

SER n'utilise qu'un seul fichier de configuration : /etc/ser/ser.cfg

Les sections à modifier sont décrites ci-après :

 
Sélectionnez

# ----------- global configuration parameters ------------------------
debug=6 # debug level (cmd line: -dddddddddd)
#fork=yes
#log_stderror=no # (cmd line: -E)
/* Uncomment these lines to enter debugging mode
fork=yes
log_stderror=yes
*/
check_via=no # (cmd. line: -v)
dns=yes # (cmd. line: -r)
rev_dns=yes # (cmd. line: -R)
#port=5060
#children=4

debug=6 (ou 5) est une valeur un peu élevée, mais nous la conseillons dans un premier temps, hors de tout contexte de production. Attention, si un client reste connecté, la taille des logs peut exploser jusqu'à plus de 100Mo par jour. debug=4 est préférable dans un premier temps.

fork (defaut : yes), comme son nom l'indique, demande à SER de forker au démarrage pour proposer un pool de processus prêts à recevoir des clients. Théoriquement, cela n'a d'intérêt que dans un contexte massivement multi-utilisateur.

Cependant, ce paramètre est nécessaire lorsque l'on veut que SER écoute sur plusieurs adresses IP (l'interface « * » correspondant au bind sur 0.0.0.0 est considérée comme une seule interface et ne demande l'utilisation que d'un seul processus). De plus, nous avons testé la configuration fork=no en demandant à SER d'écouter sur 0.0.0.0 (ou sur n'importe quelle adresse IP unique), et dans certains cas, un appel d'un client SIP provoque l'émission par SER d'une erreur « 500 I'm terribly sorry, server error occurred (7/TM) ».

Le syslog montre «ser: ERROR: add_uac: can't fwd to af 2, proto 2 (no corresponding listening socket) ». Enfin lorsque nous démarrons SER avec le script usuel /etc/init.d/ser , le processus ne se détache pas correctement de la console, et le script ne permet pas d'arrêter le serveur (il faut faire un « killall ser »).

Il semble donc, d'après tout cela, que SER ne soit pas adapté à une utilisation sans processus forkés.

Nous recommandons donc vivement de laisser cette valeur à « fork=yes », et nous faisons écouter SER sur les adresses IP suivantes :

localhost (127.0.0.1); la ou les adresse(s) IP associée(s) à la machine; l'adresse IP multicast SIP.MCAST.NET (224.0.1.75).

check_via (par défaut, égal à « no ») : ce paramètre est censé indiquer au routeur SER de tenir compte de l'entête Via: lors de la retransmission des réponses. Il ne permet pas de résoudre le problème (évoqué plus haut) d'un client se connectant derrière un réseau NAT, mais permet de router la réponse au bon routeur SER lorsqu'un message SIP traverse plusieurs routeurs SER, même s'il ne s'agit pas de la route la plus directe.

 
Sélectionnez

################ pour l'auth sql
fifo="/tmp/ser_fifo"
#fifo_mode=0666
fifo_db_url="mysql://ser:heslo@localhost/ser"
#########################

Ces lignes servent à communiquer avec le module SQL.

 
Sélectionnez

#listen=0.0.0.0
listen=137.194.192.139
listen=224.0.1.75
listen=127.0.0.1
alias=sipmob.enst.fr
alias=enst.fr
alias=sip.mcast.net

listen correspond aux IP sur lesquelles SER va écouter (s'il y a plusieurs « listen », il faut activer l'option « fork »). Par défaut, SER écoute sur toutes les interfaces activées. On peut y mettre un nom DNS (qui sera résolu au démarrage du serveur) ou une adresse IP, au choix. alias correspond, à l'instar du paramètre mydestination de Postfix, aux domaines à considérer comme locaux, pour lesquels le serveur va répondre lui-même au lieu de transmettre au prochain routeur.

 
Sélectionnez

# ------------------ module loading ----------------------------------
# Uncomment this if you want to use SQL database
loadmodule "/usr/lib/ser/modules/mysql.so"
loadmodule "/usr/lib/ser/modules/pa.so"
loadmodule "/usr/lib/ser/modules/sl.so"
loadmodule "/usr/lib/ser/modules/tm.so"
loadmodule "/usr/lib/ser/modules/rr.so"
loadmodule "/usr/lib/ser/modules/maxfwd.so"
loadmodule "/usr/lib/ser/modules/usrloc.so"
loadmodule "/usr/lib/ser/modules/registrar.so"
loadmodule "/usr/lib/ser/modules/textops.so"
# Uncomment this if you want digest authentication
# mysql.so must be loaded !
loadmodule "/usr/lib/ser/modules/auth.so"
loadmodule "/usr/lib/ser/modules/auth_db.so"

Pour effectuer une authentification sur la base SQL, il faut décommenter les lignes correspondant à mysql.so, auth.so et auth_db.so . La plupart des autres modules sont rarement utilisés. Mais s'ils sont décommentés, il faut qu'ils soient au préalable installés.

 
Sélectionnez

# ----------------- setting module-specific parameters ---------------
# -- usrloc params --
# Uncomment this if you want to use SQL database
# for persistent storage and comment the previous line
modparam("usrloc", "db_mode", 1)
###modparam("usrloc", "db_mode", 2)

Ce paramètre prend trois valeurs entre 0 et 2 [11], et définit le comportement du serveur lors de l'enregistrement dans la base SQL (table nommée « location ») des utilisateurs connectés.

 
Sélectionnez

modparam("usrloc", "db_mode", 0)

N'enregistre aucune information dans la base SQL. La liste des clients connectés se trouve dans la mémoire de SER, elle est perdue si le serveur redémarre.

 
Sélectionnez

modparam("usrloc", "db_mode", 1)	

Enregistre en temps réel les connections, déconnections, et changements d'état des clients. Cette option gourmande en ressource est à éviter dans des contextes massivement multiutilisateurs si le serveur tient mal la charge. Elle est idéale dans les phases de développement. On peut redémarrer le serveur sans perdre les informations sur les clients connectés et donc de manière transparente pour les utilisateurs.

 
Sélectionnez

modparam("usrloc", "db_mode", 2)

Enregistre périodiquement dans la table SQL le contenu de la mémoire de SER relatif aux clients connectés. Il peut y avoir des pertes en cas de redémarrage du serveur SER.

 
Sélectionnez

# -- auth params --
# Uncomment if you are using auth module
#
modparam("auth_db", "calculate_ha1", yes)
#
# If you set "calculate_ha1" parameter to yes (which true in this config),
# uncomment also the following parameter)
#
modparam("auth_db", "password_column", "password")

Il faut enfin décommenter les paramètres relatifs à l'authentification.

Le reste du fichier de configuration est celui fourni par défaut avec le logiciel. La suite concerne l'algorithme de routage : on peut forcer une décision de routage sur un critère (nom du domaine, entête particulière...) ou ajouter une entête au besoin (notamment « Record-Route: ». Il ne reste plus qu'à modifier la valeur du domaine d'exemple « iptel.org » par notre domaine (par exemple, « enst.fr »).

 
Sélectionnez

if (!www_authorize("enst.fr", "subscriber")) {
www_challenge("enst.fr", "0");
break;
};

On constate que ce bloc teste l'existence et la validité de l'entête www_authorize (qui correspond au challenge-response d'authentification), et envoie un challenge si le client n'est pas authentifié.

Lancement du serveur

Avec un paquet Debian ainsi compilé, le serveur SER se lance via le script de démarrage standard : « /etc/init.d/ser start », comme sur la plupart des distributions Linux. Il y a parfois (sous Debian) des problèmes lors de l'arrêt du serveur, au besoin il faut faire un « killall ser » et retirer manuellement le pipe dans /tmp/ser_fifo pour permettre au serveur de se lancer correctement à nouveau.

Ajout d'utilisateurs avec mot de passe

On peut soit utiliser directement la table ser.subscriber, soit utiliser l'utilitaire serctl :

add <username> <password> <email> .. add a new subscriber (*)

Par exemple

 
Sélectionnez

$ serctl add falco toto falco@falco.bz

IV. Mise en relation de deux platesformes

IV-A. Utilisation usuelle

IV-A-1. Présentation du réseau de travail

Nous avons testé le cas de deux serveurs SER avec deux ou plusieurs clients, dans des conditions volontairement différentes :

  • Un des deux réseaux est un réseau NAT.
  • Un routeur SER est sur Gentoo et l'autre sur Debian
  • Un routeur SER est issu de la DNS _sip._udp.domain.tld et l'autre ne l'est pas, c'est donc la machine domain.tld qui est contactée.
  • Un client SIP est falco@falco.bz sur la machine ganesh.falco.bz, l'autre est martins@enst.fr : noter l'absence de relation entre l'URI (« sip:falco@falco.bz ») et le nom de machine (ganesh.falco.bz). Le client sur ganesh.falco.bz aurait tout aussi bien pu s'appeler « ganesh@falco.bz ». Cependant, le domaine doit être celui géré par le routeur SER correspondant si on veut obtenir un routage correct sur Internet. Par exemple pour contacter martins@enst.fr, un client sur Internet contactera le serveur SER sipmob.enst.fr, et si martins@enst.fr se trouvait sur le réseau NAT de falco.bz, la communication ne pourra s'établir que si les règles de pare-feu entre sipmob.enst.fr et falco.bz le permettent.
Image non disponible

IV-A-2. Cas simple de l'appel présenté en exemple au premier chapitre

Pour que cela soit possible, il faut que le pare-feu de l'ENST accepte la connection entrante sur sipmob.enst.fr : ça n'est pas le cas pour le port TCP:5060, le client SIP falco@falco.bz doit donc utiliser l'UDP:5060 qui est ouvert. Rappelons que le client SIP kphone peut émettre soit en TCP soit en UDP.

Le routeur SER sipmob peut émettre en UDP aussi bien qu'en TCP, ça n'est pas une limitation du pare-feu.

Image non disponible

Lorsque le Routeur SIP reçoit la requête INVITE, il ouvre un socket (UDP ou TCP, peu importe, mais UDP par défaut) vers martins.enst.fr et transmet le INVITE, en ajoutant son adresse IP dans l'entête « Via: » ainsi que l'IP publique du réseau NAT. La réponse du client SIP est véhiculée sur le même socket dans l'autre sens; idem pour la réponse du routeur sipmob vers le réseau NAT et donc le routeur NAT peut transmettre la réponse à ganesh.falco.bz .

Par contre, nous constatons ici que lorsque le client martins@enst.fr décide par exemple d'interrompre la connection (requête BYE), et qu'il ouvre un nouveau socket directement vers le routeur NAT falco.bz, ceci ne peut fonctionner. Ce routeur NAT ne sait pas qu'il faut transmettre la connection à ganesh.falco.bz ce qui explique le problème du NAT présenté au premier chapitre.

De plus, il peut y avoir une limitation sur le pare-feu de l'ENST, et martins@enst.fr doit donc utiliser sipmob.enst.fr comme « proxy » SIP dans la configuration du client. Dans ce cas, ceci permet de passer le pare-feu de l'ENST, mais ne résoud pas le problème du NAT.

En réalité, avec SER, la situation est encore pire, puisque martin.enst.fr ou sipmob.enst.fr, lorsqu'ils cherchent à ouvrir un socket vers falco@falco.bz, ouvrent un socket vers l'adresse privée de ganesh.falco.bz (192.168.x.x), ce qui, évidemment, ne peut fonctionner.

Ce comportement est étrange, car l'entête Via fournit toutes les indications nécessaires, mais ni SER ni kphone ne semblent en tenir compte.

IV-A-3. Analyse des entêtes V ia: e t R ecord-Route:

Image non disponible

Le routeur sipmob.enst.fr modifie d'abord la première entête Via: en y ajoutant l'adresse IP apparente d'émission (l'adresse IP publique du NAT, 81.56.73.55), puis ajoute une ligne Via:, en amont (comme en SMTP : les entêtes sont ajoutées en tête), correspondant à la retransmission du routeur. Ici, sipmob effectue une connection TCP avec martins.enst.fr .

IV-A-4. Modification de SER pour un routage en TCP

Un client SIP peut communiquer avec SER aussi bien en UDP que en TCP (sous réserve d'acceptation par les politiques de sécurité en vigueur). Dans ce cas, SER répond au client sur le même socket. Lorsque SER initie une connection vers le client, il communique sur le même protocole de transport que celui utilisé par le client. En revanche, lorsqu'il s'agit d'ouvrir une connection avec un autre proxy SER, SER ouvre la connection systématiquement en UDP. Pour certaines raisons, on pourrait parfois s'attendre à ce que, pour router un message SIP, SER utilise le même type de socket que celui duquel il a reçu le message. Même si certains utilisateurs ont soumis l'idée sur les mailing-lists de iptel.org [12] , cela n'a pas été retenu par les développeurs :

At 07:19 PM 9/29/2005, Jan Janak wrote:

>The two Route header fields belong to SER (because it inserted

>corresponding Record-Route header fields), thus SER would only remove

>them -- the two Route header fields will be not used when SER decides

>where to send the request (and what transport to use).

> >

If there are no more Route header fields in the message then SER would

>forward to the URI specified in the Request-URI. If that URI contains no

>transport=tcp parameter then SER will forward over UDP.

> >

That is the explanation of what happens -- but I am not saying that this

>is the correct behavior. SER inserts two Route header field because the

>transport protocol has changed (the request was received over TCP but

>then was forwarded over UDP). In this case SER should probably forward

>the request over TCP regardless of the transport suggested by the

>Request-URI (former Contact). But I have to think about it a bit more.

I beg to disagree. Route specifies how to get to SER (and is two-fold to make sure SER is reachable from both sides), Contact dictates how to get to the next hop. Contacts may even get updated during dialog duration.

Pour forcer SER à utiliser le protocole TCP lors de l'ouverture d'une connection, il faut donc se pencher sur le code source, dans le fichier forward.h (déclaration inline de la fonction msg_send) :

 
Sélectionnez

/* params:
* send_sock= 0 if already known (e.g. for udp in some cases), non-0 otherwise
* proto=TCP|UDP
* to = destination,
* id - only used on tcp, it will force sending on connection "id" if id!=0
* and the connection exists, else it will send to "to"
* (useful for sending replies on the same connection as the request
* that generated them; use 0 if you don't want this)
* returns: 0 if ok, -1 on error*/
static inline int msg_send( struct socket_info* send_sock, int proto,
union sockaddr_union* to, int id,
char* buf, int len)
{
if (proto==PROTO_UDP){
if (send_sock==0) send_sock=get_send_socket(0, to, proto);
if (send_sock==0){
LOG(L_ERR, "msg_send: ERROR: no sending socket found\n");
goto error;
}
if (udp_send(send_sock, buf, len, to)==-1){
STATS_TX_DROPS;
LOG(L_ERR, "msg_send: ERROR: udp_send failed\n");
goto error;
}
}
#ifdef USE_TCP
else if (proto==PROTO_TCP){
if (tcp_disable){
STATS_TX_DROPS;
LOG(L_WARN, "msg_send: WARNING: attempt to send on tcp and tcp"
" support is disabled\n");
goto error;
}else{
if (tcp_send(proto, buf, len, to, id)<0){
STATS_TX_DROPS;
LOG(L_ERR, "msg_send: ERROR: tcp_send failed\n");
goto error;
}
}
}
#ifdef USE_TLS
else if (proto==PROTO_TLS){
if (tls_disable){
STATS_TX_DROPS;
LOG(L_WARN, "msg_send: WARNING: attempt to send on tls and tls"
" support is disabled\n");
goto error;
}else{
if (tcp_send(proto, buf, len, to, id)<0){
STATS_TX_DROPS;
LOG(L_ERR, "msg_send: ERROR: tcp_send failed\n");
goto error;
}
}
}
#endif /* USE_TLS */
#endif /* USE_TCP */
else{
LOG(L_CRIT, "BUG: msg_send: unknown proto %d\n", proto);
goto error;
}
return 0;
error:
return -1;
}

Si l'on souhaite que la connection soit ouverte sur un socket TCP, nous modifions les conditions de manière que le bloc "if (proto==PROTO_TCP){}" soit toujours exécuté. Il est très vraisemblable qu'il existe un paramètre à modifier dans la section route{} du fichier de configuration ser.cfg (cela permettrait par ailleurs de forcer le protocole TCP sous certaines conditions, comme l'adresse du destinataire). Cependant nous n'avons rien trouvé correspondant à cela, que ce soit dans les exemples ou dans le code source : un examen plus attentif du code source s'impose donc.

IV-A-5. Utilisation de proxies SIP

Ici, les deux clients SIP ont configuré un proxy SIP. Cela peut s'avérer particulièrement utile dans le cas de réseaux fortement firewallés, où seule la passerelle SIP a accès à l'extérieur. De plus, dans notre exemple, nous supposerons pour augmenter la difficulté, que sipmob.enst.fr est uniquement accessible en UDP sur le port 5060, et qu'il ne peut pas initier de connections sortantes en UDP (ce qui était le cas encore il y a peu) : il devra donc émettre en TCP comme expliqué au paragraphe précédent.

Image non disponible

Dans ce cadre de fonctionnement, nous pouvons constater la « portée » de la variable CSeq : une réponse 1xx ne termine pas la transaction SIP, au contraire d'une réponse 4xx ou à plus forte raison 5xx, et même d'une réponse 200 OK (qui elle, peut demander un acquittement).

La transaction BYE - 200 OK fait donc l'object d'un nouveau CSeq (mais d'un même Call-ID , puisqu'il s'agit du même appel en cours).

On peut également observer que les sessions TCP sont réutilisées pour de nouveaux messages SIP. Nous verrons ensuite que ce n'est pas toujours le cas avec l'utilisation du client kphone.

Pourquoi martins@enst.fr transmet-il en TCP ? Cela demande une explication.

Nous avons vu comment forcer le serveur SER sipmob.enst.fr à émettre uniquement en TCP. Seulement, cette obligation est valable aussi bien vers l'extérieur du réseau de l'ENST que vers l'intérieur ou même en localhost. Avec la modification du code source indiquée au paragraphe précédent, SER ne peut pas réutiliser un socket UDP ouvert et parle donc au client martins@enst.fr en TCP.

Or, kphone possède deux modes de fonctionnement : l'un en UDP, où il ouvre une écoute sur le port 5060 UDP (ou un autre port si 5060 est déjà pris), et l'autre en TCP, où kphone écoute alors à la fois en UDP et en TCP. Si nous configurons kphone en mode UDP, la réponse à la requête REGISTER lui arrivera en TCP et kphone ne pourra la récupérer. Nous devons donc configurer kphone en TCP, ce qui a pour effet que lui-même émettra également en TCP.

Du côté d'Internet entre falco.bz et sipmob.enst.fr , la connection est initiée par sipmob.enst.fr donc en TCP, et l'ensemble de la transaction se déroule sur la même connection TCP. Nous verrons ensuite que ce n'est pas le cas si la connection est initiée par falco.bz .

Cas où l'appel est initié par falco.bz

Image non disponible

Nous observons ici plusieurs comportements qui diffèrent de l'exemple précédent. Tout d'abord, falco.bz initie une connection UDP vers sipmob.enst.fr . Comme nous avons supposé que le pare-feu de l'ENST ne laisse pas sortir l'UDP, nous avons configuré sipmob.enst.fr pour qu'il réponde en TCP. En effet, lors de nos tests précédant l'ouverture du pare-feu pour l'UDP, nous avons constaté qu'une réponse à une connection UDP déjà établie (mêmes couples IP et ports source/destination) était bloquée par le pare-feu. En d'autres termes, le pare-feu de l'ENST n'effectue pas de « suivi de connection » sur l'UDP (si tant est que l'on puisse parler de connection en UDP). Par exemple, le pare-feu du noyau linux (netfilter, commandé par l'utilitaire iptables), qui est installé sur falco.bz, est capable d'assurer un suivi de l'UDP et de laisser passer la réponse.

La réponse de sipmob.enst.fr au INVITE de falco.bz ouvre donc une nouvelle connection TCP qui sera utilisée jusqu'au bout. Etrangement, le SER installé sur falco.bz n'utilise pas cette connection TCP pour émettre le ACK (qu'il émet en UDP), mais par contre il l'utilise pour le « 200 OK ».

L'autre comportement étrange est celui du client kphone qui initie une nouvelle connection TCP à chaque nouveau paquet SIP émis, même au sein d'une même transaction SIP. Il est donc manifeste que ce logiciel est peu adapté à l'utilisation du TCP et que des améliorations pourraient être effectuées afin de miniser la consommation de ressources.

Voyons enfin comment les routeurs SER traitent les entêtes Via: et Record-Route: dont nous avons parlé au premier chapitre.

IV-A-6. Quelques résolutions de problèmes sur un réseau réel

Nous présentons ici les limitations imposées par notre réseau, les problèmes qui en résultent, leurs effets pour l'utilisateur ainsi que les solutions permettant de les contourner.

Comme précédemment, nous considérons le schéma de deux clients SIP que nous nommerons ici A et B, A se trouvant derrière un routeur NAT.

Image non disponible
Image non disponible
Image non disponible

IV-A-7. Utilisation des entêtes V ia: e t R ecord-Route:

IV-A-7-a. Enregistrement de la route : émission de la requête

Image non disponible

Comme nous l'avions indiqué au premier chapitre, les entêtes Via: et Record-Route: sont complétées lors du passage de chaque machine SIP. On y trouve également le nom du protocole de transport utilisé (TCP ou UDP). On y trouve aussi une valeur « branch » qui est utilisée pour différencier les lignes même si l'IP est identique (nous avons effectivement pu observer un cas de boucle sur localhost, où les entêtes Via: ne différaient que par le suffixe de « branch »). L'algorithme exact de la gestion de ces entêtes est un peu complexe et nous invitons le lecteur à consulter les RFC de SIP ou la documention sur www.iptel.org/ser/ .

Notons toutefois de quelle façon est gérée la traduction d'adresse de l'adresse privée 192.168.0.18 vers l'adresse publique 81.56.73.55. Pour mémoire, voici les entêtes du paquet SIP reçu par martin@enst.fr lorsque les proxies ne sont pas utilisés, au début de ce chapitre

 
Sélectionnez

Record-Route: <sip:137.194.192.139;transport=tcp;r2=on;...>
Record-Route: <sip:137.194.192.139;r2=on;ftag=...>
Via: SIP/2.0/TCP 137.194.192.139;branch=...
Via: SIP/2.0/UDP 192.168.0.18;received=81.56.73.55;branch=...
CSeq: 5077 INVITE

Dans ce cas précédent, ganesh.falco.bz émettait le paquet SIP avec sa propre IP attribuée à l'entête Via: :

 
Sélectionnez

Via: SIP/2.0/UDP 192.168.0.18;branch=z9hG4bK21043C3F

La machine sipmob.enst.fr , constant que l'IP 192.168.0.18 diffère de l'IP apparente depuis laquelle il a reçu le paquet (81.56.73.55), a ajouté « received=81.56.73.55 » à l'entête Via correspondante.

Dans notre cas présent et l'utilisation d'un proxy SIP, la machine sipmob.enst.fr reçoit un paquet émis par le routeur falco.bz , c'est à dire avec la bonne adresse IP. Il est donc possible de connaître précisément la route empruntée par le paquet SIP, même avec la présence de réseaux NAT.

IV-A-7-b. Enregistrement de la route : émission de la réponse

Image non disponible

Nous illustrons ici le moment où les entêtes Via: sont dépilées. Nous constatons qu'à chaque saut, le routeur SIP dépile la première entête Via: (qui, normalement, est exactement la même entête qu'il avait pu inscrire lors du passage de la requête SIP).

Les entête Record-Route: , quant à elles, ne sont pas dépilées et informent donc le client falco@falco.bz de l'itinéraire que suit la transaction SIP en cours.

Notons enfin l'utilisation de l'entête Max-Forwards . Si cette entête tombe à 0 et que le routeur doit transmettre un paquet où Max-Forwards est négatif, il s'arrête là et répond avec une erreur « 483 Too Many Hops: ». (c'est différent du champ TTL de IP où le TTL ne vaut jamais zéro). Ce qui est étonnant, c'est que dans cette réponse, l'entête Record-Route: n'existe pas, mais les champs Via: sont tous positionnés. Ainsi, chaque routeur recevant le message SIP dépile sa propre entête puis retransmet le message au routeur indiqué à la ligne Via: suivante. Les champs Via: sont donc dépilés dans l'ordre inverse des routeurs précédemment traversés, jusqu'à réception par la machine ayant émis le paquet initial. Dans le cas de ce type d'erreur, cela génère donc deux fois plus de flux, ce qui peut surprendre.

Le protocole SIP est l'un des protocoles actuels concernant la gestion de session pour un procole tiers, comme la VoIP ou la vidéo. Bien que normalisé, il est récent et a déjà été complètement revu à sa version 2.0. Comme souvent dans les techniques de communication de pair à pair, l'utilisation de réseaux NAT soulève des difficultés que SIP ne peut résoudre seul. Cependant l'utilisation de serveurs STUN (qui demandent un travail supplémentaire d'installation et de configuration, cette technique étant relativement peu utilisée), et surtout des proxies SIP, permet de s'affranchir de ce problème de NAT grâce à la souplesse du protocole de routage de SIP, illustré ici avec le logiciel SIP Express Routeur.

V. Bibliographie

[1] RFC 2916, E.164 number and DNS : http://www.ietf.org/rfc/rfc2916.txt

[2] RFC 2915, The Naming Authority Pointer (NAPTR) DNS ressource Record : http://www.ietf.org/rfc/rfc2915.txt

[3] RFC 2543, SIP: Session Initiation Protocol : http://www.ietf.org/rfc/rfc2915.txt

[4] RFC 3261, SIP: Session Initiation Protocol : http://www.ietf.org/rfc/rfc3261.txt

[5] Understanding the SIP, Albert B. Johnston, second edition. Artech House Publishers, 2004.

[6] RFC 2052, A DNS RR for specifying the location of services (DNS SRV) : http://www.ietf.org/rfc/rfc2052.txt

[7] SER HowTo, 2.5. DNS SRV ressource Records : http://www.iptel.org/ser/doc/serhowto/ser-Howto.html#AEN132

[8] RFC Draft, SIP authentication using CHAP-Password : http://www.iptel.org/info/players/ietf/aaa/draft-byerly-sip-radius-00.txt

[9] RFC 3489, STUN - Simple Traversal of User Datagram Protocol (UDP) Through Network Address Translators (NATs) : http://www.ietf.org/rfc/rfc3489.txt

[10] SIP Express Routeur : http://www.iptel.org/ser/

[11] SER HowTo, 3.1. Modify SER configuration : http://www.iptel.org/ser/doc/ser-howto/ser-Howto.html#AEN174

[12] Serusers mailing list : loose routing of BYE from UDP to TCP : http://mail.iptel.org/pipermail/serusers/2005-September/024494.html