Primi esperimenti con il VoIP: Asterisk + OpenSIPS

Attenzione ! Questo articolo è stato scritto più di due anni fa. Ti prego quindi di considerare che le informazioni riportate potrebbero non essere aggiornate o non più valide.

Come avevo già scritto precedentemente, sono al lavoro per la progettazione e successiva realizzazione di un sistema VoIP piuttosto grande. Dopo aver realizzato diversi PBX Asterisk, con limitato numero di interni (10-20), indubbiamente la mia predisposizione era fortemente indirizzata verso software libero. Tuttavia Asterisk, come confermato anche da esperti del settore, non è adatto a sopportare un elevato numero di interni (si parla di 1000-2000 telefoni) perché offre tutta una serie di servizi avanzati e di transcoding non sempre necessari. Per la semplice gestione della registrazione e gestione del routing è sufficiente un SIP proxy, molto più snello di un completo sistema PBX come Asterisk. Moltissimi, compresi appliances commerciali, hanno scelto OpenSIPS:

OpenSIPS (Open SIP Server) is a mature Open Source implementation of a SIP server. OpenSIPS is more than a SIP proxy/router as it includes application-level functionalities. OpenSIPS, as a SIP server, is the core component of any SIP-based VoIP solution. With a very flexible and customizable routing engine, OpenSIPS ‘unifies voice, video, IM and presence services in a highly efficient way, thanks to its scalable (modular) design.
What OpenSIPS has to offer, comes in a reliable and high-performance flavour – OpenSIPS is one of the fastest SIP servers, with a throughput that confirms it as a solution up to enterprise or carrier-grade class.

Premetto che un forte supporto e aiuto nella comprensione del tutto mi è stata data dal libro “Building Telephony Systems with OpenSIPS 1.6“, disponibile anche su Amazon.it.

Senza voler ripetere le basi già descritte nel mio precedente post (OpenSIPS: l’Installazione), il contesto nel quale  lavoreremo in questo post è composto da:

Per quanto riguarda l’installazione di Asterisk ho scelto di NON mettere FreePBX e di configurare manualmente il tutto. Asterisk, soprattutto in questa prima fase, servirà esclusivamente come gateway con la rete telefonica tradizionale.

Vediamo velocemente la configurazione di Asterisk. In particolare ho dovuto modificare i files sip.conf:

[voip-trunk]
type=peer
fromdomain=voip.unisi.it
host=voip.unisi.it
insecure=invite
disallow=all
allow=alaw

e extensions.conf:

[macro-from-pstn-1]
exten => s,1,Noop(Entering macro-from-pstn-1 with DID = ${DID} and setting to: 2617)
exten => s,n,Set(DNID=2617)

[macro-from-pstn-2]
exten => s,1,Noop(Entering macro-from-pstn-2 with DID = ${DID} and setting to: 2650)
exten => s,n,Set(DNID=2650)

[from-pstn]
exten => _X.,1,Set(DID=${EXTEN})
exten => _X.,n,Goto(s,1)
exten => s,1,Noop(Entering from-dahdi with DID == ${DID})
exten => s,n,Ringing()
exten => s,n,Set(DID=${IF($["${DID}"= ""]?s:${DID})})
exten => s,n,Noop(DID is now ${DID})
exten => s,n,Set(CHAN=${CHANNEL:6})
exten => s,n,Set(CHAN=${CUT(CHAN,-,1)})
exten => s,n,Macro(from-pstn-${CHAN},${DID},1)
exten => s,n,Noop(Returned from-pstn-${CHAN})
exten => s,n,Dial(SIP/${DNID}@voip-trunk,20);
exten => s,n,Hangup()

[from-voip]
exten=s,1,NooP("from-voip: ${EXTEN}")
exten=>_[0-9].,1,Dial(DAHDI/g0/${EXTEN})
exten=>_[0-9].,2,hangup()

Notare che in questo caso ho solamente 2 canali PSTN configurati all’interno del gruppo 0 (DAHDI/g0). Queste impostazioni si gestiscono nel file dahdi-channels.conf:

; Span 1: WCTDM/4 "Wildcard TDM400P REV I Board 5" (MASTER) 
;;; line="1 WCTDM/4/0 FXSKS"
signalling=fxs_ks
callerid=asreceived
group=0
context=from-pstn
channel => 1

...

Adesso passiamo al cuore del sistema: OpenSIPS. In questo caso deve occuparsi del routing, dell’accounting e della registrazione/autenticazione dei telefoni VoIP. Ho scelto di basare il tutto su database MySQL, creando le opportune tabelle.

