Just Another SIP
Un article de Cenabumiki.
Just another SIP
Sommaire |
Objet
Cette page présente un script PERL que j'ai écrit suite à un taunt de Jantigenie, qui cherchait à pouvoir scripter un coup de téléphone.
Une recherche non-exhaustive des outils en ligne de commande n'en révèle aucun qui permette de faire cela facilement. En effet, ces derniers sont plus des interfaces utilisateurs en mode texte que des jeux de commande SIP.
Net::SIP
Ce script s'appuie sur le module Net::SIP disponible sur CPAN.
Comme d'accoutumé avec le PERL, cette librairie semble assez complète et ne se limite pas qu'à la tâche recherchée ; cette page-ci cependant ne décrit que les parties nécessaire à :
- L'enregistrement auprès du registrar
- La composition du numéro
- L'émission du message
- Le raccrochage
et les démarches qui m'ont permis de le faire fonctionner sur freephonie (l'intérêt étant que si le lecteur passe par un autre fournisseur, il puisse lui même déboguer le script)
Le cas échéant, si ce script est appelé à évoluer, je lui réserverais d'autres pages.
L'expérimentation
Enregistrement
La première étape consistait à s'enregistrer chez le registrar ; étape a priori simple vu que Net::SIP::Simple fournit un exemple qui, traduit en freephonie donne un truc du genre!
use Net::SIP;
# create new agent
my $ua = Net::SIP::Simple->new(
registrar => 'freephonie.net',
domain => 'freephonie.net',
from => '<mon numéro ici>',
auth => [ '<mon numéro ici>','<mon mot de passe freephonie>' ],
);
$ua->register;
Bon... alors ça, ça ne marche pas. Du coup, je recommande chaleureusement de modifier en :
$ua->register || die ( "Pas enregistré " . $ua->error );
ce qui vous donnerait comme sortie:
Pas enregistré Failed with code 423 at toto.pl line 10.
Une recherche sur la RFC idoine (3261) nous informe que ce message d'erreur est dû au fait que le délai d'expiration est trop bref (21.4.17), une seconde recherche concernant l'expiration nous indique qu'en fait, le serveur nous indique la valeur minimum qu'il souhaite (20.23)
Un coup de wireshark en filtrant sur juste l'adresse de freephonie.net et en relançant le script permet de voir l'entête concernée (minimum 1800)
La doc explique qu'un des arguments de register est expires. (Si ce n'est pas dans la doc, bien énervé, il faut aller le chercher dans le code. :) )
Donc la nouvelle ligne:
$ua->register( expires => 1800 ) || die ( "Pas enregistré " . $ua->error );
Appeler et passer le fichier son
La suite, semble directe : passer l'appel et jouer le fichier:
# Invite other party, send anncouncement once connected
$ua->invite( '<le numéro de mon correspondant>',
init_media => $ua->rtp( 'send_recv', '<mon fichier son>' ),
asymetric_rtp => 1,
);
Bon... ça non plus, ça ne marche pas... Et c'est beaucoup moins bavard, même un
$ua->invite( '<le numéro de mon correspondant>',
...
) ||die "invite failed: ".$ua->error;
ne donne rien. En revanche un
printf $call->error;
immédiatement après le code donne
Failed with error 22 code=488
Ici la RFC n'est que de peu de secours: « 21.4.26 488 Not Acceptable Here » (Pourtant je ne portais pas de baskets)
Wireshark en revanche montre que le serveur SIP nous donne quand même quelques infos: « 488 no vocodeur intersection »
Une petite googlelisation sur ce message informe que le client n'utilse pas un codec qui est supporté par le registrar et que free ne supporte que le PCM alaw.
Pour l'instant, le seul paramètre se rapprochant de la notion de codec est init_media, un parcours rapide de la doc sur CPAN pointe vers le paramètre rtp (et de fil en aiguille rtp_param) :
- Data for the codec used in the media specified by init_media and for the initialization of the default SDP data. This is an array reference [pt,size,interval,name] where pt is the payload type, size is the size of the payload and interval the interval in which the RTP packets will be send. name is optional and if given rtpmap and ptime entries will be added to the SDP so that the name is associated with the given payload type. The default is for PCMU/8000: [0,160,160/8000].
En revanche aucune autre info sur les entrées pour les autres codecs. Il existe sans doute une doc référençant tous les payload possibles, mais là, pour le coup, je ne l'ai pas trouvée.
Finalement un dernier coup de Wireshark mais ce coup-ci avec un client SIP " normal" m'a permis de voir que le PCM aLaw correspondait à la valeur de 8 d'où l'ajour du paramètre rtp_param dans le code.
# Invite other party, send anncouncement once connected
$ua->invite( '<le numéro de mon correspondant>',
init_media => $ua->rtp( 'send_recv', '<mon fichier son>' ),
asymetric_rtp => 1,
rtp_param => [ 8, 160, 160/8000, 'PCMA/8000' ],
);
Finalement, j'ai modifié le paramètre asymetric_rtp à 0, non pas parce que ça ne marchait pas (pas testé) mais parce que je ne le sentais pas avec une box en NAT.
Émission du message et raccrochage
Les dernières lignes du code d'exemple sont:
# Mainloop $ua->loop;
En laissant ces lignes, ça appelle et joue le message, en revanche ça ne raccroche pas tout seul.
En admettant que la finalité du ce script serait de harceler son ennemi en l'appelant toutes les 2 minutes sur son portable, il n'aurait plus qu'à ne pas raccrocher pour nous rendre la monnaie de la pièce ;-).
La question qui au final est posée est : comment sortir de la loop?
Le documentation de loop indique que l'on peut lui passer:
- un timeout
- des variables d'arrêt
Ailleurs dans la doc (Eventloop) il est écrit que les variables d'arrêt sont modifiée par les fonctions de callback.
La documentation de Net::SIP::Simple::RTP indique qu'à la fin du fichier cb_rtp_done sera appelée ; d'où l'ajout de la ligne
cb_rtp_done => \$rtp_done,
dans la fonction invite précédente afin que celle-ci modifie la variable $rtp_done.
$rtp_done est également passée comme référence à loop afin que quand le fichier est terminé, la loop se termine, il suffit de terminer ensuite la conversation avec un bye.
# Mainloop $ua->loop( \$rtp_done ); $call->bye;
Une recherche plus approfondie de la doc indique que l'on peut passer d'autre référence pour sortir de la boucle principale (comme si le correspondant raccroche ou ne répond pas, etc...)
Le script final
Après un peu de nettoyage et généralisation le script final ressemble à ceci:
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long qw(:config posix_default bundling);
use Net::SIP;
#Usage
sub usage {
print STDERR "ERROR: @_\n" if @_;
print STDERR <<EOS;
usage: $0 [ options ] no de tel
Joue un fichier vers un téléphone après que le correspondant décroche
Options:
-R|--registrar host[:port] register at given address
-S|--send filename Fichier audio
--username name
--password pass probablement mieux à faire pour le cacher d'un ps
EOS
exit( @_ ? 1:0 );
}
my ($file,$registrar,$username,$password);
$registrar='freephonie.net';
$file='test.pcm.wav';
GetOptions(
'R|registrar=s' => \$registrar,
'S|send=s' => \$file,
'username=s' =>\$username,
'password=s' =>\$password,
) || usage( "bad option" );
my( $to )=@ARGV;
$to || usage( "pas de destination" );
# create new agent
print "Creating connection\n";
my $ua = Net::SIP::Simple->new(
registrar => $registrar,
domain => $registrar,
from => $username,
auth => [ $username,$password ],
);
# Register agent
$ua->register( expires => 1800 ) # <- Valeur mini chez free
|| die ( "Pas enregistré " . $ua->error );
print "Enregistré\n";
# Variables d'arret.(sort de loop quand rtp_done est vrai)
my $rtp_done;
print "Appelle ".$to.'@'.$registrar."\n";
my $call= $ua->invite( $to,
init_media => $ua->rtp( 'send_recv', $file ),
cb_rtp_done => \$rtp_done,
asymetric_rtp => 0,
rtp_param => [ 8, 160, 160/8000, 'PCMA/8000' ],
) ||die "invite failed: ".$ua->error;
# Mainloop
$ua->loop( \$rtp_done );
$call->bye;
Ce qui reste encore beaucoup trop lisible comme PERL, il faudra que je le complique plus. :-)
Un mot sur le fichier wav
A moins que le lecteur ne soit nostalgique de platines disques où l'on pouvait s'amuser à jouer des disques à une vitesse autre que celle prévue, il faut bien prendre soin à:
- Échantillonner le fichier audio à la bonne fréquence (8000Hz) en mono (une seule piste)
- Choisir le bon encodage
Pour cela j'ai utilisé audacity et ses fonctions d'export... Le détail dépasse le contexte de ce document.
Perspectives
D'avoir joué avec les possibilités de scripter du SIP (qui doit servir à Jantigenie pour un projet domotique) ouvre bien des possibilités, comme piper l'entrée sur un logiciel de synthèse vocale, voire la sortie sur de la reconnaissance... Il n'y a pas il faut que je jette un coup d'œil!
--Francis 30 nov 2009 à 23:58 (CET)

