Kamailio – Autenticación de troncales por IP

Ahora lo que vamos a proceder es a configurar un troncal para que nuestras llamadas entrantes puedan ser procesadas desde nuestro operador que nos aparagua. El escenario es muy sencillo, tenemos a usuarios que trabajan con nuestro Kamailio. Cuando ellos emiten llamadas, las enviamos a un proveedor (ver post Kamailio – Enrutando llamadas hacia nuestro provider o SBC) y cuando no están disponibles, las enviamos al voicemail (ver posts Parte 1 y Parte 2 de Kamailio – Asterisk Realtime: Voicemails para nuestros usuarios de Kamailio).

Hasta ahora, todas las llamadas que nuestros usuarios recibían eran de nuestros dominios, y las hemos autenticado por usuario y password. Por otro lado, para las salientes, simplemente las hemos enviado a un troncal SIP donde está el route[PSTN].

Pero, ¿Cómo podemos recibir llamadas desde el exterior? En este post vamos a configurar un de modo simple un troncal de entrada, autenticado por IP (en nuestro caso 92.127.221.233), para poder recibir llamadas desde un operador que nos aparaguará y nos dará acceso de llamadas entrantes desde la PSTN de otros operadores.

Lo primero que haremos es modificar el request_route. Antes del route(LOCATION) y route(PSTN) he agregado una nueva ruta que es route(FROMPSTN) dado que esta nos mirará si es una llamada de algún troncal para nosotros, siempre y cuando venga de la IP del proveedor.

  # Llamadas entrantes
 if (src_ip==92.127.221.233){
     route(FROMPSTN);
 }

 # Llamadas salientes
 else{
 # user location service
     route(LOCATION);

 # dispatch destinations to PSTN
     route(PSTN);
 }

[TODO] Luego definimos el route[FROMPSTN] para poder procesar estas peticiones. Aquí lo que vamos a hacer es lo siguiente:

(1) En el primer “if” añadimos el P-Asserted-Identity para llamadas entrantes, en caso de que no exista. De esta forma, nuestra red SIP tendrá soporte para RFC3325.

(2) Miramos si existe en dbaliases el número que se ha requerido, y en caso de existir añadimos un “P-hint” para indicar que es una llamada entrante.

(3) En caso de no encontrarse en el database de location, lo que hacemos es ver si este DDI pertenece a nuestra compañía, en caso afirmativo, lo enviamos a nuestro Asterisk de Voicemail donde se podrá reproducir la locución de no disponible (en caso de no tener servicio activado) o el voicemail.

(4) En caso de no encontrarse en el dbaliases, simplemente respondemos con un 404. Not Found.

route[FROMPSTN] {

 xlog("*****DEBUGTONI: Llamada entrante autenticada desde $src_ip\n");
 xlog("*****DEBUGTONI: Llamada entrante para $rU desde $fU $fd \n");

 if(!is_present_hf("P-Asserted-Identity")){
 $var(pidentity_a)="tel:"+$fU+"\n";
 $var(pidentity_p)="\""+$fU+" <"+$fu+">\" <sip:"+$fU+"@"+$(fu{uri.domain})+">\n";
 xlog("*****DEBUGTONI: $var(pidentity_p)\n");
 append_hf("P-Asserted-Identity: $var(pidentity_a)","Call-ID");
 append_hf("P-Preferred-Identity: $var(pidentity_p)","Call-ID");
 }

 #!ifdef WITH_ALIASDB
 # search in DB-based aliases

 if(alias_db_lookup("dbaliases")){
 xlog("*****DEBUGTONI: Existe en dbaliases, es $fU numero de MI EMPRESA y corresponde a $rU ****\n");
 append_hf("P-hint: outbound >> inbound\r\n");
 }

 $avp(oexten) = $rU;
 if (!lookup("location")) {

 xlog("*****DEBUGTONI: $rU no esta en location...****\n");
 $var(rc) = $rc;

 # MODIFIED: Solo enviamos las llamadas para que vayan al Voicemail cuando el requestedUri se encuentre
 # en nuestro dbaliases, si no, al venir de un provider de PSTN le decimos que Not Found.

 if(avp_db_query("select alias_username from dbaliases where username='$rU' and (alias_username like '9%' or alias_username like '8%')","$avp(mailbox)")){ 
 xlog("*****DEBUGTONI: el mailbox es $avp(mailbox) fromUri: $tU RequestedUri: $rU****\n");
 route(TOVOICEMAIL);

 }
 else {
 t_newtran();
 switch ($var(rc)) {
 case -1:
 case -3:
 send_reply("404", "Not Found");
 exit;
 case -2:
 send_reply("405", "Method Not Allowed");
 exit;
 }
 }
 # when routing via usrloc, log the missed calls also
 #if (is_method("INVITE")) {
 # setflag(FLT_ACCMISSED);
 #}
 }
 #!endif

 # when routing via usrloc, log the missed calls also
 if (is_method("INVITE")) {
 setflag(FLT_ACCMISSED);
 }

 route(RELAY);
 exit;
 return;
}

El route[VOICEMAIL] tiene la configuración que vimos en el post pasado.

Una vez realizado esto, ya se pueden recibir llamadas sin problemas de este operador.

¡Espero que el post os sea de utilidad!

Referencias:

http://www.kamailio.org/events/2006-OpenSER-Summit/slides/openser-summit-2006_06_klaus.darilion_practical-peering-with-openser.pdf

http://www.kamailio.org/docs/modules/3.1.x/modules_s/textops.html#is_present_hf

Kamailio – Asterisk Realtime: Voicemails para nuestros usuarios de Kamailio (Parte 2)

En la parte dos, vamos a hacer un script para que cuando un usuario pulse un código determinado, se haga un dial contra nuestro Kamailio, y entre en un menú para activar o desactivar el buzón de voz, autenticandose por password.

Básicamente deberemos de configurar una ruta para que las llamadas con un determinado código vayan a nuestro Asterisk Realtime, que hemos configurado en Parte 1, y allí dentro hacer unas rutinas para escribir y borrar en el database en que correrá en el Asterisk Realtime.

En el caso que nos ocupa, hemos elegido los siguientes códigos:

90: Activar Voicemail

91: Desactvar voicemail

Bien, ahora vamos a ponernos a la configuración. En Kamailio simplemente hemos de hacer una ruta para que dentro de route[PSTN] se reenvien estas llamadas contra el TOVOICEMAIL cuando hagamos un request de 90 o 91:

if($rU=~"^(9[0-1])"){
   xlog("*****DEBUGTONI: REDIRECTING TO VOICEMAIL\n");
   route(TOVOICEMAIL);
   exit;
 }

Ahora nos meteremos en Asterisk . Lo primero que haremos es configurar el odbc para que desde nuestro dialplan podamos acceder al mysql donde tenemos cargada la configuración de nuestros voicemail y sipusers para poder modificarla.

root@vm-server-001:~# vim /etc/odbc.ini
[MySQL-asterisk]
Description = MySQL Asterisk database
Trace = Off
TraceFile = stderr
Driver = MySQL
SERVER = localhost
USER = asterisk
PASSWORD = Passw0rdMysqL!
PORT = 3306
DATABASE = asterisk

Una vez creado el conector para realizar las conexiones de mysql contra esta base de datos, procederemos a definir las queries que utilizaremos. En este caso, vamos a definir dos bien diferenciadas: activar voicemail (VOICEMAILACT) y desactivar (VOICEMAILACTDEACT) voicemail.

root@vm-server-001:~# vim /etc/asterisk/func_odbc.conf

[VOICEMAILACT]
dsn=asterisk
prefix=FO
readsql=SELECT mailbox FROM voicemail WHERE mailbox='${ARG1}'
writesql=INSERT INTO voicemail (context,mailbox,password,email) VALUES ('${ARG1}','${ARG2}','${ARG3}','${ARG4}')

[VOICEMAILACTDEACT]
dsn=asterisk
prefix=FO
readsql=SELECT mailbox FROM voicemail WHERE mailbox='${ARG1}'
writesql=DELETE FROM voicemail WHERE mailbox='${ARG1}'