Per semplificarne la gestione ho fatto configurare una VLAN sulla rete e ho messo un server DHPC che, oltre a fornire l’indirizzo IP e la configurare di rete, invia anche l’opzione 66, “tftp-server-name“, per la configurazione automatica dei telefoni (autoprovisioning). Nel nostro caso si tratta di telefoni Yealink SIP-T20P che supportano l’autoprovisioning: inviando tale opzione DHCP, i telefoni tentano di scaricare la configurazione attraverso il server TFTP indicato. Ovviamente la configurazione di ogni singolo apparecchio deve essere salvata in un file dal nome [mac-address].cfg ma anche questo dipende dal modello del telefono e dalle specifiche del produttore.

Tornando all’OpenSIPS, usato come SIP proxy, ecco la configurazione:

####### Global Parameters #########
debug=4
log_stderror=no
log_facility=LOG_LOCAL7
fork=yes
children=4
# default db_url to be used by modules requiring DB connection
db_default_url="mysql://opensips:xxx@localhost/opensips"
port=5060
listen=udp:172.20.0.1:5060

####### Modules Section ########
mpath="/usr/lib/opensips/modules/"
loadmodule "db_mysql.so"
loadmodule "signaling.so"
loadmodule "sl.so"
loadmodule "tm.so"
loadmodule "rr.so"
loadmodule "maxfwd.so"
loadmodule "usrloc.so"
loadmodule "registrar.so"
loadmodule "textops.so"
loadmodule "mi_fifo.so"
loadmodule "uri.so"
loadmodule "acc.so"
loadmodule "auth.so"
loadmodule "auth_db.so"
loadmodule "alias_db.so"
loadmodule "domain.so"
loadmodule "presence.so"
loadmodule "permissions.so"
# ----------------- setting module-specific parameters ---------------
modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")
modparam("rr", "append_fromtag", 1)
modparam("registrar", "max_contacts", 10)
modparam("usrloc", "db_mode", 2)
modparam("usrloc", "db_url","mysql://opensips:xxxx@localhost/opensips")
modparam("uri", "use_uri_table", 0)
modparam("acc", "early_media", 1)
modparam("acc", "cdr_flag", 1)
modparam("acc", "report_cancels", 1)
modparam("acc", "detect_direction", 1)
modparam("acc", "failed_transaction_flag", 3)
modparam("acc", "log_flag", 1)
modparam("acc", "log_missed_flag", 2)
modparam("acc", "db_flag", 1)
modparam("acc", "db_missed_flag", 2)
modparam("acc", "db_table_acc", "acc")
modparam("acc", "db_url", "mysql://opensips:xxx@localhost/opensips")
modparam("acc", "db_extra", "from_uri=$fU; to_uri=$tU; ip=$si; ua=$hdr(User-Agent)")
modparam("auth_db", "calculate_ha1", yes)
modparam("auth_db", "password_column", "password")
modparam("auth_db", "db_url","mysql://opensips:xxx@localhost/opensips")
modparam("auth_db", "load_credentials", "")
modparam("auth_db", "use_domain", 1)
modparam("alias_db", "db_url","mysql://opensips:xxxx@localhost/opensips")
modparam("domain", "db_url","mysql://opensips:xxx@localhost/opensips")
modparam("domain", "db_mode", 1) # Use caching
modparam("auth_db|usrloc|uri", "use_domain", 1)
modparam("presence", "db_url","mysql://opensips:xxx@localhost/opensips")
modparam("presence", "presentity_table", "presentity")
modparam("presence", "active_watchers_table", "active_watchers")
modparam("presence", "watchers_table", "watchers")
modparam("presence", "db_update_period", 100)
modparam("presence", "notify_offline_body", 1)
modparam("permissions", "db_url", "mysql://opensips:xxxx@localhost/opensips")
modparam("permissions", "address_table", "address")
modparam("permissions", "grp_col", "grp")
modparam("permissions", "ip_col", "ip")
modparam("permissions", "mask_col", "mask")
modparam("permissions", "port_col", "port")
modparam("permissions", "proto_col", "proto")
modparam("permissions", "info_col", "context_info")

####### Routing Logic ########

