Primi esperimenti con il VoIP: Asterisk + OpenSIPS

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.

 

(Visitato in totale 7 volte, oggi 1 visite)

Rispondi

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