Para que nuestro Asterisk vea que tenemos estas funciones, deberemos recargar el modulo de res_odbc y el fun_odbc, donde se cargará el conector y las funciones creadas respectivamente. En el output del cli de Asterisk veremos que aparecen las funciones creadas.

vm-server-001*CLI> module reload res_odbc.so
vm-server-001*CLI> module reload func_odbc.so

vm-server-001*CLI> odbc show all

ODBC DSN Settings
-----------------

 Name: asterisk
 DSN: MySQL-asterisk
 Last connection attempt: 1970-01-01 00:00:00
 Pooled: No
 Connected: Yes

vm-server-001*CLI> 

Finalmente, simplemente tenemos que definir en el extensions.conf de Asterisk qué debemos de hacer con las llamadas, es decir, ver si estamos activando o desactivando el voicemail.

Como se puede ver, lo que vamos a analizar es el campo from del paquete SIP, y nos vamos a quedar con la parte  “Uri Username” que está entre los dos puntos del sip: y el @ del dominio. En este caso, la Requested URI no nos aporta información sobre el usuario, dado que todos nuestros usuarios marcarán 90 o 91.

exten => 90,1,NoOp(**Desactivando VM**)
same => n,Set(tmp=${SIP_HEADER(From)})
same => n,Set(tmp=${CUT(tmp,@,1)})
same => n,Set(tmp=${CUT(tmp,:,2)})
same => n,Set(exists=$[FO_VOICEMAILACTDEACT(${tmp})])
same => n,GoToIf($["${exists}"="${tmp}"]?ok:error)
same => n(ok),Set(FO_VOICEMAILACTDEACT(${tmp})=test)
same => n(ok),PlayBack(voice-mail-system&de-activated)
same => n(ok),Hangup()
same => n(error),NoOp(**Error deactivatingVM ${test})
same => n(error),PlayBack(an-error-has-occured)
same => n(error),Hangup()
same => n,Hangup

exten => 91,1,NoOp(**Activando VM**)
same => n,Set(tmp=${SIP_HEADER(From)})
same => n,Set(tmp=${CUT(tmp,@,1)})
same => n,Set(tmp=${CUT(tmp,:,2)})
same => n,Set(exists=$[FO_VOICEMAILACTDEACT(${tmp})])
same => n,GoToIf($["${exists}"="${tmp}"]?error:ok)
same => n(ok),Set(FO_VOICEMAILACT("default",${tmp},"9685324","tibanezlujan@gmail.com")=test)
same => n(ok),NoOp(${res})
same => n(ok),Playback(voice-mail-system&activated)
same => n(ok),Hangup()
same => n(error),NoOp(**Error deactivatingVM ${test})
same => n(error),PlayBack(an-error-has-occured)
same => n(error),Hangup()
same => n,Hangup

Por otro lado, en el caso de activación he puesto a mano la password y el correo (es una prueba de concepto) pero simplemente sería necesario definir un database extra en nuestro mysql server y consultar esta variable, igual que lo hemos hecho con la variable de voicemail.

Es interesante ver que hemos definido dos lineas antes de añadir o eliminar el voicemail donde se mira que exista al voicemail, dado que si existe, no dejaremos que se cree de nuevo, y si no existe, no permitiremos que se elimine dicho registro.

Importante: Tal y como vimos en Parte 1, en mi caso, antes de enviar a Kamailio es muy importante que hayamos substituido el from por el DDI del cliente, para asociar la el número de buzón a este DDI y no al username.

Podríamos utilizar el Voicemail con usuario, pero luego a la hora de decir que la extensión no está disponible, no se reproducirían los dígitos, y nuestro archivo extensions.conf debería de ser mucho más complejo a la hora de mapear expresiones regulares, de ahí que haya preferido trabajar con el DDI del cliente:

 -- Executing [90@from-kamailio:3] Set("SIP/sipproxy001-00000015", "tmp="victor-voip002" <sip:victor@voip002.netvoip.no-ip.org>;tag=a3jo6lz5iz") in new stack

Al fin y al cabo,si alguien llama desde el exterior, siempre llamará al DDI y no al sipUsername, de ahí que también esto sea importante tenerlo configurado así. En cualquier caso, simplemente comentar que nosotros desde Kamailio lo hemos enviado con el número del aliases, así que es pro este motivo que en Asterisk desmontamos el paquete SIP para poder quedarnos con la parte del fromUsername.

¡Espero que os sea de utilidad!

Kamailio – Asterisk Realtime: Voicemails para nuestros usuarios de Kamailio (Parte 1)

A continuación, lo que vamos a proceder a hacer es la integración de un Asterisk en realtime contra un Kamailio (el que hemos estado montando en posts anteriores) con la idea de poder ofrecer servicio de voicemail cuando el usuario esté ocupado y/o no se encuentre registrado en nuestro sistema (por algún corte en su linea IP, por ejemplo).

Al tratarse de un escenario completo, lo hemos dividido en dos partes, para ir poco a poco, y no hacer un post demasiado largo: La Parte 1 (esta que estás leyendo) consiste en poder dejar mensajes y que se envien al correo, mientras que en la Parte 2 configuraremos que los usuarios puedan activar y/o desactivar el servicio a través del mismo Asterisk.

Vamos a instalar un Asterisk en Realtime, para ello utilizaremos los repositorios de debian de asterisk.org. Así que lo primero es añadir el repositorio y hacer un update:

root@vm-server-001:~# add-apt-repository "deb http://packages.asterisk.org/deb `lsb_release -cs` main"

root@vm-server-001:~# apt-get update

Sin rompernos demasiado la cabeza, procedemos a realizar una instalación del Asterisk a pelo (muchos diran que es poco eficiente, pero al fin y al cabo es lo más rápido):

root@vm-server-001:~# apt-get install asterisk

Ahora mismo, la versión que está disponible es la de Asterisk11, tal y como podemos ver si nos conectamos a su consola:

root@vm-server-001:~# asterisk -rv
Asterisk 11.11.0~dfsg-2ubuntu1, Copyright (C) 1999 - 2013 Digium, Inc. and others.
Created by Mark Spencer <markster@digium.com>
Asterisk comes with ABSOLUTELY NO WARRANTY; type 'core show warranty' for details.
[...]

Al tratarse de una instalación de Asterisk11 en Realtime, necesitaremos una base de datos para conectarnos y aguantar allí la configuración de nuestra PBX y nuestros voicemails, así que procedemos a la instalación de los paquetes necesarios, así como de ODBC para tratar con los drivers/librerias de mysql:

root@vm-server-001:~#apt-get install mysql-server

root@vm-server-001:~#apt-get install libmysqlclient-dev

root@vm-server-001:~#apt-get install unixodbc-dev

root@vm-server-001:~#apt-get install libmyodbc

Lo siguiente que tenemos que hacer, y esto lo he consultado en alguna página de las que tenemos en las referencias, es entrar en el mysql y crear el database para nuestro Asterisk. Para ello, definimos las tablas mysql para Asterisk dentro de un archivo llamado mysql_asterisk.sql (hacer copy paste de lo siguiente dentro de este archivo que creamos llamado mysq_asterisk.sql):

CREATE DATABASE asterisk;
 
USE asterisk;
 
GRANT ALL ON asterisk.* TO asterisk@localhost IDENTIFIED BY 'asterisk_password';
 