route{
 if (!mf_process_maxfwd_header("10")) {
   sl_send_reply("483","Too Many Hops");
   exit;
 }
 if (has_totag()) {
  if (loose_route()) {
   if (is_method("BYE")) {
    setflag(1); # do accounting ...
    setflag(3); # ... even if the transaction fails
   } else if (is_method("INVITE")) {
    record_route();
   }
  route(1);
 } else {
  if (is_method("SUBSCRIBE") && $rd == "172.20.0.1") {
  # in-dialog subscribe requests
   route(2);
   exit;
  }
  if ( is_method("ACK") ) {
   if ( t_check_trans() ) {
    # non loose-route, but stateful ACK; must be an ACK after 
    # a 487 or e.g. 404 from upstream server
    t_relay();
    exit;
   } else {
    # ACK without matching transaction ->
    # ignore and discard
    exit;
   }
  }
  sl_send_reply("404","Not here");
  }
 exit;
}
# CANCEL processing
if (is_method("CANCEL")) {
 if (t_check_trans())
  t_relay();
 exit;
 }
 t_check_trans();

 if (!(method=="REGISTER") && is_from_local()) {
  if(!check_source_address("0")){
   if (!proxy_authorize("", "subscriber")) {
    proxy_challenge("", "0");
    exit;
   }
   if (!db_check_from()) {
    sl_send_reply("403","Forbidden auth ID");
    exit;
   }
   consume_credentials();
   # caller authenticated
  }
 }
 # preloaded route checking
 if (loose_route()) {
  xlog("L_ERR", "Attempt to route with preloaded Route's [$fu/$tu/$ru/$ci]");
  if (!is_method("ACK"))
    sl_send_reply("403","Preload Route denied");
  exit;
 }
 # record routing
 if (!is_method("REGISTER|MESSAGE"))
  record_route();
 # account only INVITEs
  if (is_method("INVITE")) {
   setflag(1); # do accounting
  }
  if (!is_uri_host_local()) {
   append_hf("P-hint: outbound\r\n"); 
   route(1);
  }

 if( is_method("PUBLISH|SUBSCRIBE"))
  route(2);

 if (is_method("PUBLISH")) {
  sl_send_reply("503", "Service Unavailable");
  exit;
 }
 if (is_method("REGISTER")) {
  # authenticate the REGISTER requests
  if (!www_authorize("", "subscriber")) {
   www_challenge("", "0");
   exit;
  }
  if (!db_check_to()) {
   sl_send_reply("403","Forbidden auth ID");
   exit;
  }
  if (!save("location")) 
   sl_reply_error();
  exit;
 }
 if ($rU==NULL) {
  # request with no Username in RURI
  sl_send_reply("484","Address Incomplete");
  exit;
 }
 # apply DB based aliases 
 alias_db_lookup("dbaliases");
 # do lookup with method filtering
 if (!lookup("location","m")) {
  switch ($retcode) {
   case -1: # no contact - forward to the call to Asterisk PSTN gateway
   route(4);
   exit;
  case -3: # internal error
   t_newtran();
   t_reply("404", "Not Found");
   exit;
  case -2: # method not supported
   sl_send_reply("405", "Method Not Allowed");
   exit;
  }
 }

 # when routing via usrloc, log the missed calls also
 setflag(2);
 route(1);
}

route[1] {
 # for INVITEs enable some additional helper routes
 if (is_method("INVITE")) {
  t_on_branch("2");
  t_on_reply("2");
  t_on_failure("1");
 }
 if (!t_relay()) {
  sl_reply_error();
 };
 exit;
}

# Presence route
route[2]
{
 if (!t_newtran()) {
  sl_reply_error();
  exit;
 };
 if(is_method("PUBLISH")) {
  handle_publish();
 }
 else if( is_method("SUBSCRIBE")) {
  handle_subscribe();
 }
 exit;
}
route[4] {
 # Forward call to Asterisk 
 rewritehostport("172.20.0.5:5060");
 route(1);
}
branch_route[2] {
 xlog("new branch at $ru\n");
}
onreply_route[2] {
 xlog("incoming reply\n");
}
failure_route[1] {
 if (t_was_cancelled()) {
   exit;
 }
}

Utilizzando ngrep (ngrep -W byline -td any . port 5060) vediamo la fase di REGISTER all’accensione di un telefono (2617):

U 2012/06/08 12:18:23.083810 172.20.0.10:5062 -> 172.20.0.1:5060
REGISTER sip:voip.unisi.it SIP/2.0.
Via: SIP/2.0/UDP 172.20.0.10:5062;branch=z9hG4bK1849514868.
From: "2617" <sip:2617@voip.unisi.it>;tag=1735972936.
To: "2617" <sip:2617@voip.unisi.it>.
U 2012/06/08 12:18:24.916666 172.20.0.1:5060 -> 172.20.0.10:5062
SIP/2.0 200 OK.
Via: SIP/2.0/UDP 172.20.0.10:5062;branch=z9hG4bK1844770290.

