SIPp – Benchmarking servidores VoIP

SIPp (http://sipp.sourceforge.net/) es una herramienta opensource de prueba que permite inyectar tráfico SIP contra servidores de los cuales deseemos evaluar el rendimiento. Este programa puede actuar tanto actuando como UAC (User Agent Client) o UAS (User Agent Server), de tal forma que es capaz de generar tráfico, así como de reponder a él como si de un telefono o servidor Asterisk se tratase.

Gracias a unos ficheros XML, de los cuales mostraremos alguno, esta herramienta, que está disponible en los repositiorios de debian (ubuntu), permite establecer multiples llamadas y/o registros entre diferentes entidades. Estos ficheros XMLs pueden describir desde simples flujos de llamadas hasta algunos de mayor complejidad (dependerá de nuestro nivel y ganas).

Permite conexiones SIP sobre TCP o UDP, y es totalmente customizable. SIPP también puede enviar tráfico de audio (inyección de RTP) lo cual es muy interesante para poder analizar cuando se degrada el audio, por ejemplo, y/o analizar que nuestro servidor se encuentra completamente operativo.

Es una herramienta muy completa, y en el post actual voy a intentar hacer unas pruebas de concepto para familiarizarme con el, y poderlo utilizar regularmente como herramienta de debug.

Este software debe de ser utilizado con responsabilidad, y en ningún caso para realizar ataques contra terceras entidades. Tal y como yo lo veo, ha de ser una herramienta que nos permita calibrar la capacidad de nuestras máquinas, y así poder ver cuantos recursos tenemos disponibles y el comportamiento de centralitas y SBCs bajos condiciones extremas.

(1) Instalación de SIPP: Bajamos, descromprimimos e instalamos.

Instalamos SIPP, para comenzar a trabajar contra nuestro servidor. Así que abrimos una terminal, y si lo queremos hacer desde source code, nos deberemos de bajar el tar.gz, descomprimirlo, y realizar el ./configure y el make de lo que deseemos

toni@netvoip-sbc001:/home/toni# wget http://sourceforge.net/projects/sipp/files/sipp/3.4/sipp-3.3.990.tar.gz/download

toni@netvoip-sbc001:/home/toni# tar -xvzf sipp-3.3.990.tar.gz

toni@netvoip-sbc001:/home/toni# cd sipp-3.3.990

toni@netvoip-sbc001:/home/toni/sipp-3.3.990#./configure

En el paso de configure, podremos elegir qué soporte queremos para nuestro sipp. En cualquier caso, durante el proceso de instalación cabe destacar que yo tuve que ir haciendo apt-get installs de librerias y dependencias que faltaban.

toni@netvoip-sbc001:/home/toni/sipp-3.3.990#./configure --with-sctp --with-pcap

Instalamos SIPP a através de repositorios, o bajando del source. De modo simple:

toni@netvoip-sbc001:/home/toni#sudo apt-get install sipp

Una vez hecho esto, podemos ver si lo tenemos corriendo del siguiente modo:

toni@netvoip-sbc001:~$ sipp -v
SIPp v3.4.1-RTPSTREAM built Dec 29 2015, 16:36:44.
This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as
 published by the Free Software Foundation; either version 2 of
 the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.
You should have received a copy of the GNU General Public
 License along with this program; if not, write to the
 Free Software Foundation, Inc.,
 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Author: see source files.
toni@netvoip-sbc001:~$

(2) Bajamos y/o generamos un XML para trabajar

La verdad es que me bajé un XML, con el objetivo de generar registros, pero no me funcionó demasiado bien (básicamente no era capaz de hacerlo funcionar, así que me puse a tuneralo un poco y me funcionó contra el Asterisk). El XML que comparto lo he bajado de OpenVoip (http://www.open-voip.org/index.php?title=SIPP_UAC_and_asterisk). Es muy interesante esta página.

 <?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<!-- -->
<!-- Sipp Rgistration scenario. -->
<!-- -->
<scenario name="Basic Sipstone UAC">
 <!-- ********************************* -->
 <!-- ***** Send Register packet ****** -->
 <!-- ********************************* -->
 <send retrans="500">
 <![CDATA[

 REGISTER sip:[remote_ip]:[remote_port] SIP/2.0
 Via: SIP/2.0/[transport] [local_ip]:[local_port];port;branch=branchTag.[call_id]
 Route: <sip:[remote_ip]:[remote_port];transport=[transport];lr>
 Max-Forwards: 70
 From: <sip:[service]@[remote_ip]>;tag=[call_number]
 To: <sip:[service]@[remote_ip]>
 Call-ID: [call_id]
 CSeq: 2 REGISTER
 User-Agent: sipp-testing-reg
 Contact: <sip:[service]@[local_ip]:[local_port];ob>
 Expires: 7200
 Content-Length: 0

 ]]>
 </send>
<!--
Contact: <sip:[service]@[local_ip]:[local_port]>;transport=[transport]
-->

<!-- ******************************************** -->
<!-- ***** Getting 100 message is optional ****** -->
<!-- ******************************************** -->
 <recv response="100"
 optional="true">
<!-- ***** Must get 401. auth="true" to take the challenge into account ****** -->
 </recv>
 <recv response="401" auth="true">
 </recv>
<!-- ********************* -->
<!-- ***** Send Ack ****** -->
<!-- ********************* -->
 <send>
 <![CDATA[

 ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0
 Via: SIP/2.0/[transport] [local_ip]:[local_port];port;branch=branchTag.[call_id]
 From: <sip:[service]@[remote_ip]:[local_port]>;tag=[call_number]
 To: <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
 Call-ID: [call_id]
 CSeq: 1 ACK
 Contact: sip:[service]@[local_ip]:[local_port]
 Max-Forwards: 70
 Subject: Performance Test
 Content-Length: 0

 ]]>
 </send>
<!-- ********************************************************************************** -->
<!-- ***** Send Registration with the authentication, note the user and password ****** -->
<!-- ********************************************************************************** -->
 <send retrans="500">
 <![CDATA[

 REGISTER sip:[remote_ip] SIP/2.0
 Via: SIP/2.0/[transport] [local_ip]:[local_port];port;branch=branchTag.[call_id]
 From: <sip:[service]@[remote_ip]:[remote_port]>;tag=[call_number]
 To: <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
 Call-ID: [call_id]
 CSeq: 2 REGISTER
 User-Agent: sipp-testing-reg
 Contact: <sip:[service]@[local_ip]:[local_port];ob>
 [authentication]
 Expires: 7200
 Content-Length: 0

 ]]>
 </send>
<!-- ******************************************** -->
<!-- ***** Getting 100 message is optional ****** -->
<!-- ******************************************** -->
 <recv response="100"
 optional="true">
 </recv>
<!-- ****************************** -->
<!-- ***** Should get 200 Ok ****** -->
<!-- ****************************** -->
 <recv response="200" rtd="true">
 </recv>
</scenario>

In order to modify this XML file, I take into account regular REGISTERs sent from a regular user agent, and I just copied the syntax, emulating the behavior.

(3) Hacemos pruebas

Una vez listo nuestro XML procederemos a ejecutar sipp con los comandos necesarios para poder autenticar nuestro UA simulado contra nuestro Asterisk:

Extension to register: 1001

Password extension to register: p4ss0rdn4d4s3gur4

Source IP of the server (Local): 10.0.0.1

Destination Server (Asterisk): server.testing.netvoip.no-ip.org:5060

File to load:

Resumiendo, ejecutamos lo siguiente:

toni@netvoip-sbc001:/home/toni#sipp -s 1001 -ap p4ss0rdn4d4s3gur4 -i 10.0.0.1:5060 -r 10 server.testing.netvoip.no-ip.org:5060 -sf samplesXML/reg_uac.xml

Nos aparecerá una pantalla como la siguiente la cual se ira actualizando en nuestra consola.

sipp

Resultados de SIPP registrandose contra un servidor Asterisk

Lo aconsejable: ir trasteando y no olvidar el man de sipp.

Bueno, espero que os haya parecido de utilidad la herramienta. La verdad es que sólo le veo posibilidades…

Como siempre, os dejo con las referencias!

Sources:

http://sipp.sourceforge.net/

http://sipp.sourceforge.net/doc/reference.html#Create+your+own+XML+scenarios

http://www.open-voip.org/index.php?title=SIPP_UAC_and_asterisk

http://techvick.blogspot.co.uk/2014/09/how-to-intsall-sipp-on-ubuntu.html

https://www.mail-archive.com/sipp-users@lists.sourceforge.net/msg02305.html

Asterisk: baneando IPs “peligrosas” de por paises

Hoy he encontrado una página muy interesante donde se reportan bloques de IP que pueden ser utilizados para realizar ataques. A la hora de securizar nuestro Asterisk, tener estos bloques de IPs denegados, nos ayudará mucho a mitigar malas experiencias, como por ejemplo, fraudes en llamadas.

Mi consejo es siempre banear todas las IPs excepto aquellas que queramos utilizar, conociendo a nuestros clientes, sin embargo, en algunos casos esto no puede ser, y hay que tener nuestro Asterisk abierto a todo el mundo.

¿A todo el mundo? Bueno, a casi todo el mundo. En este post explicaré como poner en funcionamiento un script que he encontrado junto a fail2ban (herramienta que no queremos que se deshabilite).

Este script que he encontrado en http://www.lowendguide.com/ se nutre de la base de datos de http://www.ipdeny.com, donde se pueden encontrar “algunas” (muchas) IPs que no son deseadas ni deseables, y desde las cuales se realizan mucho ataques. Estas están almacenadas por países.

Script:

Este script lo que hace es simplemente conectarse a la página, bajarse un listado de IPs de los países que queramos, y proceder a añadirlas en el IPTables de nuestra máquina. Sí, efectivamente, deberemos tener instalado IPTables en nuestro servidor para poder utilizar dicho script.

Una vez creado este script, el cual tenéis pegado en el siguiente apartado, lo que haremos es crear un fichero, con permisos de ejecución en nuestra máquina e insertar el siguiente código en bash:

#!/bin/bash
### Block all traffic from AFGHANISTAN (af) and CHINA (CN). Use ISO code ###
ISO="af cn"
 
### Set PATH ###
IPT=/sbin/iptables
WGET=/usr/bin/wget
EGREP=/bin/egrep
 
### No editing below ###
SPAMLIST="countrydrop"
ZONEROOT="/root/iptables"
DLROOT="http://www.ipdeny.com/ipblocks/data/countries"
 
cleanOldRules(){
$IPT -F
$IPT -X
$IPT -t nat -F
$IPT -t nat -X
$IPT -t mangle -F
$IPT -t mangle -X
$IPT -P INPUT ACCEPT
$IPT -P OUTPUT ACCEPT
$IPT -P FORWARD ACCEPT
}
 
# create a dir
[ ! -d $ZONEROOT ] && /bin/mkdir -p $ZONEROOT
 
# clean old rules
cleanOldRules
 
# create a new iptables list
$IPT -N $SPAMLIST
 
for c in $ISO
do
 # local zone file
 tDB=$ZONEROOT/$c.zone
 
 # get fresh zone file
 $WGET -O $tDB $DLROOT/$c.zone
 
 # country specific log message
 SPAMDROPMSG="$c Country Drop"
 
 # get 
 BADIPS=$(egrep -v "^#|^$" $tDB)
 for ipblock in $BADIPS
 do
 $IPT -A $SPAMLIST -s $ipblock -j LOG --log-prefix "$SPAMDROPMSG"
 $IPT -A $SPAMLIST -s $ipblock -j DROP
 done
done
 
# Drop everything 
$IPT -I INPUT -j $SPAMLIST
$IPT -I OUTPUT -j $SPAMLIST
$IPT -I FORWARD -j $SPAMLIST
 
# call your other iptable script
# /path/to/other/iptables.sh
 
exit 0

Si os fijáis bien, en la segunda línea del script se puede indicar entre las comillas los países que queremos que se baneen.:

### Block all traffic from AFGHANISTAN (af) and CHINA (CN). Use ISO code ###
ISO="af cn"

Si queremos banear las no deseadas de España, pondremos: “es” entre las comillas.

Una vez realizado esto, ejecutamos el script y a esperar. ¡Ojo! Este script siempre borra todo lo que tengáis en el IPTables, dado que ejecuta una función definida dentro del mismo llamada “cleanOldRules” la cual hace un flush de todas las reglas existentes.

Una vez realizado esto, y para mantener nuestro Fail2Ban trabajando como es de recibo, lo que os aconsejo es que procedáis a realizar un:

/etc/init.d/iptables reload

Con esto volveremos a cargar las chains de reglas del IPtables.

Depués de utilizar este script, y hacer un Word count de IPtables, pues me han aparecido un todal de 57692 reglas que banean (casi nada, y eso que comenté la parte de LOG…)

root@VOIP:/etc/asterisk# iptables -nL | wc -l
57692
root@VOIP:/etc/asterisk#

Mi consejo es que para ver que hace iptables de ahora en adelante, utilicéis un head (y a poder ser que os creéis un alias en la consola http://www.thegeekstuff.com/2010/04/unix-bash-alias-examples/). Las reglas se añaden en una cadena llamada “countrydrop”:

root@VOIP:/etc/asterisk# iptables -nL | head -n 20

Chain INPUT (policy ACCEPT)
target prot opt source destination
fail2ban-ASTERISK-UDP all -- 0.0.0.0/0 0.0.0.0/0
fail2ban-ASTERISK-TCP all -- 0.0.0.0/0 0.0.0.0/0
fail2ban-ssh tcp -- 0.0.0.0/0 0.0.0.0/0
countrydrop all -- 0.0.0.0/0 0.0.0.0/0

Chain FORWARD (policy ACCEPT)
target prot opt source destination
countrydrop all -- 0.0.0.0/0 0.0.0.0/0

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
countrydrop all -- 0.0.0.0/0 0.0.0.0/0

Chain countrydrop (3 references)
target prot opt source destination
DROP all -- 27.116.56.0/22 0.0.0.0/0
DROP all -- 43.231.131.0/24 0.0.0.0/0
DROP all -- 43.249.40.0/22 0.0.0.0/0

Luego para no tener que repetir el proceso cada vez que se reinicie la máquina, haré un:

root@VOIP:/etc/asterisk# /etc/init.d/iptables-persistent save

¡Espero que este post sea de utilidad!

 

Referencias:

http://www.lowendguide.com/3/networking/block-an-entire-country-with-iptables-2/

http://www.ipdeny.com/ipblocks/data/countries

http://jonathanmanning.com/2012/01/17/how-to-iptables-firewall-configuration-for-sipvoip-on-centos-rackspace-cloud/

FoIP – Fax Over IP – Análisis de call Flow de T.38

T.38 es un estándar de 1998 para permitir que los faxes se envíen a través de redes IP entre diferentes terminales G3 (terminales fax), y poder enviar faxes a través de VoIP.

Si intentásemos enviar un fax a través de VoIP, al utilizarse en algunos casos códecs con compresión de voz, no siempre el resultado sería satisfactorio. Esto en gran parte es debido a que si se envía una tasa superior de ciertos baudios (símbolos/muestras por segundo), y luego se comprime la voz (a través de los códecs), se puede perder información propia del fax, quedando este ilegible o imposible de transmitir. El teorema de Nyquist (velocidad de muestreo) aplica aquí, dado que en una señal ya muestreada uno no puede tomar menos muestras por segundos que se ha utilizado para digitalizar la señal.

Generalmente, si se desea enviar un fax convencionalmente a través de VoIP (sin utilizar protocolos auxiliares) se debe de utilizar G711 (a-law o u-law dependiendo si estamos en USA o Europa) y una velocidad máxima de 9600 baudios (por motivos relativos a Nyquist ya explicados). Este método, que consiste en enviar el FAX sin procesado extra, como si de voz se tratase, se conoce como pass-through, dado que lo único que se hace es conectar los dos terminales directamente, y hacer que pase el audio de uno a otro. En este caso el audio lleva un fax dentro (si lo capturamos con Wireshark u otro sniffer podremos ver los tonos que se envían entre ellos sin problema).

El envío de faxes con Pass-Through es muy sensible a problemas en la línea, y si se pierden paquetes durante la transmisión es probable acabar con problemas en los envíos.

Para evitar esto, se crea el protocolo T.38, RFC 3362 (https://tools.ietf.org/html/rfc3362), el cual permite encapsular  T.30 (protocolo PSTN para enviar faxes) a través de IP. El escenario general es el siguiente: se envía faxes a través de de T.30, unos conversores de analógico/digital a IP lo traducen en T.38, encapsulando T.30, y se procede a realizar el envio extremo a extremo, tal y como en la imagen se puede apreciar.

Mucha de la señalización que veremos dentro de este articulo es T.30, dado que T.38 simplemente es el soporte para encapsular señalización T.30 dentro de este protocolo.

1

Este es el caso en que necesitaremos entender qué sucede, dado que es el único que podremos analizar en VoIP (el pass-through sólo podemos averiguar si la calidad de audio es correcta o no).

Cuando se envía un fax existen diferentes fases, las cuales vamos a proceder a detallar.

  • Fase A: Establecimiento de canal.
  • Fase B: Pre-mensajes e intercambio de capacidades. Negociación.
  • Fase C: Transferencia de imagen.
    • Fase C1: Pre-imagen test.
    • Fase C2 : Transferencia imagen.
  • Fase D: Procesos post-mensajes.
  • Fase E: Finalización de la llamada.

Es muy importante recordar las  velocidades de fax que existen, según los estándares. Estas se pueden ver en la siguiente tabla:

2

Dicho esto, vamos a intentar ver que mensajes existen en cada fase y cómo se realiza un flujo de llamada de FAX.

 

Fase A: Establecimiento de canal

Se establece la llamada y se detecta que se quiere transmitir un fax, dado que se envía durante unos tres segundos los famosos tonos de FAX. Aquí estamos dentro del dominio SIP, y lo que es el protocolo T.38 aún no ha empezado a actuar en la transacción.

3

 

El hecho de que un extremo detecte sonido de FAX produce que se realice un RE-INVITE. Estos tonos se conocen como ANS (ANSwering tone) y se dan en la primera fase del fax.

Tal y como se puede ver en la siguiente imagen, cuando se detecta el tono ANS (enviados a través de RTP en la conversación), dentro del dialogo SIP se renegocian las capacidades del medio que se va a retransmitir. Recordemos que SIP significa Session Initation Protocol, y simplemente sirve para que dos end-points se descubran y negocien (a través de SDP) cómo se van a enviar un flujo de datos. Cómo sea ese flujo de datos, es indiferente para SIP.

4

Es entonces cuando comienzan a aparecer los tonos dentro de T.38. Los primeros que se intercambian son conocidos como CNG y CED.

CNG(CalliNG tone): Es el tono que genera la máquina que envía el fax. Es un tono a 1100Hz. En este caso es el RTP (g711A) que se puede ver en primer lugar.

CED (CallED terminal identification tone): Es el tono que genera la máquina que recibe el fax. Es un tono a 2100Hz. Es el RTP (g711A) que se genera después del primer flujo RTP en sentido inverso.

5

En el caso que vemos como se procede a enviar dentro de SDP del mensaje INVITE y su 200OK el protocolo T.38, indicándose algunas de sus capacidades dentro de este.

6

Es importante que no olvidemos que aún nos encontramos en la Fase A: Establecimiento de canal para el envío de fax. Aún no se ha procedido a realizar ningún tipo de negociación a nivel de fax, a pesar de haber intercambiado las capacidades por SIP.

 

Fase B: Pre-mensajes e intercambio de capacidades. Negociación

Una vez que ya se ha detectado que la comunicación era entre dos faxes, y se da como finalizada la Fase A, se procede a realizar la negociación T.38 para intercambiar datos propios de la comunicación de fax, tal y como el llamante, el numero llamado y las velocidades de intercambio.

7

Estos son los mensajes que se intercambian durante esta fase entre los dos dispositivos:

CSI (Called Subscriber): Este mensaje sirve para que las máquinas intercambien sus identidades. Los terminales internacionales realizan su intercambio de identidades a través de este mensaje. En el caso inferior, el número llamado no se identifica (aparece “Number: unkown” en el campo).

8

DIS (Digital Identification Signal): Es un mensaje obligatorio/mandatorio, en cual los faxes que reciben describen sus capacidades para realizar las transferencias de archivos. Es una propuesta de negociación. En este caso, el fax que recibe dispone de v.27 y v.29 para recibir este fax.

9

DCS (Digital Command Signal): A través de este mensaje, los dos extremos de fax informan sobre la velocidad que van a utilizar para la transmisión, dado que el que envía el fax hace un set de las capacidades que va a proceder a utilizar para ello. En el caso de la imagen inferior se puede ver cómo se desea transferir a través de v.29 (9600 baudios).

10

TSI (Transmitting Subscriber Identification): Con este mensaje se informa al terminal que recibirá la llamada quien está generando el fax. En el cuadrado negro de la imagen inferior se puede ver el número llamante.

11

Una vez intercambiados estos mensajes se procederá a realizar la transferencia de la imagen del FAX, tal y como vamos a exponer a continuación.

 

Fase C: Transferencia de la imagen

En la fase C se procede, finalmente, a realizar la transferencia del FAX. Esta fase se divide en dos partes, una de training o pre-testeo del canal, que ayudará a evaluar cómo se encuentra el camino entre origen y destino, y otra donde finalmente se procederá a enviar la información del FAX.

Fase C1: Pre-imagen test

En esta sub-fase se realiza el envío de lo que es la pre-imagen. Para ello se utilizan los mensajes de preámbulo.

TCF (Test Pattern): Este es un mensaje que envía cero de forma continuada durante 1.5 segundos aproximadamente. Sirve como patrón para corroborar  que el canal se puede utilizar a la velocidad que se desea.

12

13

CFR (Confirmation to Receive): Se confirma que se ha recibido correctamente el training enviado por la otra parte (TCF).

14

FTT (Failure To Train): indica que el patrón recibido no es correcto. La máquina llamante deberá de bajar la tasa de transmisión, y enviar de nuevo un mensaje DCS seguido de un TCF para probar el canal con un nuevo datarate. Este proceso de bajar la velocidad se puede llegar a realizar hasta tres veces. En caso de que bajando tres veces la velocidad no resulte suficiente, el fax se desconectará.

15

En esta imagen superior se ve que cuando se recibe un FTT se realiza de nuevo un DCS donde se negocia una velocidad inferior, y los siguientes Trainings se envían a una velocidad inferior (de 9600 baudios a 7200 baudios).

Es importante recordar que entre los diferentes mensajes aparece preámbulos:

VXX-Preamble: Antes de intercambiar un mensaje entre emisor y receptor (y viceversa), se envía un preámbulo con los caracteres 01111110 en binario (0x7E) que se remite durante 1 segundo aproximadamente. De esta forma se controla el acceso al medio. Si un lado no recibe respuesta, después de tres retransmisiones se desconectará.

16

Fase C2: Transferencia imagen

Una vez realizado la transferencia de la pre-imagen y de haber determinado que todo funciona correctamente, finalmente se procederá a enviar los datos del FAX. En la imagen inferior se puede ver el intercambio de mensajes entre las dos entidades donde se la transferencia de la misma.

17

Desde el terminal que origina el fax se podrán enviar los siguientes mensajes:

MPS (Multi-Page-Signal): Lo origina el llamante para indicar que hay más páginas que van a seguir a la transmisión que se ha completado. En la última página del fax se envía un EOP. La entidad receptora contestará con un mensaje de tipo MCF, RTP, o RTN (dependiento del resultado).

EOP (End-Of-Page): Lo origina el llamante, para decir que todas las paginas han sido transmitidas. Si todo ha funcionado correcto, se procede a la última fase del envío de FAX, la fase E.

EOM (End-Of-Message) : Indica el final de una página, para que ambas máquinas (recepción-transmisión) puedan volver a fase B.

NSF (Non  Standard Funtion): Opcionalmente las máquinas de fax puden intercambiar otros mensajes no estándares, los cuales se engloban dentro de estos mensajes NSF, dado que no están soportados por el estándar.

Las respuestas a estos mensajes se analizarán en la siguiente fase, procesos de post-mensajes, que serán las respuestas que el receptor del fax nos dará a estos envíos.

 

Fase D: Procesos post-mensajes

Respuestas del destino de fax:

MCF (Message Confirmation): La imagen se ha recibido correctamente, y se pueden proceder a recibir nuevas páginas sin problema, o en caso de que se haya acabado la transacción, desconectar la llamada.

18

RTP (Re-Train positive): Indica que la imagen ha sido recibida pero que existen algunos errores y que la línea debe de ser re-evaluada antes de enviar el siguiente mensaje. Por ello, lo siguiente será enviar un mensaje de DCS/TCF para retestear la línea, pero no se volverá a enviar la información, dado que es suficientemente buena.

19

RTN (Retrain Negative): El receptor considera que la señal es inaceptable. En este caso se deberá de volver a enviar un DCS/TCF para retestear la línea y se volverá a reenviar el mensaje que no ha llegado correctamente.

Fase E: Finalización de la llamada

El extremo que se encarga de enviar el FAX debe de indicar con un mensaje de tipo DCN que quiere desconectar la comunicación a nivel de T.38. Esto vendrá acompañado de un BYE a nivel de SIP, tal y como se puede ver en las imágenes inferiores.

DCN (Disconnect): Este mensaje se acaba enviando para desconectar la sesión de fax. DCN se utiliza en algunos casos, cuando la transmisión no ha sido satisfactoria, para volver a enviar el fax en pass-through (no siempre es para finalizar la transacción).

20

 

Bueno, espero que esta entrada os haya sido de utilidad. Para mi ha sido clave, dado que ahora puedo entender un poco más cómo funciona esto del FAX y el T.38. Si algo me queda pendiente es analizar a fondo la correspondencia entre el contenido de SDP de T.38 con los mensajes que dentro de este se intercambian (pero esto ya lo haré en posts siguientes).

 

Referencias:

https://tools.ietf.org/html/rfc3362

http://what-when-how.com/voip/pstn-fax-call-phases-voip/

http://wiki.voipinnovations.com/display/VI/T.38

http://www.cisco.com/c/en/us/support/docs/voice/fax-modem-over-ip/20227-faxrelay-tsguide.html#topic1-1

QoS sobre nuestras comunicaciones VoIP (SIP y RTP)

Cuando etiquetamos tráfico con diferentes priorirdades, existen dos modos de hacerlo, a nivel de protocolo IP (ToS) o a nivel de VLAN (802.1p). En este post vamos a ocuparnos de la primera parte, de la que se encarga de realizar diferenciación a nivel de IP (ToS).

A pesar de que en la practica presentada vamos a trabajar etiquetando todo el tráfico de VoIP entre dos redes, es necesario comentar que lo que queremos tratar con más alta prioridad no es el tráfico SIP (señalización) o el que se encuentre entre dos redes, sino sólo el RTP (paquetes de voz) que es el que lleva la información que una vez iniciada la conversación nos interesará más.

 

Diferenciación a nivel IP: DSCP (ToS)

El ToS (Type Of Service) es un campo que existe en la cabecera del protocolo IPV4 el cual se ha definido en diferentes RFCs. Es un campo de 6 bits, el cual indica a los routers que a la hora de enrutar los paquetes deben de hacerlo con mayor prioridad que el resto.

En la practica, nunca se ha aplicado a nivel extendido, pero en entornos que controlemos nosotros, es decir, en nuestra red, podemos trabajar sin problema con este parámetro.

Cuanto mayor sea el valor de este campo, mejor. Es decir, cuanto mayor sea más prioridad se le va a dar al paquete. La siguiente tabla se puede encontrar en Wikipedia. En nuestro caso, y como lo que queremos es dar prioridad a tráfico de VoIP vamos a diferenciar los datos RTP de nuestras comunicaciones.

DSCP Name DS Field Value (Dec) IP Precedence (Description)
CS0 0 0 : Best Effort
CS1,AF11-13 8,10,12,14 1 :Priority
CS2,AF21-23 16,18,20,22 2 :Immediate
CS3,AF31-33 24,26,28,30 3 :Flash – mainly used for voice signaling
CS4,AF41-43 32,34,36,38 4 :Flash Override
CS5,EF 40,46 5 :Critical – mainly used for voice RTP
CS6 48 6 :Internet
CS7 56 7 :Network

 

Diferenciación a nivel MAC: 802.1p

Esta técnica consiste, conocica como CoS, es un campo que se engloba dentro del las etiquetas del protocolo VLAN 802.1Q. Consiste en dar un valor a un campo de 3 bits llamado PCP. Su valor va entre 0 y 7 a nivel de prioridad. En la siguiente tabla de Wikipedia se puede ver los diferentes valores y acrónimos que se utilizan para este campo. Paara nuestro caso, el de tráfico VoIP, deberemos de utilizar el nivel 5, dejando los dos superiores a capas de management e internetwork.

PCP Priority Acronym Traffic types
1 0 (lowest) BK Background
0 1 BE Best Effort
2 2 EE Excellent Effort
3 3 CA Critical Applications
4 4 VI Video, < 100 ms latency and jitter
5 5 VO Voice, < 10 ms latency and jitter
6 6 IC Internetwork Control
7 7 (highest) NC Network Control

Es de importancia vital entender que el estandard 802.1p se encuentra intimamente relacionado con los 802.1d y 802.1q.

Implementando DSCP en nuestra red VoIP:

Tal y como hemos comentado anteriormente, vamos a realizar una aproximación. En nuestro caso lo que vamos a etiquetar es el tráfico saliente, para que llegue correctamente identificado a su destino, así como a los saltos intermedios. No tiene sentido etiquetar el tráfico en destino, dado que ya ha recorrido del paquete ya se ha realizado, con el riesgo de que haya sufrido congestión o pérdidas.

En nuestro Asterisk procedemos a realizar un etiquetado de tráfico a nivel de DSCP a través de iptables añadiendo las siguientes reglas con mangle. Siguiendo la tabla al campo DSCP le vamos a asignar el valor EF (que en decimal es 46, y en hexadecimal corresponde a 2E).

iptables -A OUTPUT -t mangle -p udp -m udp --sport 5060 -j DSCP --set-dscp 0x2e
iptables -A OUTPUT -t mangle -p udp -m udp --dport 5060 -j DSCP --set-dscp 0x2e
iptables -A OUTPUT -t mangle -p udp -m udp --sport 10000:20000 -j DSCP --set-dscp 0x2e
iptables -A OUTPUT -t mangle -p udp -m udp --dport 10000:20000 -j DSCP --set-dscp 0x2e

Una vez aplicado esto en nuestro iptables, si hacemos un tcpdump o ngrep, veremos como el tráfico que se envía a con puertos de origen iguales a los que hemos definido (protocolo UDP) aparecen con DSCP EF.

Routers Cisco:

A continuación vamos a configurar las diferentes políticas en nuestros routers Ciscos para que trabajen con DSCP, etiquetando todo el tráfico que entre con un destino determinado.

Configuramos un object-group para identificar todo el tráfico de voz que vamos a enviar:

object-group service voipservice 
 udp eq 5060
 udp gt 10000
 udp lt 20000

Después creamos las ACLs donde identificamos este tráfico. Para nuestro caso, vamos a tratar el tráfico que tiene como origen 192.168.3.0/24 y destino 192.168.1.0/24, pero podríamos haber puesto “any any” para cualquier tipo de tráfico que cumpliese el object-group. El hecho de realizar estos ACLs se realiza para aplicar después a los class-maps:

access-list 100 permit object-group voipservice 192.168.1.0 0.0.0.255 192.168.3.0 0.0.0.255

Creamos un class-map utilizando esta ACL para así poder tener nuestro tráfico clasificado dentro de nuestro router:

class-map match-any voip
 description VoIP-Traffic
 match access-group 100

Y finalmente la aplicamos a este tráfico una acción con policy-map. Es decir, ya lo tenemos identificado dentro de un class-map, y ahora le decimos que lo etiquete como EF en su campo DSCP.

policy-map voipdscppolicy
 description VoIP-Traffic-192168003
 class voip
  set ip dscp ef

Una vez tenemos esto ya configurado, aplicamos este policy-map en las interfaces de entrada. En nuestro caso es una interfaz que va conecta a la LAN del cliente, pero podría ser, perfectamente, una VLAN.

interface FastEthernet0/0
 description LANCLIENTE
 ip address 192.168.3.1 255.255.255.0
 !
 service-policy input voipdscppolicy

Y a generar tráfico y ver qué pasa. En mi caso, a la ACL he añadido una regla para que los ICMPs también los etiquete (he utilizado GNS3 para estas simulaciones).

CapturaTraficoEF

Referencias:

http://en.wikipedia.org/wiki/Type_of_service

http://en.wikipedia.org/wiki/IEEE_P802.1p

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