DROP TABLE IF EXISTS sipusers;
CREATE TABLE `sipusers` (
     `id` INT(11) NOT NULL AUTO_INCREMENT,
      `name` VARCHAR(10) NOT NULL,
      `ipaddr` VARCHAR(15) DEFAULT NULL,
      `port` INT(5) DEFAULT NULL,
      `regseconds` INT(11) DEFAULT NULL,
      `defaultuser` VARCHAR(10) DEFAULT NULL,
      `fullcontact` VARCHAR(35) DEFAULT NULL,
      `regserver` VARCHAR(20) DEFAULT NULL,
      `useragent` VARCHAR(20) DEFAULT NULL,
      `lastms` INT(11) DEFAULT NULL,
      `host` VARCHAR(40) DEFAULT NULL,
      `type` enum('friend','user','peer') DEFAULT NULL,
      `context` VARCHAR(40) DEFAULT NULL,
      `permit` VARCHAR(40) DEFAULT NULL,
      `deny` VARCHAR(40) DEFAULT NULL,
      `secret` VARCHAR(40) DEFAULT NULL,
      `md5secret` VARCHAR(40) DEFAULT NULL,
      `remotesecret` VARCHAR(40) DEFAULT NULL,
      `transport` enum('udp','tcp','udp,tcp','tcp,udp') DEFAULT NULL,
      `dtmfmode` enum('rfc2833','info','shortinfo','inband','auto') DEFAULT NULL,
      `directmedia` enum('yes','no','nonat','update') DEFAULT NULL,
      `nat` enum('yes','no','never','route') DEFAULT NULL,
      `callgroup` VARCHAR(40) DEFAULT NULL,
      `pickupgroup` VARCHAR(40) DEFAULT NULL,
      `language` VARCHAR(40) DEFAULT NULL,
      `disallow` VARCHAR(40) DEFAULT NULL,
      `allow` VARCHAR(40) DEFAULT NULL,
      `insecure` VARCHAR(40) DEFAULT NULL,
      `trustrpid` enum('yes','no') DEFAULT NULL,
      `progressinband` enum('yes','no','never') DEFAULT NULL,
      `promiscredir` enum('yes','no') DEFAULT NULL,
      `useclientcode` enum('yes','no') DEFAULT NULL,
      `accountcode` VARCHAR(40) DEFAULT NULL,
      `setvar` VARCHAR(40) DEFAULT NULL,
      `callerid` VARCHAR(40) DEFAULT NULL,
      `amaflags` VARCHAR(40) DEFAULT NULL,
      `callcounter` enum('yes','no') DEFAULT NULL,
      `busylevel` INT(11) DEFAULT NULL,
      `allowoverlap` enum('yes','no') DEFAULT NULL,
      `allowsubscribe` enum('yes','no') DEFAULT NULL,
      `videosupport` enum('yes','no') DEFAULT NULL,
      `maxcallbitrate` INT(11) DEFAULT NULL,
      `rfc2833compensate` enum('yes','no') DEFAULT NULL,
      `mailbox` VARCHAR(40) DEFAULT NULL,
      `session-timers` enum('accept','refuse','originate') DEFAULT NULL,
      `session-expires` INT(11) DEFAULT NULL,
      `session-minse` INT(11) DEFAULT NULL,
      `session-refresher` enum('uac','uas') DEFAULT NULL,
      `t38pt_usertpsource` VARCHAR(40) DEFAULT NULL,
      `regexten` VARCHAR(40) DEFAULT NULL,
      `fromdomain` VARCHAR(40) DEFAULT NULL,
      `fromuser` VARCHAR(40) DEFAULT NULL,
      `qualify` VARCHAR(40) DEFAULT NULL,
      `defaultip` VARCHAR(40) DEFAULT NULL,
      `rtptimeout` INT(11) DEFAULT NULL,
      `rtpholdtimeout` INT(11) DEFAULT NULL,
      `sendrpid` enum('yes','no') DEFAULT NULL,
      `outboundproxy` VARCHAR(40) DEFAULT NULL,
      `callbackextension` VARCHAR(40) DEFAULT NULL,
      `timert1` INT(11) DEFAULT NULL,
      `timerb` INT(11) DEFAULT NULL,
      `qualifyfreq` INT(11) DEFAULT NULL,
      `constantssrc` enum('yes','no') DEFAULT NULL,
      `contactpermit` VARCHAR(40) DEFAULT NULL,
      `contactdeny` VARCHAR(40) DEFAULT NULL,
      `usereqphone` enum('yes','no') DEFAULT NULL,
      `textsupport` enum('yes','no') DEFAULT NULL,
      `faxdetect` enum('yes','no') DEFAULT NULL,
      `buggymwi` enum('yes','no') DEFAULT NULL,
      `auth` VARCHAR(40) DEFAULT NULL,
      `fullname` VARCHAR(40) DEFAULT NULL,
      `trunkname` VARCHAR(40) DEFAULT NULL,
      `cid_number` VARCHAR(40) DEFAULT NULL,
      `callingpres` enum('allowed_not_screened','allowed_passed_screen','allowed_failed_screen'
                         ,'allowed','prohib_not_screened','prohib_passed_screen','prohib_failed_screen'
                         ,'prohib') DEFAULT NULL,
      `mohinterpret` VARCHAR(40) DEFAULT NULL,
      `mohsuggest` VARCHAR(40) DEFAULT NULL,
      `parkinglot` VARCHAR(40) DEFAULT NULL,
      `hasvoicemail` enum('yes','no') DEFAULT NULL,
      `subscribemwi` enum('yes','no') DEFAULT NULL,
      `vmexten` VARCHAR(40) DEFAULT NULL,
      `autoframing` enum('yes','no') DEFAULT NULL,
      `rtpkeepalive` INT(11) DEFAULT NULL,
      `call-limit` INT(11) DEFAULT NULL,
      `g726nonstandard` enum('yes','no') DEFAULT NULL,
      `ignoresdpversion` enum('yes','no') DEFAULT NULL,
      `allowtransfer` enum('yes','no') DEFAULT NULL,
      `dynamic` enum('yes','no') DEFAULT NULL,
      `sippasswd` VARCHAR(80) DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `name` (`name`),
      KEY `ipaddr` (`ipaddr`,`port`),
      KEY `host` (`host`,`port`)
) ENGINE=MyISAM;
 
 
DROP TABLE IF EXISTS sipregs;
CREATE TABLE `sipregs` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(80) NOT NULL DEFAULT '',
  `fullcontact` VARCHAR(80) NOT NULL DEFAULT '',
  `ipaddr` VARCHAR(45) DEFAULT NULL,
  `port` mediumint(5) UNSIGNED NOT NULL DEFAULT '0',
  `username` VARCHAR(80) NOT NULL DEFAULT '',
  `regserver` VARCHAR(100) DEFAULT NULL,
  `regseconds` INT(11) NOT NULL DEFAULT '0',
  `defaultuser` VARCHAR(80) NOT NULL DEFAULT '',
  `useragent` VARCHAR(20) DEFAULT NULL,
  `lastms` INT(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
);
 
 
DROP TABLE IF EXISTS voicemail;
CREATE TABLE voicemail (
	-- All of these column names are very specific, including "uniqueid".  Do not change them if you wish voicemail to work.
	uniqueid INT(5) NOT NULL AUTO_INCREMENT PRIMARY KEY,
	-- Mailbox context.
	context CHAR(80) NOT NULL DEFAULT 'default',
	-- Mailbox number.  Should be numeric.
	mailbox CHAR(80) NOT NULL,
	-- Must be numeric.  Negative if you don't want it to be changed from VoicemailMain
	password CHAR(80) NOT NULL,
	-- Used in email and for Directory app
	fullname CHAR(80),
	-- Email address (will get sound file if attach=yes)
	email CHAR(80),
	-- Email address (won't get sound file)
	pager CHAR(80),
	-- Attach sound file to email - YES/no
	attach CHAR(3),
	-- Which sound format to attach
	attachfmt CHAR(10),
	-- Send email from this address
	serveremail CHAR(80),
	-- Prompts in alternative language
	LANGUAGE CHAR(20),
	-- Alternative timezone, as defined in voicemail.conf
	tz CHAR(30),
	-- Delete voicemail from server after sending email notification - yes/NO
	deletevoicemail CHAR(3),
	-- Read back CallerID information during playback - yes/NO
	saycid CHAR(3),
	-- Allow user to send voicemail from within VoicemailMain - YES/no
	sendvoicemail CHAR(3),
	-- Listen to voicemail and approve before sending - yes/NO
	review CHAR(3),
	-- Warn user a temporary greeting exists - yes/NO
	tempgreetwarn CHAR(3),
	-- Allow '0' to jump out during greeting - yes/NO
	operator CHAR(3),
	-- Hear date/time of message within VoicemailMain - YES/no
	envelope CHAR(3),
	-- Hear length of message within VoicemailMain - yes/NO
	sayduration CHAR(3),
	-- Minimum duration in minutes to say
	saydurationm INT(3),
	-- Force new user to record name when entering voicemail - yes/NO
	forcename CHAR(3),
	-- Force new user to record greetings when entering voicemail - yes/NO
	forcegreetings CHAR(3),
	-- Context in which to dial extension for callback
	callback CHAR(80),
	-- Context in which to dial extension (from advanced menu)
	dialout CHAR(80),
	-- Context in which to execute 0 or * escape during greeting
	exitcontext CHAR(80),
	-- Maximum messages in a folder (100 if not specified)
	maxmsg INT(5),
	-- Increase DB gain on recorded message by this amount (0.0 means none)
	volgain DECIMAL(5,2),
	-- IMAP user for authentication (if using IMAP storage)
	imapuser VARCHAR(80),
	-- IMAP password for authentication (if using IMAP storage)
	imappassword VARCHAR(80),
	-- IMAP server location (if using IMAP storage)
	imapsever VARCHAR(80),
	-- IMAP port (if using IMAP storage)
	imapport VARCHAR(8),
	-- IMAP flags (if using IMAP storage)
	imapflags VARCHAR(80),
	stamp TIMESTAMP
);
 
DROP TABLE IF EXISTS voicemail_data;
CREATE TABLE voicemail_data (
	-- Path to the recording
	filename CHAR(255) NOT NULL PRIMARY KEY,
	-- Mailbox number (without context)
	origmailbox CHAR(80),
	-- Dialplan context
	context CHAR(80),
	-- Dialplan context, if voicemail was invoked from a macro
	macrocontext CHAR(80),
	-- Dialplan extension
	exten CHAR(80),
	-- Dialplan priority
	priority INT(5),
	-- Name of the channel, when message was left
	callerchan CHAR(80),
	-- CallerID on the channel, when message was left
	callerid CHAR(80),
	-- Contrary to the name, origdate is a full datetime, in localized format
	origdate CHAR(30),
	-- Same date as origdate, but in Unixtime
	origtime INT(11),
	-- Value of the channel variable VM_CATEGORY, if set
	category CHAR(30),
	-- Length of the message, in seconds
	duration INT(11)
);
DROP TABLE IF EXISTS voicemail_messages;
CREATE TABLE voicemail_messages (
	-- Logical directory
	dir CHAR(255),
	-- Message number within the logical directory
	msgnum INT(4),
	-- Dialplan context
	context CHAR(80),
	-- Dialplan context, if Voicemail was invoked from a macro
	macrocontext CHAR(80),
	-- CallerID, when the message was left
	callerid CHAR(80),
	-- Date when the message was left, in Unixtime
	origtime INT(11),
	-- Length of the message, in seconds
	duration INT(11),
	-- The recording itself
	recording BLOB,
	-- Text flags indicating urgency of the message
	flag CHAR(30),
	-- Value of channel variable VM_CATEGORY, if set
	category CHAR(30),
	-- Owner of the mailbox
	mailboxuser CHAR(30),
	-- Context of the owner of the mailbox
	mailboxcontext CHAR(30),
	-- Unique ID of the message,
	msg_id CHAR(40),
	PRIMARY KEY (dir, msgnum)
);

Y lo siguiente es simplemente volcarla en nuestro servidor de mysql a través del siguiente comando:

root@vm-server-001:~# mysql -u root -p < mysql_asterisk.sql
 Enter password:
root@vm-server-001:~#

Entramos dentro de nuestro servidor mysql y creamos usuario mysql para Asterisk, el cual en principio sólo debe de conectarse desde nuestro mismo equipo (localhost):

mysql> GRANT ALL ON asterisk.* to asterisk@localhost IDENTIFIED BY 'passw0rd!!';

Ahora procedemos a configurar el driver de mysql  a través de ODBC:

root@vm-server-001:~# vim /etc/odbcinst.ini

[MySQL]
Description = MySQL driver
Driver = libmyodbc.so
Setup = libodbcmyS.so
CPTimeout =
CPReuse =
UsageCount = 1

Y siguiendo con la configuración, procedemos a configurar el acceso a mysql para que el driver pueda acceder a las tablas creadas:

root@vm-server-001:~# vim /etc/odbc.ini

[MySQL-asterisk]
Description = MySQL Asterisk database
Trace = Off
TraceFile = stderr
Driver = MySQL
SERVER = localhost
USER = asterisk
PASSWORD = passw0rdDelMysql!!
PORT = 3306
DATABASE = asterisk

Lo siguiente, es definir a Astersisk para que utilice ODBC así que procedemos a editar el archivo de configuración, para ello vamos a /etc/asterisk/res_odbc.conf y editamos del modo siguiente:

root@vm-server-001:~# vim /etc/asterisk/res_odbc.conf

[asterisk] 
enabled => yes 
dsn => MySQL-asterisk 
username => asterisk 
password => passw0rd!! 
pre-connect => yes

Vamos a ver si funciona. ¿Cómo? Conectandonos a la consola de asterisk y haciendo un ‘sip show peers’. ¿Cual es mi resultado? Que no funciona… Así que nos toca mirar el log, y lo que nos damos cuenta es de que tenemos un error en la carga de módulos… Hay que indicarle a Asterisk para que cargue el mysql:

root@vm-server-001:~# vim /etc/asterisk/modules.conf

; Enable these if you want to configure Asterisk in a database
;
load => res_config_odbc.so
load => res_config_pgsql.so

Bien, una vez comprovado que todo nuestro Asterisk está arriba y funcionando a través de mysql, lo siguiente que debemos de hacer es modificar el enrutado de Kamailio para que envie las llamada ahí cuando “algo malo pase” (que no nos conteste el cliente o que esté ocupado), así que procedemos a leer nuestro kamailio.cfg y vemos que, efectivamente, hay un modulo preparado para ello!

#!define WITH_VOICEMAIL

Procedemos a ajustar los parámetros conforme nuestro servidor Asterisk:

#!ifdef WITH_VOICEMAIL
# VoiceMail Routing on offline, busy or no answer
#
# - by default Voicemail server IP is empty to avoid misrouting
voicemail.srv_ip = "92.127.133.213" desc "VoiceMail IP Address"
voicemail.srv_port = "5060" desc "VoiceMail Port"
#!endif

Pero claro, nosotros lo queremos customizado, es decir, vamos a modificar un poco el tema de la no respuesta, dado que en Kamailio trabajamos con usernames, pero nos gustaría en asterisk identificar a los clientes por su DID, así que añadimos la traducción, tal y como hemos hecho en posts anteriores. Así queda la ruta de TOVOICEMAIL:

 route[TOVOICEMAIL] {
#!ifdef WITH_VOICEMAIL
 if(!is_method("INVITE|SUBSCRIBE"))
 return;

 # check if VoiceMail server IP is defined
 if (strempty($sel(cfg_get.voicemail.srv_ip))) {
 xlog("SCRIPT: VoiceMail rotuing enabled but IP not defined\n");
 return;
 }
 if(is_method("INVITE")) {
 if($avp(oexten)==$null)
 return;

 # MODIFIED: Poner aquí el tema de que si se trata de un interno, poner claramente que no es el useragent
 # si no el DID al que se ha llamado. En Asterisk vamos a poner el DID como rquested URI para 
 # poder trabajar con ello.

 if(avp_db_query("select alias_username from dbaliases where username='$rU' and domain='$fd'","$avp(callerid)")){
 xlog("*****DEBUGTONI: Enviando llamada a Voicemail dado que cliente no responde o no disponible $avp(callerid) ****\n");
 $ru= "sip:"+$avp(callerid)+"@"+$sel(cfg_get.voicemail.srv_ip)
 + ":" + $sel(cfg_get.voicemail.srv_port);
 }
 else{
 $ru = "sip:" + $avp(oexten) + "@" + $sel(cfg_get.voicemail.srv_ip)
 + ":" + $sel(cfg_get.voicemail.srv_port);
 }
 } else {
 if($rU==$null)
 return;

 $ru = "sip:" + $rU + "@" + $sel(cfg_get.voicemail.srv_ip)
 + ":" + $sel(cfg_get.voicemail.srv_port);
 }

 route(RELAY);
 exit;
#!endif

 return;
}

Esto está genial, pero ojo! Recordemos que en mi caso lo primero que hago es mirar si está en el route[LOCATION] y si no, intentar en el route[PSTN], por ello, si llamo a un número que no existe en nuestro dbaliases, no pasará del route[LOCATION], y este me lo intente enviar a nuestro Asterisk (fail, yo lo que quiero es llamar fuera de mi dominio). Por esto tengo que pulir la configuración .

Para que las llamadas salientes sigan saliendo correctamente, sólo deberemos de enviar las llamadas cuando existan en nuestro dbaliases y no se encuentren en el location, así que en el route[LOCATION] debemos de insertar lo siguiente antes de enviar a route[TOVOICEMAIL].

 if(avp_db_query("select alias_username from dbaliases where username='$rU' and domain='$fd'")) { 
 route(TOVOICEMAIL);
 }

Resumiendo: Si no existe en el dbaliases quiere decir que el DID al que se llama no está gestionado por mi, con lo cual, seguro que no tengo voicemail para el, y lo que debo de hacer es enviarlo a mis carriers.

Luego vamos al dialplan de Asterisk y comenzamos a darle caña con la configuración, lo primero es definir en nuestro sip.conf el troncal para recibir llamadas de nuestro kamailio.

[fromsbc](!) ; a template
 dtmfmode=rfc2833
 context=from-kamailio
 type=peer
 directmedia=no

[codecs](!) ; a template for my preferred codecs
 disallow=all
 allow=alaw
 allow=ulaw
 allow=gsm

[aclserver](!)
 deny=0.0.0.0/0.0.0.0
 permit=92.127.0.0/16
 permit=172.16.0.0/16

[sipproxy001](fromsbc,codecs)
 host=92.127.133.214

Una vez vemos que entran las llamadas, lo siguiente es procesarlas. Para ello hemos creado un contexto llamado [from-kamailio] donde procesaremos estas llamadas y las enviaremos al voicemail:

[from-kamailio]

exten => _9XXXXXXXX,1,NoOp("Llamada entrante buscando Voicemail de TONI TELECOM para ${EXTEN}")
same => n,Voicemail(${EXTEN},u)
same => n,GoToIf($["${VMSTATUS}"=="FAILED"]?fail,1)
same => n,Hangup()

exten => fail,1,NoOp(*Client with out VM configured*)
same => n,PlayBack(number-not-answering&or&is-currently&on-busy&please-try-again-later&&tt-monkeys)
same => n,Hangup()

exten => 90,1,NoOp(**TODO EN PARTE 2: Desactivar VM**)
same => n,Hangup

exten => 91,1,NoOp(**TODO EN PARTE 2: Activar VM**)
same => n,Hangup

¡Importante también! Las voces las queremos en castellano, así que nos las bajaremos de http://voipnovatos.es (¡el amigo Alberto aún las tiene colgadas!):

root@vm-server-001:/usr/src# wget https://www.voipnovatos.es/voces1.2/asterisk-voces-es-v1-711a-voipnovatos.zip

unzip asterisk-voces-es-v1-711a-voipnovatos.zip

cp -rv es/ /usr/share/asterisk/sounds/es_custom

No nos olvidaremos de en el sip.conf seleccionar este dentro de su sección [general]:

language=es_custom

Y ya para acabar lo que vamos a hacer es insertar usuarios en voicemail para que nos envíe un correo electrónico cuando llegue la llamada. Como podéis ver, todos los números los he reedirijido contra mi correo personal, dado que estamos en modo testing:

INSERT INTO voicemail(context, mailbox, password, email) VALUES ('default', '930000000', '1234','tibanezlujan@gmail.com');
 INSERT INTO voicemail(context, mailbox, password, email) VALUES ('default', '930000001', '1234','tibanezlujan@gmail.com'); 
INSERT INTO voicemail(context, mailbox, password, email) VALUES ('default', '930000002', '1234','tibanezlujan@gmail.com');
INSERT INTO voicemail(context, mailbox, password, email) VALUES ('default', '930000003', '1234','tibanezlujan@gmail.com');
INSERT INTO voicemail(context, mailbox, password, email) VALUES ('default', '930000004', '1234','tibanezlujan@gmail.com');

Llamo, me salta la locución… Pero vaya… No me llega nada… Claro, lo que me falta es configurar el servidor para que pueda enviar correos. Así que procedemos a instalar y configurar el exim4 para que envie mails:

root@vm-server-001:/etc/asterisk# apt-get install exim4

root@vm-server-001:/etc/asterisk#dpkg-reconfigure exim4-config

Seguimos pasos de https://wiki.debian.org/Exim y hecho, ya funciona nuestro servicio:

root@vm-server-001:/etc/asterisk#sendmail tibanezlujan@gmail.com < /tmp/email.txt

Volvemos a intentar dejar un mensaje, y efectivamente! El voicemail para los clientes que tenemos en Kamailio ya funciona!!! Por cierto, para configurar el correo que recibimos (el texto, subject,etc…) hemos de editar /etc/asterisk/voicemail.conf.

Bueno, una vez esto listo, debemos de proceder a parte 2: Cómo hacer que nuestros usuarios puedan activar y desactivar el buzón de voz.

¡Espero que os haya sido de utilidad!

Referencias:

http://kb.asipto.com/asterisk:realtime:kamailio-4.0.x-asterisk-11.3.0-astdb

http://www.voip-info.org/wiki/index.php?page_id=1738&comments_page=1

http://www.kamailio.org/dokuwiki/doku.php/asterisk:voicemail-system

https://wiki.debian.org/Exim

Kamailio – Instalación de RTPProxy

Muchos de nuestros cientes pueden encontrarse en escenarios diferentes y complicados, pudiendo esto afectar a nuestras comunicaciones de voz (de voz, refiriendome a audio). Para ello, y siguiendo con los posts que estamos realizando de Kamailio, vamos a tratar de instalar junto a nuestro SIP Proxy un servidor Proxy RTP.

¿En qué nos ayuda el servidor Proxy RTP? ¿Porqué utilizarlo? Os dejo algunas de las características en las que nos puede ayudar nuestro Proxy RTP:

* NAT / VoIP a través de Firewalls

* Retransmisión de voz, video o cualquier flujo de datos

* Reproducir anuncios in-band precodificados

* Reentramado de payloads para RTP

* Optimizar el flujo de paquetes

* Enrutar llamadas de voz sobre links VPN

* Copiar en tiempo real flujos de voz

¿Viene nuestro Kamailio preparado para ello? La respuesta es sí, y sólo hace falta abrir el archivo kamailio.cfg para ver que es así. En este caso aparece un módulo llamada WITH_NAT donde se puede ver referencia a rtpproxy.

# *** To enable nat traversal execute:
# - define WITH_NAT
# - install RTPProxy: http://www.rtpproxy.org
# - start RTPProxy:
# rtpproxy -l _your_public_ip_ -s udp:localhost:7722
# - option for NAT SIP OPTIONS keepalives: WITH_NATSIPPING

¡Cuidado! Nosotros vamos a trabajar con RTPProxy, pero existen otros softwares como erlrtpproxy o MediaProxy que se podrían utilizar para ello. En nuestro caso, vamos a utilizar lo estandard, y lo recomendado por Kamailio (vamos a ir a lo fácil). Así que optaremos por RTPProxy.

Vamos a instalarlo, mi primera sorpresa ha sido esta… ¡Está en los repositorios! Así que no me he complicado demasiado la vida y he ido a lo rápido.

root@sipproxy001:~# apt-cache search rtp | grep proxy
rtpproxy - Relay for Real-time Transport Protocol (RTP) media streams
root@sipproxy001:~#
root@sipproxy001:~# apt-get install rtpproxy
[...]
root@sipproxy001:~#/etc/init.d/rtpproxy stop

Una vez instalado, lo que he realizado es peraparar el Kamailio para que comience a trabajar junto a RTPProxy. Añadimos en los defines:

#!define WITH_NAT

Luego miramos los parametros del modulo WITH_NAT y analizamos un poco de qué va todo esto. Cómo se puede ver, nosotros vamos a trabajar con un SIP Proxy y el RTPProxy en la misma máquina, pero podría no ser así.

#!ifdef WITH_NAT
# ----- rtpproxy params -----
modparam("rtpproxy", "rtpproxy_sock", "udp:127.0.0.1:7722")
# ----- nathelper params -----
modparam("nathelper", "natping_interval", 30)
modparam("nathelper", "ping_nated_only", 1)
modparam("nathelper", "sipping_bflag", FLB_NATSIPPING)
modparam("nathelper", "sipping_from", "sip:pinger@voip001.netvoip.no-ip.org")
# params needed for NAT traversal in other modules
modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)")
modparam("usrloc", "nat_bflag", FLB_NATB)
#!endif

Algunos comandos interesantes son:

natping_interval: Cada cuanto lanzamos un ping desde nuestro RTPProxy hacia el cliente, para mantener conexión de firewall abierta

ping_nated_only: Define si sólo vamos a hacer ping a los User Agents (UAs) que estén detrás de un NAT.

sipping_bflag: Define que rama/branch vamos a utilizar para hacer pings a los UAs que se encuentren nateados.

Una vez realizado esto, guardamos la configuración y procedemos a arrancar el servicio:

root@sipproxy001:~#rtpproxy -l 92.127.133.214 -s udp:localhost:7722

Y no funciona… Pero leemos lo siguiente: “note: you can not run RTPproxy as superuser, switch to non-privileged user before starting RTPproxy server”. Pues eso, que debemos de arrancar el servicio siendo otro usuario, y al instalar por apt se nos ha creado un usuario llamado rtpproxy.

En caso contrario, lo creamos:

root@sipproxy001:~#adduser rtpproxy --home /home/rtpproxy --shell /bin/false

Y en ambos casos, volvemos a ejecutar el servicio arrancando desde usuario rtpproxy:

root@sipproxy001:~# rtpproxy -u rtpproxy -l 92.127.133.214 -s udp:localhost:7722

Reiniciamos Kamailio para que funcione todo a la vez…

root@sipproxy001:~# /etc/init.d/kamailio stop
 * Stopping Kamailio SIP Server: kamailio [ OK ] 
root@sipproxy001:~# /etc/init.d/kamailio start
 * Starting Kamailio SIP Server: kamailio loading modules under config path: /usr/lib/x86_64-linux-gnu/kamailio/modules/
Listening on 
 udp: 127.0.0.1:5060
 udp: 91.127.133.214:5060
 tcp: 127.0.0.1:5060
 tcp: 91.127.133.214:5060
Aliases: 
 *: voip001.netvoip.no-ip.org:5060:*
 *: voip001.netvoip.no-ip.org:5060:* 
 *: voip001.netvoip.no-ip.org:5060:*
 [ OK ] 
root@sipproxy001:~#

Y procedemos a hacer llamadas. ¿Cómo saber si funciona o no? Pues como siempre, nada como hacer ngrep y ver qué esta haciendo nuestro servidor. En mi caso, al realizarlo he notado que el tráfico RTP comienza a pasar por la máquina del SIP Proxy y que algunos campos de los paquetes SIP en el INVITE se han modificado:

Sin RTPProxy, lo que vemos que en el INVITE enviado desde el SIP Proxy hacia la UA de destino tienen estos valores en los campos:

Record-Route: <sip:92.127.133.214;lr>

Connection Information (c): IN IP4 92.127.133.214 >> @IP de UA donde se ha inciado la llamada

 

Con RTPProxy, podemos ver que n el INVITE enviado desde el SIP Proxy hacia la UA de destino tienen estos valores en los campos:

Record-Route: <sip:92.127.133.214;lr;nat=yes>

Connection Information (c): IN IP4 92.127.133.235 >> @IP de SIP Proxy donde corre el RTPProxy

Si hacemos captura, veremos lo siguiente, como se mueve el audio entre dos:

U 92.127.133.214:41610 -> 92.127.133.217:5012
...>.:..t.^.....}vlhhmw.....wsnlieb^]_kz........zth}jmpk~s}|}.y..vzw.sutn{x...........tjinvt|..}tytrnie_^_ek.........{numxhhnj}...t...zsqnyxon|..........umkjox|....~}zrif_\
#
U 92.127.133.217:5012 -> 92.127.133.214:41610
..\...G.{...r}w.....~.|......wtqtyz......uvtt~y}~}.........xpmjiikow.........owtv|t~y...........vquoru.....||qvppuq~z|.{........tmmjonput~~....}.~.}{.....}~..~~}~}}}}}..~~~
#
U 92.127.133.214:41610 -> 92.127.133.217:5012
...?.:.xt.^.^ceo...........xoojomkrx.....}..}usomt...........xnigkmo}.......tia^^`dn............~qiiginnppv~.......{x|{{..........{y|yquxrsxzxwtnjiklo{.............zwonnopn
#
U 92.127.133.217:5012 -> 92.127.133.214:41610
..]...H.{.....}~..~~..~~~~}~.}..~.......~~~~~~~}~.}..~.~}.~~~~~~~~~~~~....~~.~~~~~~~~~~~~.............~~~~~~~~~~~~~~~~}~.~~~....~~~~~~.~}.~~~......~~~~}}}}.~~~~~...~}.....~
#
U 92.127.133.214:41610 -> 92.127.133.217:5012
...@.:..t.^.rqpw{.............~......}|.{yxvuvvsrqmijknt~.............ztpklmptwzxvx{z...................|}yyztonnllnmjhhkn..............{sqnlnqsx|{xvvxw{~|~..........|yvrsu
#
U 92.127.133.217:5012 -> 92.127.133.214:41610
..]...I {...~~~~~~~~~~~~.....~~.~~~~~~~~~~~~~}~.~~~~}~.....~~~~~~}}~~~~~~.~~..~~~.~~.~}}}}~~~~~~............~~~~~....~~~}}~~~~~~}~...~~~.~}.~~.~~.~}~..~~}~~~..~~~.~~~~~~~}~
#
U 92.127.133.214:41610 -> 92.127.133.217:5012
...A.:..t.^.zz{.zutsnlljfefim............~zrnonpz....yvtuvuwspqw.........xsonklmox}...}somiebcdjz.........|~}{wsyuz....~qnmnosyvvx|.........wrommmnpx.....tnkigedeip........
#
U 92.127.133.217:5012 -> 92.127.133.214:41610
..]...I.{....~.~}..~~.}~.}..~....~~~}~}}~~~~......~~.~~~~~~}}~}}~}~~~.}~..........~}}}}~~~~~~~....~~..~~~.~}..~~~~.~~~~~..}~~~~~~~~~}~.~.~~.}~~~~~~~~~~~~~~~~~~~....~~~..~~~
#

Bueno, ¡espero que os haya sido de utilidad el post!

Sources:

http://kamailio.org/docs/modules/4.0.x/modules/rtpproxy.html

http://www.rtpproxy.org/

http://sourceforge.net/p/sippy/rtpproxy/ci/master/tree/

http://voiprookie.blogspot.com.es/2009/04/rtpproxy-12x-installation.html

http://nil.uniza.sk/sip/kamailio/rtp-proxy-kamailio-32-configuration-debian-squeeze

http://www.iptel.org/ser/howtos/optimizing_the_use_of_rtp_proxy

Kamailio – Identificador de llamadas entre SIPUsers formato E.164 a través de dbaliases

Este post no viene a ser más que una mera extensión del anterior, dado que como ya vimos, sí que podíamos realizar llamadas al exterior de forma anónima y con format E.164, pero cuando nos llamabamos entre SIPUsers del mismo dominio, o que eran del mismo Kamailio, seguía apareciendo el SIPUserName, que podía coincidir (o no) con nuestro DID.

Para ello lo que hemos realizado es una modificación dentro de nuestra rutina route[LOCATION] para que se evalue la request de la siguiente forma:

(1) En primera instancia, si se trata de un número que está en dbaliases, procedemos a buscar en dbaliases como se identifica el llamante mediante DID y sobreescribimos el from, a demás de añadir los headers de Identity.

(2) En caso de que la llamada comience por 067, hacemos un strip de este código, buscamos en el dbaliases. Si está, procesmos la llamada cambiando el from y añadiendolo como anónima (headers de Identity incluídos).

(3) Por otro lado, en caso de que no se haga un match con las condiciones uno o dos, lo que procedemos a hacer es enviarlo a la PSTN donde en el post anterior ya definimos cómo tratar la llamada, respetando en todo momento la requestURI del llamante (si tenía 067 delante, lo volvemos a dejar).

Traduciendo, esto se convierte en esto:

#!ifdef WITH_ALIASDB
 # search in DB-based aliases
 if(alias_db_lookup("dbaliases")){

 #MODIFIED: Añadimos cambiar el identificador de llamada para que muestre el
 # DID para llamadas entre usuarios de ADAMO TELECOM
 if(avp_db_query("select alias_username from dbaliases where username='$fU' and domain='$fd'","$avp(callerid)")){
 $fu= "sip:"+$avp(callerid)+"@"+$(fu{uri.domain});
 $var(pidentity_a)="tel:"+$avp(callerid)+"\n";
 $var(pidentity_p)="\"ToniKamailio <"+$avp(callerid)+">\" <sip:"+$avp(callerid)+"@"+$(fu{uri.domain})+">\n";
 append_hf("P-Asserted-Identity: $var(pidentity_a)","Call-ID");
 append_hf("P-Preferred-Identity: $var(pidentity_p)","Call-ID");
 route(SIPOUT);
 }
 else{
 sl_send_reply("428", "Use Identity Header.");
 exit;
 }
 }

 #MODIFIED: Para añadir la opción de llamada privada entre usuarios de Kamalio
 # miramos que la llamada comience por 067 y que sea de nuestro dominio
 # y procedemos a tratarla

 if (uri==myself && ($rU=~"^(067)(6|7|8|9)[0-9][0-9]{5,20}$")){ 

 xlog ("*****DEBUGTONI: Miramos si la llamada es para un aliases\n");
 $var(calledNum)=$(rU{s.strip,3});
 $var(calledNumOriginal)=$rU;
 $rU=$var(calledNum);

 if(alias_db_lookup("dbaliases")){
 xlog ("*****DEBUGTONI: Llamada hacia el interior dado que esta en dbaliases\n");
 $fu="sip:anonymous@anonymous.invalid";
 $var(pidentity_a)="tel:"+$avp(callerid)+"\n";
 $var(pidentity_p)="\"ToniKamailio <"+$avp(callerid)+">\" <sip:"+$avp(callerid)+"@"+$(fu{uri.domain})+">\n";
 append_hf("P-Asserted-Identity: $var(pidentity_a)","Call-ID");
 append_hf("P-Preferred-Identity: $var(pidentity_p)","Call-ID");
 append_hf("Privacy: id\n","Call-ID");
 route(SIPOUT);
 }
 else{
 $rU=$var(calledNumOriginal);
 }
 }

#!endif

¡Espero que os sea de ayuda!

Kamailio – Identificador de llamadas salientes E.164 a través de dbaliases

Nuestro problema/reto a intentar solventar hoy es cómo identificar las llamadas salientes a través de un DID que tengamos asignado para nuestro SIP End Point en el dbaliases. Hasta ahora, lo que nos ha ocupado sobre esta tabla era muy sencillo: entra un número con un formato E.164 y queremos reenviar esto a nuestro cliente, con lo cual traducíamos de un número tal y como 34930000000 a robert.voip001.netvoip.no-ip.org.

Pero, ¿y cuando robert.voip001.netvoip.no-ip.org llama al exterior, nosotros debemos de asignarlo a este número? ¿Cómo hacerlo para que no salga anónimo o el número 1000? (en caso de que sea 1000 ese el SIP Username). Pues vamos a ello.

Lo primero que vamos a hacer es cargar un módulo que nos permita interactuar a nuestras variables con nuestras tablas mysql.

loadmodule "avpops.so"

Una vez cargado el módulo, procedemos a asignar los valores que necesitamos para que funcione:

# ----- avpops params ----
modparam("avpops","db_url","mysql://kamailio:p4ssw0rd3t3st1ng!@localhost/kamailio")
modparam("avpops","avp_table","dbaliases")

Y ahora comenzamos con nuestra lógica de llamadas, es decir, tocando el route[PSTN] para que las llamadas exteriores se cursen de modo adecuado. Lo que queremos es que cuando se llame al exterior, se consulte la tabla de dbaliases y se reescriba el identificador de llamada saliente.

Adjunto toda mi rutina de route[PSTN] para poder hacer esto:

route[PSTN] {
#!ifdef WITH_PSTN
 # check if PSTN GW IP is defined
 if (strempty($sel(cfg_get.pstn.gw_ip))) {
 xlog("SCRIPT: PSTN rotuing enabled but pstn.gw_ip not defined\n");
 return;
 }

 # Dejamos que se llamen a los números que están marcados correctamente
 # en caso contrario, le decimos que tienen un mal formato y rechazamos.
 # El 067 lo metemos para que salga identificado sin callerID
 if(!($rU=~"^(\+|00)[1-9][0-9]{6,20}$") && !($rU=~"^(6|7|8|9)[0-9][0-9]{5,20}$") && !($rU=~"^(067)(6|7|8|9)[0-9][0-9]{5,20}$")){
 sl_send_reply("408", "Not Allowed - Bad Format Number.");
 xlog("*****DEBUGTONI: ERROR IN FORMAT NUMBER\n");
 exit;
 }

 # Con este prefijo no dejamos que se llamen a números de tarificacion especiales
 # de este modo nos ahorramos problemas en invoicing
 if(($rU=~"^(8|9)[0][3-7]{6,20}$") || ($rU=~"^(067)(8|9)[0][3-7]{6,20}$")){
 sl_send_reply("409", "Not Allowed - Special Number.");
 xlog("*****DEBUGTONI: SPECIAL NUMBER NOT ALLOWED\n");
 exit;
 }

 # Solo permitir llamadas desde nuestros dominios
 if(from_uri!=myself) {
 sl_send_reply("403", "Not Allowed");
 exit;
 }

 if (strempty($sel(cfg_get.pstn.gw_port))) {
 $ru = "sip:" + $rU + "@" + $sel(cfg_get.pstn.gw_ip);
 } else {
 $ru = "sip:" + $rU + "@" + $sel(cfg_get.pstn.gw_ip) + ":"
 + $sel(cfg_get.pstn.gw_port);
 }
 xlog ("*****DEBUGTONI: Usuario llamando a $rU desde $fu con un tag $tu \n");
 $var(a) = $(fu{uri.user});
 xlog ("*****DEBUGTONI: User llamando con caller ID: $var(a)\n");

 # Aquí ponemos el caller ID a través del cual debemos de mostrar 
 # al destino. Para ello utilizamos el P-Asserted-Identity, el 
 # P-Preferred-Identity y modificamos el from, dado que muchos sistemas
 # lo utilizan para ello.
 if (avp_db_query("select alias_username from dbaliases where username='$fU' and domain='$fd'","$avp(callerid)")){
 xlog("*****DEBUGTONI: Sobreescribimos el uri user del from a $avp(callerid)\n");

 #Llamada normal , es decir, sin tratamiento de privacidad.
 if(!($rU=~"^(067)[0-9]{5,20}$")){
 $fu= "sip:"+$avp(callerid)+"@"+$(fu{uri.domain});
 }

 #Llamada con numero oculto, para lo cual se ha prefijado el
 # código 067 delante del numero marcado
 else{
 $var(calledNum)=$(rU{s.strip,3});
 $rU= $var(calledNum);
 $tu="sip:"+$var(calledNum)+"@"+$(tu{uri.domain})+";"+$(tu{uri.params});
 $fu="sip:anonymous@anonymous.invalid";
 }

 $var(pidentity_a)="tel:"+$avp(callerid)+"\n";
 $var(pidentity_p)="\"ToniKamailio <"+$avp(callerid)+">\" <sip:"+$avp(callerid)+"@"+$(fu{uri.domain})+">\n";

 append_hf("P-Asserted-Identity: $var(pidentity_a)","Call-ID");
 append_hf("P-Preferred-Identity: $var(pidentity_p)","Call-ID");

 # Si se ha prefijado el 067 le insertamos privacidad
 if(($rU=~"^(067)[0-9]{5,20}$")){
 xlog ("*****DEBUGTONI: User ANONIMAMENTE a llamando a $rU and this is to: $tu\n");
 append_hf("Privacy: id\n","Call-ID");
 }
 }
 else{
 sl_send_reply("428", "Use Identity Header.");
 exit;
 }

 route(RELAY);
 exit;
#!endif

 return;
}

Si nos fijamos bien, lo que he tenido que realizar es cambiar el $fu (fromUri) y añadir dos campos en el header de SIP (P-Asserted-Identity y P-Preferred-Identity). Depende de cual sea vuestro carrier, con el P-Assert-Identity debería de ser suficiente, pero en mi caso he tenido que rehacer el fromUri.

Como veréis, además de esto, he añadido una subrutina/subcódigo para que prefijando con 067 el número llamante salga como oculto. Echadle un vistazo al RFC 3325 el cual trata sobre Asserted Identity (desgraciadamente, el RFC no lo cumple nadie… Pero bueno,  es un punto de referencia muy importante y ayuda a ver qué estamos haciendo).

Cuidado, para que no fallen las llamadas iniciadas por nuestros SIP End Points, hemos cambiado el orden en el enrutado general, para que miren antes si este DID E.164 está en el location table (es una llamada intra dominio) y en caso contrario que lo envie a través de PSTN:

 # MODIFIED: Primero miramos si está en location table, y de ser así, procedemos a llamar.

 # user location service
 route(LOCATION);

 # dispatch destinations to PSTN
 route(PSTN);

Por otro lado, y para evitar sustos y funcionamientos erroneos, hemos modificado parte del enrutamiento de route[LOCATION], para que las llamadas salientes sólo sean de cuando uno de nuestros SIP End Points no encuentre el número, y evitar que terceros utilicen nuestra ruta PSTN si son ajenos a nuestros dominios SIP:

 # MODIFIED: Comentado para que vaya hacia fuera en caso
 # de no estar en location busque en PSTN, siempre y cuando la llamada
 # sea originada desde nuestro dominio.
 if (uri==myself){
    route(PSTN);
 }
 else{
    send_reply("404", "Not Found");
 }
 exit;

Por otro lado, las llamadas ocultas, tal y como se ha configurado, sólo funcionan para llamadas salientes, pero vamos, que simplemente es necesario aplicar las mismas políticas n el route[LOCATION] para que funcionen de modo interno.

¡Espero que os sea de utilidad! La verdad es que me lo he pasado muy bien siguiendo el flujo de llamadas de Kamailio en su archivo de configuración, y es un ejercicio que recomiendo.

Resources:

http://kamailio.org/docs/modules/3.1.x/modules/avpops.html#id2532762

http://www.kamailio.org/docs/modules/3.1.x/modules_k/textops.html

http://www.kamailio.org/wiki/cookbooks/devel/transformations

http://www.kamailio.org/wiki/cookbooks/3.2.x/transformations

http://kamailio.org/dokuwiki/doku.php/pseudovariables:3.1.x

http://lists.sip-router.org/pipermail/users/2009-July/023972.html

http://en.wikipedia.org/wiki/E.164

Kamailio – Enrutando llamadas hacia nuestro provider o SBC

Nuestro primer objectivo es levantar un troncal entre un Asterisk y nuestro Kamailio autenticado por IP. Para ello, lo primero que vamos a hacer es añadir salida PSTN en nuestro archivo de kamailio.cfg.

Para ello, simplemente tenemos que pasearnos por su archivo de configuración y leer. La adición de troncales para llamadas a través de PSTN (SIP Trunks) permite uno sólo uno por defecto (en post posteriores veremos cómo hacer LCRs y añadir otras salidas).

 # *** To enable PSTN gateway routing execute:
 # - define WITH_PSTN
 # - set the value of pstn.gw_ip
 # - check route[PSTN] for regexp routing condition

Aquí lo ponemos la dirección IP hacia la que queremos enviar la llamada saliente, en nuestro caso 92.127.91.91:

>> #!ifdef WITH_PSTN
>> # PSTN GW Routing
>> #
>> # - pstn.gw_ip: valid IP or hostname as string value, example:
>> # pstn.gw_ip = "10.0.0.101" desc "My PSTN GW Address"
>> #
>> # - by default is empty to avoid misrouting
>> pstn.gw_ip = "92.127.91.91"
>> pstn.gw_port = "5060"
>> #!endif

Luego editamos la parte de PSTN dentro de la lógica de llamadas, y aceptamos qué queremos que acepte y envíe. para nuestro caso, lo que hemos hecho es dehar que se llame a todo lo que comience por +XX o por 00 en el primer if, así como por 6,7,8 y 9. En caso de que no comience por estos números, lo que hacemos es descartar esta llamada con un “408 – Toni Not Allowed – Bad Format Number.”

Por otro lado, los números 906 y especiales (de pago añadido) tambióen los descartamos en el siguiente if, respondiendo con un “409 – Toni Not Allowed – Special Number.”

route[PSTN] {
#!ifdef WITH_PSTN
 # check if PSTN GW IP is defined
 if (strempty($sel(cfg_get.pstn.gw_ip))) {
 xlog("SCRIPT: PSTN rotuing enabled but pstn.gw_ip not defined\n");
 return;
 }
 # route to PSTN dialed numbers starting with '+' or '00'
 # (international format)
 # - update the condition to match your dialing rules for PSTN routing
 if(!($rU=~"^(\+|00)[1-9][0-9]{6,20}$") && !($rU=~"^(6|7|8|9)[0-9][0-9]{5,20}$")){
 sl_send_reply("408", "Toni Not Allowed - Bad Format Number.");
 xlog("**************************SCRIPT: ERROR IN FORMAT NUMBER\n");
 return;
 }
if(($rU=~"^(8|9)[0][3-7]{6,20}$")){
 sl_send_reply("409", "Toni Not Allowed - Special Number.");
 xlog("**************************SCRIPT: SPECIAL NUMBER NOT ALLOWED\n");
 return;
 }
# only local users allowed to call
 if(from_uri!=myself) {
 sl_send_reply("403", "Not Allowed");
 exit;
 }
if (strempty($sel(cfg_get.pstn.gw_port))) {
 $ru = "sip:" + $rU + "@" + $sel(cfg_get.pstn.gw_ip);
 } else {
 $ru = "sip:" + $rU + "@" + $sel(cfg_get.pstn.gw_ip) + ":"
 + $sel(cfg_get.pstn.gw_port);
 }
route(RELAY);
 exit;
#!endif
return;
}

Espero que os haya sido de utilidad, y recordad que los endpoints (en este caso vuestro Asterisk y vuestro Teléfono VoIP, por si os llegasen mensajes de tipo “488 – Not acceptable here.”.

IMPORTANTE:

Si vamos a utilizar DIDs para llamar entre nuestros usuarios, es necesario que cambiemos el orden en request_route para route(PSTN) y route(LOCATION), dado que si no, lo primero que hacemos es enviar la llamada hacia el exterior, sin mirar antes si la tenemos en nuestro dominio.

# user location service: Primero miramos si está en location table, y de ser así, procedemos a llamar. En caso contrario, lanzamos llamada contra PSTN.

 route(LOCATION);

# dispatch destinations to PSTN, ya que no está en LOCATION nuestro número.
 route(PSTN);

# # user location service, comentado por motivos explicados anteriormente.
# route(LOCATION);