Alla registrazione di un telefono, OpenSIPS memorizza il relativo numero (oltre ad altri dati tecnici), nella tabella “location“:

Quando dall’interno 2617 chiamo un altro numero “interno” (ad es. 2650), inteso come registrato sul medesimo proxy, ecco il flusso delle segnalazioni:

U 2012/06/08 12:32:32.512136 172.20.0.10:5062 -> 172.20.0.1:5060
INVITE sip:2650@voip.unisi.it SIP/2.0.
Via: SIP/2.0/UDP 172.20.0.10:5062;branch=z9hG4bK1302839294.
From: "2617" <sip:2617@voip.unisi.it>;tag=533584413.
To: <sip:2650@voip.unisi.it>
U 2012/06/08 12:32:32.611392 172.20.0.1:5060 -> 172.20.0.10:5062
SIP/2.0 100 Giving a try.
Via: SIP/2.0/UDP 172.20.0.10:5062;branch=z9hG4bK836251423.
From: "2617" <sip:2617@voip.unisi.it>;tag=533584413.
To: <sip:2650@voip.unisi.it>.
U 2012/06/08 12:32:32.611712 172.20.0.1:5060 -> 172.20.0.12:5062
INVITE sip:2650@172.20.0.12:5062 SIP/2.0.
Record-Route: <sip:172.20.0.1;lr;ftag=533584413>.
Via: SIP/2.0/UDP 172.20.0.1;branch=z9hG4bK6231.d78da302.0.
Via: SIP/2.0/UDP 172.20.0.10:5062;branch=z9hG4bK836251423.
From: "2617" <sip:2617@voip.unisi.it>;tag=533584413.
To: <sip:2650@voip.unisi.it>.
U 2012/06/08 12:32:32.790067 172.20.0.12:5062 -> 172.20.0.1:5060
SIP/2.0 180 Ringing.
Via: SIP/2.0/UDP 172.20.0.1;branch=z9hG4bK6231.d78da302.0.
Via: SIP/2.0/UDP 172.20.0.10:5062;branch=z9hG4bK836251423.
Record-Route: <sip:172.20.0.1;lr;ftag=533584413>.
From: "2617" <sip:2617@voip.unisi.it>;tag=533584413.
To: <sip:2650@voip.unisi.it>;tag=379428395.
U 2012/06/08 12:32:32.792249 172.20.0.1:5060 -> 172.20.0.10:5062
SIP/2.0 180 Ringing.
Via: SIP/2.0/UDP 172.20.0.10:5062;branch=z9hG4bK836251423.
Record-Route: <sip:172.20.0.1;lr;ftag=533584413>.
From: "2617" <sip:2617@voip.unisi.it>;tag=533584413.
To: <sip:2650@voip.unisi.it>;tag=379428395.

Se il 2650 non risponde e il chiamante decide di rinunciare, ecco che parte una segnalazione CANCEL e si chiude la chiamata:

U 2012/06/08 12:32:37.588802 172.20.0.1:5060 -> 172.20.0.10:5062
SIP/2.0 200 canceling.
Via: SIP/2.0/UDP 172.20.0.10:5062;branch=z9hG4bK836251423.
From: "2617" <sip:2617@voip.unisi.it>;tag=533584413.
To: <sip:2650@voip.unisi.it>;tag=643c56a457061490c918aea728c07866-83f3.
U 2012/06/08 12:32:37.589140 172.20.0.1:5060 -> 172.20.0.12:5062
CANCEL sip:2650@172.20.0.12:5062 SIP/2.0.
Via: SIP/2.0/UDP 172.20.0.1;branch=z9hG4bK6231.d78da302.0.
From: "2617" <sip:2617@voip.unisi.it>;tag=533584413.
Call-ID: 1123041723@172.20.0.10.
To: <sip:2650@voip.unisi.it>.

Quando l’interno 2617 chiama un numero esterno, esempio 2169, ecco che OpenSIPS reindirizza la chiamata all’Asterisk:

U 2012/06/08 12:38:37.068478 172.20.0.10:5062 -> 172.20.0.1:5060
INVITE sip:2169@voip.unisi.it SIP/2.0.
Via: SIP/2.0/UDP 172.20.0.10:5062;branch=z9hG4bK1064669372.
From: "2617" <sip:2617@voip.unisi.it>;tag=969196503.
To: <sip:2169@voip.unisi.it>.
U 2012/06/08 12:38:37.072909 172.20.0.1:5060 -> 172.20.0.10:5062
SIP/2.0 100 Giving a try.
Via: SIP/2.0/UDP 172.20.0.10:5062;branch=z9hG4bK1064669372.
From: "2617" <sip:2617@voip.unisi.it>;tag=969196503.
To: <sip:2169@voip.unisi.it>.
U 2012/06/08 12:38:37.074540 172.20.0.1:5060 -> 172.20.0.5:5060
INVITE sip:2169@172.20.0.5:5060 SIP/2.0.
Record-Route: <sip:172.20.0.1;lr;ftag=969196503>.
Via: SIP/2.0/UDP 172.20.0.1;branch=z9hG4bKe413.748e1db.0.
Via: SIP/2.0/UDP 172.20.0.10:5062;branch=z9hG4bK1064669372.
U 2012/06/08 12:38:37.077108 172.20.0.5:5060 -> 172.20.0.1:5060
SIP/2.0 100 Trying.
Via: SIP/2.0/UDP 172.20.0.1;branch=z9hG4bKe413.748e1db.0;received=172.20.0.1.
Via: SIP/2.0/UDP 172.20.0.10:5062;branch=z9hG4bK1064669372.

Se accade l’opposto, ovvero dalla rete PSTN (es. numero 2169) chiamano un numero interno 2617:

U 2012/06/08 12:41:27.002715 172.20.0.5:5060 -> 172.20.0.1:5060
INVITE sip:2617@voip.unisi.it SIP/2.0.
Via: SIP/2.0/UDP 172.20.0.5:5060;branch=z9hG4bK1fce9b73;rport.
Max-Forwards: 70.
From: "asterisk" <sip:asterisk@voip.unisi.it>;tag=as49579a42.
To: <sip:2617@voip.unisi.it>.
Contact: <sip:asterisk@172.20.0.5>.

Su Asterisk ho:

-- Starting simple switch on 'DAHDI/1-1'
 -- Executing [s@from-pstn:1] NoOp("DAHDI/1-1", "Entering from-dahdi with DID == ") in new stack
 -- Executing [s@from-pstn:2] Ringing("DAHDI/1-1", "") in new stack
 -- Executing [s@from-pstn:3] Set("DAHDI/1-1", "DID=s") in new stack
 -- Executing [s@from-pstn:4] NoOp("DAHDI/1-1", "DID is now s") in new stack
 -- Executing [s@from-pstn:5] Set("DAHDI/1-1", "CHAN=1-1") in new stack
 -- Executing [s@from-pstn:6] Set("DAHDI/1-1", "CHAN=1") in new stack
 -- Executing [s@from-pstn:7] Macro("DAHDI/1-1", "from-pstn-1,s,1") in new stack
 -- Executing [s@macro-from-pstn-1:1] NoOp("DAHDI/1-1", "Entering macro-from-pstn-1 with DID = s and setting to: 2617") in new stack
 -- Executing [s@macro-from-pstn-1:2] Set("DAHDI/1-1", "DNID=2617") in new stack
 -- Executing [s@from-pstn:8] NoOp("DAHDI/1-1", "Returned from-pstn-1") in new stack
 -- Executing [s@from-pstn:9] Dial("DAHDI/1-1", "SIP/2617@voip-trunk,20") in new stack
 == Using SIP RTP CoS mark 5
 -- Called 2617@voip-trunk
 -- SIP/voip-trunk-0000006b is ringing

Ho ovviamente rimosso tutte le segnalazioni di ACK e di OK non necessarie alla comprensione del flusso di funzionamento. Credo comunque che sia piuttosto chiaro il flusso delle segnalazioni SIP. Importante citare che la voce viene inviata via RTP da telefono a telefono pertanto considerate che, a meno di prevedere proxy RTP, tutti gli apparecchi devono essere raggiungibili sulla rete.

Ovviamente queste sono da considerarsi note di lavoro e sono, pertanto, soggette a errori, imprecisioni e omissioni.

 

Michele Pinassi

Blogger, appassionato di tecnologia, società e politica. Attualmente Responsabile del Sistema telefonico di Ateneo presso l'Università degli Studi di Siena ed esperto di sicurezza informatica nello staff del DPO. Utilizza quasi esclusivamente software libero.

Potrebbero interessarti anche...

Rispondi

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.

%d blogger hanno fatto clic su Mi Piace per questo: