Asterisk 12

20 Ene

Hola, como muchos de ustedes ya se habrán dado cuenta desde el pasado Diciembre el grupo de desarrolladores de Asterisk liberó la primer versión  estable de Asterisk 12. A pesar de que no es un Branch LTS, Asterisk 12 será (en mi opinión) un parte-aguas con respecto al modo de trabajar y desarrollar soluciones basadas en Asterisk.

Se preguntarán: ¿Qué es lo que lo hace tan especial? Bueno, técnicamente hablando Asterisk 12 es la fusión del proyecto Asterisk y Asterisk SCF, lo cual da como resultado una nueva arquitectura, los desarrolladores están pensando en dejar de lado el «bugguiento» chan_sip (aún se puede usar en Asterisk 12) por el nuevo core de SIP que usa PJSIP; además de un nuevo modelo de desarrollo usando ARI (no confundir con la interfaz de usuario de FreePBX) y de STATSIS.

Siguiendo la línea técnica habrá mucho que aprender ya sea para desarrollar soluciones basadas en ARI (Asterisk REST Interface), familiarizarse con el bus de mensajes de STASIS, comprender los nuevos registros que generará el CDR, aunque el más importante será la migración a CHAN_PJSIP.

De este último podemos decir que la configuración cambia bastante con respecto al «viejo» CHAN_SIP, y es que ahora no solo basta con crear un peer y las definiciones que generalmente usamos como: secret, host, allow, disallow, qualify, context, etc. No, ahora necesitaremos: definir un tipo de transporte para ser usado por el endpoint, crear el endpoint, crear el AOR (Address Of Record) del endpoint, crear las autenticaciones del endpoint (auths), entre otras. Por ejemplo: si se tratase de una línea SIP o si hay que registrarse o si estamos fuera de la red. Suena complejo, ¿no? Lo cierto es que con el uso diario uno se va acostumbrando. Ejemplifico lo anterior:

Sintaxis para Asterisk 1.X, 10 y 11

[1000]
context=home
secret=mysupersecret
host=dynamic
disallow=all
allow=ulaw
allow=g729
callerid="Casa"<1000>
qualify=yes
....

Sintaxis para Asterisk 12:

;TRANSPORTE
[simpletrans]
type=transport
protocol=udp
bind=0.0.0.0

;Dispositivo 1000
[1000]
type=endpoint
context=casa
disallow=all
allow=ulaw
allow=g729
transport=simpletrans
auth=1000
aors=1000

;AUTH
[1000]
type=auth
auth_type=userpass
password=mysupersecret
username=1000

;AOR
[1000]
type=aor
max_contacts=5

Tuve la fortuna de probar las versiones alphas de Asterisk 12 cuando el CHAN_PJSIP se llamaba CHAN_GULP y una de las funciones más atractivas tanto para los desarrolladores como para los usuarios es la posibilidad de registrar más de un dispositivo SIP a la misma cuenta y llamarlas sin necesidad de usar el & en la aplicación Dial. Bueno, la primera vez que lo usas es como comer helado. Para poder hacer esto hay que especificar en la sección AOR cuantos contactos/dispositivos pueden «registrarse» usando la misma información, para el ejemplo anterior serán 5 los dispositivos capaces de usar la información del ENDPOINT 1000, o sea que podemos registrar el télefono IP de la oficina, el teléfono IP de la casa, el softphone del Celular y aún así nos quedan 2 libres para poder conectar un softphone basado en WS por ejemplo o un softphone en cualquier otro sitio de emergencia. Para llamar a todos los contactos registrados basta invocar esta línea en nuestro dialplan:

exten => 1000,1,Dial(${PJSIP_DIAL_CONTACTS(1000)})

Eso sí, tendremos que poner especial atención al CDR cuando llamemos a nuestros contactos porque los demás aparecerán como UNANSWERED.

Hablando de la Interfaz REST de Asterisk(ARI) surgen un sin fin de posibilidades para programar lo que se les ocurra en el lenguaje de programación que deseen, un ejemplo muy vistoso de como usar esta nueva interfaz esta en esta dirección http://ari.asterisk.org/ y usa swagger-ui como ‘core’ para crear la documentación interactiva. Para aquellos que estén muy familiarizados con las APIs REStfull y JSON se les hará pan comido, a su servidor le da dolores de cabeza. Actualmente existen proyectos dedicados a usar ARI para reemplazar las applicaciones Voicemail y APP_QUEUE de Asterisk, lo cual lo hace muy interesante, en el canal de Youtube de  Asterisk está la conferencia de Paul Belanger sobre esto.

Sobre el nuevo bus de mensajes llamado STASIS no tengo mucho que decir ya que aún lo sigo probando y tratando de entender, así que para que doy mala información, lo único que puedo decir es que con la aplicación STASIS podremos generar eventos bajo demanda de un canal para después obtener dichos eventos vía un Websocket. Útil para esas aplicaciones que necesitan estar monitoreando llamadas(y no, no es como AMI de hecho ni ARI ni STASIS están hechas para reemplazar AMI).

Desde la perspectiva del usuario no habrá mucho cambio, salvo que tendrá un sistema mas robusto y si usan PJSIP un canal mas estable con menos BUGS.

Desde la perspectiva del integrador o desarrollador, sólo es cuestión de leer y leer; pero seamos honestos en este mundo de Linux siempre estamos leyendo y leyendo, lo cuál hará  el proceso mas llevadero. Hablando de la instalación surgen nuevas dependencias como PJPROJECT, JANSSON, LibSRTP(esta última la deberías instalar con todos tus asterisk en realidad). Para aquellos que siempre quisieron un Asterisk enfocado a ‘multi-tennant’, Asterisk 12 será su mejor amigo. Y lo mejor de todo es que para aquellos que son FAN FAN de las WEB GUI de Asterisk, la excelente noticia es que FreePBX ha sacado ya una versión beta para usar Asterisk 12, ha sido un gran trabajo en conjunto con los desarrolladores de Digium y sólo como probadita te dejan escoger entre Asterisk 11 o Asterisk 12, si deseas usar sólo CHAN_SIP, o sólo CHAN_PJSIP o ambos.

Viene un camino largo por recorrer con Asterisk 12 y parafraseando a Elio Rojano, «Asterisk 12 es la maduración de un proyecto» que lleva ya 10 años(o más) de vida.

Les dejo estos enlaces para saber más de Asterisk 12:

Instalación y Configuración de Asterisk 12.

Iniciándose con ARI.

Especificaciones del CDR y CEL en Asterisk 12.

Ejemplo de ARI en ASTERISK usando Swagger-UI(necesitas tener un Asterisk12 corriendo y soportando ARI).

¿Quieres saber de Multi-tennant y más información de Asterisk 12? Elio Rojano te regala una presentación I:M:P:R:E:S:I:O:N:A:N:T:E

Desarollo de Paul Belanger para dejar de usar APP_QUEUE basado en ARI.

El github de Payload.

Video acerca de Payload.

Las extensiones internas se bloquean cuando el acceso a internet se cae. ¿Cómo resolverlo?

29 Jul

Este es un problema viejo y aunque la solución puede encontrarse buscando por internet, quise tomarme unos minutos para escribir un breve post que habla del problema y como resolverlo.

Estoy seguro que a varios les ha pasado: tienes tu conmutador configurado perfectamente y todo marcha bien. De pronto, de la nada, tus extensiones internas se caen: no haces ni recibes llamadas. Revisas un poco y te das cuenta de que no tienes internet. ¿Pero para que necesito internet si mis extensiones son internas? ¿Qué tiene que ver una cosa con la otra? Es ahí donde entramos.

El problema radica en la manera en como Asterisk resuelve los dominios de las troncales SIP a donde necesita conectarse. El canal SIP utiliza un método de consulta de DNS síncrono, lo que quiere decir que cuando llega una petición de resolver un DNS (ej. siptrunk.alianza.com) el canal SIP le pregunta al servidor DNS, espera la respuesta, y cuando finalmente la obtiene continú con su siguiente petición SIP. En un mundo ideal esto no es problema, ya que la resolución por DNS es muy rápida y toma unos cuantos milisegundos, por lo que normalmente no la notamos. Pero… ¿qué ocurre cuando por alguna falla en internet, el servidor de DNS al que solemos apuntar se cae o simplemente no podemos acceder a él? Pues la respuesta es que esto provoca un efecto  dominó en Asterisk que ocasiona que todo el canal SIP se caiga.

¿Cómo puede pasar esto?

Imagina el siguiente caso:

  1. Juan quiere hacer una llamada a través de su troncal de pbx.micarrier.com, así que levanta su teléfono y llama al número deseado.
  2. Asterisk recibe la petición de llamada y le manda a su DNS la solicitud de resoler pbx.micarrier.com, pero al utilizar paquetes por UDP, Asterisk no se da cuenta de que el equipo está offiline, así que le da un cierto tiempo de timeout a que el servidor responda.
  3. Mientras que Asterisk espera a recibir la respuesta, Jorge desea hacer una llamada a través de pbx.miotrocarrier.com, pero aún no puede enviar la llamada porque Asterisk está ocupado con la petición anterior.
  4. Para cuando Juan por fin recibe la respuesta de request timeout, Jorge ya lleva buen rato esperando a que su petición apenas comience, así que muy probablemente Jorge reciba un timeout pero a nivel de SIP, porque Asterisk se tardó mucho en responderle por estar ocupado en la petición de Juan.
  5. Si a este escenario le agregan más usuarios y más carriers, el sistema se hace más complejo exponencialmente, ocasionando una serie de retrasos que eventualmebte tiran todo el servidor porque nada responde (todos se quedan esperando a todos y ninguna llamada logra salir).

Esto es un problema a nivel de código de Asterisk: si las peticiones fueran asíncronas la espera de uno no se convertiría en la espera del otro y todos serían felices. Pero como se menciona en los foros de desarrollo de Asterisk, esta es una funcionalidad que requiere mucho tiempo para ser resuelta, y que al menos en la versión 1.8.21.0, persiste.

¿Cómo lo resolvemos?

Hay algunas soluciones:

  • Editar manualmente el /etc/hosts y poner allí todos los dominios y direcciones IP que necesitemos. El problema es que esto no funciona para resolución inversa de DNS, así que tiene fallos. Otro problema es que tendríamos que agregar manualmente cada dominio, lo cual puede consumir mucho tiempo, además de que si el proveedor actualiza su IP, nuestra resolución fallaría.
  • Configurar en Asterisk las IPs fijas de cada carrier. Fácil de hacer pero de igual manera, es manual, así que nos exponemos a los problemas de tener que estar vigilando nuestro PBX constantemente.

La solución que mas predomina es la de instalar localmente (en el mismo servidor de Asterisk) un servicio de cache de DNS, como es el caso de bind. Esto hará que el equipo almacede de manera local la información de consulta frecuente para que en caso de fallos con el internet, Asterisk sobreviva del cache. Y es bastante fácil de hacer.

En CentOS/RedHat lo hacemos fácil:

yum install bind bind-utils bind-libs caching-nameserver

En Debian/Ubuntu, también es sencillo:

apt-get install bind9

En CentOS/RedHat, por default el servicio no arranca automáticamente, así que debemos decirle a Linux que lo arranque cuando el sistema prenda.

chkconfig named on

También aprovechamos y lo arrancamos:

/etc/init.d/named start

Con esto el servicio de named ya está corriendo. Ahora le decimos a Linux que lo utilice. Editamos el archivo /etc/resolv.conf y nos aseguramos que solamente contenga una linea como la que sigue:

nameserver 127.0.0.1

Si usamos Elastix, hacer el cambio también desde la interfaz web. Abrimos el menu de System > Network y editamos la configuración para dejar únicamente un DNS:

DNS en Elastix

De esta manera, obligamos a que use el local. Si el local falla, lo peor que pasa es que perdemos nuestra troncal, pero es mejor recibir una respuesta negativa del DNS a no recibir respuesta.

Podemos validar que todo está corriendo revisando que el puerto 53 esté ocupado por el servicio de named:

[root@pbx ~]# netstat -anpl | grep 53
tcp        0      0 0.0.0.0:9090                0.0.0.0:*                   LISTEN      3953/java
tcp        0      0 127.0.0.1:53                0.0.0.0:*                   LISTEN      4745/named
tcp        0      0 127.0.0.1:953               0.0.0.0:*                   LISTEN      4745/named
udp        0      0 127.0.0.1:53                0.0.0.0:*                               4745/named

Y claro está, no puede faltar la super prueba del ping que muestre que los dominios resuelven correctamente:

[root@elastix ~]# ping asteriskmx.com
PING asteriskmx.com (216.93.172.112) 56(84) bytes of data.
64 bytes from 216.93.172.112.servepath.com (216.93.172.112): icmp_seq=1 ttl=53 time=74.6 ms
64 bytes from 216.93.172.112.servepath.com (216.93.172.112): icmp_seq=2 ttl=53 time=74.5 ms
64 bytes from 216.93.172.112.servepath.com (216.93.172.112): icmp_seq=3 ttl=53 time=74.9 ms

--- asteriskmx.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 74.563/74.723/74.912/0.265 ms

Hecho estos sencillos pasos, tu sistema estará preparado para caidas en el internet y tus troncales SIP no causarán que todo tu sistema se muera.

Solo una nota final: el servicio de named/bind al parecer vacia su cache cuando arranca. Esto quiere decir que si se va la luz y el internet al mismo tiempo, perderás la conectividad y todo se caerá (lo siento, no hay solución universal), pero en caso de que eso pase todo lo que necesitas hacer es detener el servicio y con esto tus DNS fallarán inmediatamente, con lo que te quedarás sin troncales SIP pero no si extensiones locales.

¡Suerte!

Recibe notificaciones push en tu móvil de llamadas hechas en Asterisk

13 Dic
pushover

Pushover está disponible para iOS y Android

Hace unos cuantos meses empecé a probar una aplicación para iOS/Android llamada Pushover que te permite crear notificaciones personalizadas de tipo push en tu móvil. El servicio es gratuito (solo debes comprar la aplicación que cuesta $4 USD) y te permite recibir hasta 7500 notificaciones al mes (suficientes creo yo). La aplicación es muy fácil de configurar: tras darte de alta solo debes registrar una aplicación (se te proporcionará un token al registrarla) y tomar nota de tu user key. Tras obtener esos datos, crea un script como el que sigue (puedes ponerlo en /sbin/push.php):

#!/usr/bin/php
<?php

// Reemplaza este valor por tu verdadero userKey de Pushover
$userKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

/**************************************************
 Reemplaza este valor con el de tu propia aplicación.
 Si no lo cambias recibirás notificaciones de parte de "Asterisk México"
 (según la cantidad de usuarios que usen este token, pueden acabarse las notificaciones
 permitidas por Pushover al mes 
***************************************************/
$appToken="ptTjW6PlKHU7lB5jOdhLN7vHlKSIei";

curl_setopt_array($ch = curl_init(), array(
  CURLOPT_URL => "https://api.pushover.net/1/messages.json",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POSTFIELDS => array(
     "token" => $appToken,
     "user" => $userKey,
     "message" => $argv[1],
   )));
curl_exec($ch);
curl_close($ch);

?>

No te olvides de hacer el script ejecutable:

chmod 755 /sbin/push.php

Con esto el script ya debe de enviarte notificaciones a tu teléfono. Puedes enviarte un mensaje de prueba para validarlo. Teclea en el CLI de Linux:

/sbin/push.php "Este es un mensaje de prueba"

Si lo ejecutas, deberas recibir una notificación como la siguiente:

Ejemplo de notificación de Pushover

Ejemplo de notificación de Pushover

Ahora toca decirle a Asterisk que nos envíe notificaciones. Vamos a ver el código necesario para 2 escenarios: Asterisk puro y Asterisk bajo FreePBX (Elastix/Trixbox)

 

Para Asterisk Puro:

Lo que necesitamos insertar es insertar una llamada a la aplicación System que invoque nuestra aplicación con los valores que necesitamos. Por ejemplo, supongamos que tenemos esta línea en nuestro plan de llamadas para hacer una marcación internacional (archivo extensions.conf):

exten => _00ZXXXX.,1,Dial(DAHDI/g0/${EXTEN})

Lo que requerimos es mandar llamar a la notificación antes de que se genere el Dial, quedando algo así:

exten => _00ZXXXX.,1,System(/sbin/push.php "Llamada de ${CALLERID(num)} hacia ${EXTEN}")
exten => _00ZXXXX.,n,Dial(DAHDI/g0/${EXTEN})

Es importante que escapemos correctamente las comillas » «, de lo contrario solo nos llegará la primer palabra del mensaje.

Si tratamos de marcar al exterior, recibiremos una notificación como la que sigue:

Notificaciones de llamadas de Asterisk en Pushover

Notificaciones de llamadas de Asterisk en Pushover

Con esto ya quedan las notificaciones a nuestro sistema usando Asterisk puro.

Para FreePBX/Trixbox/Elastix:

Editamos el archivo de extensions_custom.conf y hacemos uso del hook que ya existe en FreePBX para no tener que modificar nuestro código:

[macro-dialout-trunk-predial-hook]
exten => s,1,System(/sbin/push.php "Llamada de ${CALLERID(num)} hacia ${OUTNUM}")

Ojo: esto enviará notificaciones por cada llamada que use troncales. Si quieren que solo se use para cierto tipo de llamadas, necesitan validar la variable ${OUTNUM} para que solo contenga los números que ustedes quieran.

 

¡Suerte!

Asterisk con Alta-Disponibilidad + MySQL

10 Dic

Este tutorial fue escrito por uno de los participantes de nuestro foro: navaismo.
El artículo original lo pueden encontrar aquí.

En algunas ocasiones nos vemos en la necesidad de crear un Cluster de alta disponibilidad para nuestros servicios de Asterisk. A continuación se describen los pasos necesarios para llevar esto acabo en nuestros servidores usando Asterisk y Mysql(por si queremos usar Asterisk Realtime Architechture).

Algunas indicaciones iniciales:

  • Estos pasos están basados en las instrucciones que brinda Digium y los tutoriales de DRBD para Mysql.
  • Este tutorial no esta hecho para hacer copy&paste.
  • El color verde indica que son pasos para realizar en ambos servidores.
  • El color Naranja indica que son pasos para realizar en el servidor primario.
  • El color Rojo indica que son pasos para realizar en el servidor secundario.
  • El Hostname del Servidor primario es «node1«.
  • El Hostname del Servidor secundario es «node2«.
  • La dirección IP del servidor primario es 10.0.1.51
  • La dirección IP del servidor secundario es 10.0.1.52
  • La dirección IP compartida del cluster y a la que deberán apuntar los servicios(como el registro de teléfonos, MySQL o apache) es 10.0.1.50.
  • La dirección IP del Gateway es 10.0.1.1.
  • Se puede adaptar fácilmente el Hardware Failover que provee Digium(rseries) y los servicios de Apache.

DRBD Cluster

La Imagen anterior describe el funcionamiento del Cluster:

  • Escenario 1: El servidor primario esta activo y el secundario esta en modo pasivo esperando.
  • Escenario 2: El servidor Primario ha entrado en estado de falla(por conexión de RED o por reinicio o falla en el kernel), el servidor secundario entonces, se convierte en el servidor primario y es marcado como activo.
  • Escenario 3: El servidor secundario(antes primario) se ha recuperado de la falla y ha entrado en modo pasivo.
  • Escenario 4: El servidor primario(antes secundario)  ha presentado falla  y el servidor secundario(antes primario) es marcado como servidor primario nuevamente y entra en modo activo.

 

Paso 1 —- Realizar en Ambos Servidores:

Instala CentOS 5.X(Los agentes de recursos «ocf»de Digium no son compatibles con las versiones 6.X de CentOS). Escoger el modo de partición manual y dejar un espacio libre sin formateo ni nada, en este tutorial yo he dejado 5GB sin particionar. Este espacio será donde guardemos nuestros datos a replicar en el Cluster, así que deberán considerar cuanto espacio necesitaran para sus archivos y logs.

 

Paso 2 —- Realizar en Ambos Servidores:

Instala las dependencias para nuestros servidores. Primero añadiré el repositorio de rpmforge:

rpm -ihv http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.2-2.el5.rf.[ARCH].rpm

Cambia [ARCH] por la arquitectura del servidor: i386 o x86_64.

Ahora instala las dependencias:

yum -y install kernel kernel-devel libxml2  libxml2-devel  libtiff  libtiff-devel  lame httpd  mysql mysql-devel mysql-server php  php-pear  php-mysql  php-gd openssl openssl-devel perl  bison ncurses-devel audiofile-devel curl sox gcc newt-devel libusb-devel glibc-devel  zlib-devel svn gcc-c++ subversion make nano wget cfdisk

 

Paso 3 —- Realizar en Ambos Servidores:

Ejecuta el comando:

cfdisk

Obtendrás una ventana como esta:

Selecciona el espacio libre y crea una nueva partición con todo el espacio libre usando los botones: New, Primary, Write. Al finalizar te preguntara si quieres efectuar los cambios, escribe la palabra: yes y da enter.

Cuando el proceso termine reinicia el servidor:

reboot

Una vez que el servidor haya arrancado de nuevo hay que limpiar la nueva partición usando el siguiente comando:

dd if=/dev/zero of=/dev/[hs]da[#] bs=1M; sync

Cambia /dev/[hs]da[#] por tu dispositivo que puede ser SDA3,HDA3 o SDB3 HDB3 etc. En este tutorial es HDA3.

Veras una salida similar cuando termine el proceso, dependiendo del tamaño de tu partición y de la velocidad de tus discos duros puede tomar minutos u horas.

Crea un nuevo directorio en /usr/src:

cd /usr/src/
mkdir asterisk
cd asterisk/

En el nuevo directorio descarga Asterisk y sus componentes:

wget http://downloads.asterisk.org/pub/telephony/dahdi-linux-complete/dahdi-linux-complete-2.6.1+2.6.1.tar.gz
wget http://downloads.asterisk.org/pub/telephony/libpri/libpri-1.4.13.tar.gz
wget http://downloads.asterisk.org/pub/telephony/certified-asterisk/certified-asterisk-1.8.11-current.tar.gz
wget http://downloads.digium.com/pub/telephony/rseries/rseries-current.tar.gz

Descomprime los archivos fuente:

for i in `ls *gz`; do tar zxvf $i; done

Compilamos DAHDI:

cd dahdi-linux-complete-2.6.1+2.6.1
make && make all && make install && make config

Compilamos LibrPRI y configuramos DAHDI:

cd ../libpri-1.4.13
make && make install
service dahdi start
dahdi_genconf
dahdi_cfg -vvvvv

Instalamos los agentes de Digium para el cluster:

cd ../rseries-1.0.0/
make && make install

 

Paso 4 —- Realizar solo en  Servidor Primario..

Instalar Asterisk en el servidor primario:

cd ../certified-asterisk-1.8.11-cert9
contrib/scripts/get_mp3_source.sh
./configure && make menuselect

En el menú selecciona las aplicaciones que quieras disponibles en tu asterisk:

Guarda los cambios y compila Asterisk:

make && make install && make config && make samples

Inicia el servicio de Mysql y detenlo esto solo para crear las bases de datos por default.

service mysqld start
service mysqld stop

 

Paso 5 —- Realizar en Ambos Servidores.

Cambiate al directorio de rseries:

cd /usr/src/asterisk/rseries-1.0.0/

Instala los servicios de DRBD:

yum -y install drbd83 kmod-drbd83

Instala Libesmtp, dependencia necesaria para Pacemaker:

wget http://dl.fedoraproject.org/pub/epel/5/[ARCH]/libesmtp-1.0.4-5.el5.[ARCH].rpm #64bits

Cambia [ARCH] por la arquitectura del servidor: i386 o x86_64.

Instala PaceMaker y Corosync:

wget -O /etc/yum.repos.d/pacemaker.repo http://clusterlabs.org/rpm/epel-5/clusterlabs.repo
yum -y install -x heartbeat-stonith* pacemaker corosync

Estamos exluyendo el paquete heratbeat-stonith ya que cre un conflicto con pacemaker.

Instala los archivos de configuración que provee Digium:

make samples

Agrega estas lineas al archivo /etc/drbd.conf:

echo «include «drbd.d/global_common.conf»;» >> /etc/drbd.conf

echo «include «drbd.d/*.res»;» >> /etc/drbd.conf

Edita el archivo /etc/drbd.d/asterisk.res. Cambia astnode1 por el nombre el hostname del servidor primario, cambia astnode2 por el hostname del servidor secundario, cambia /dev/sda3 por la partición que creamos con el espacio libre(en este tutorial hda3). Cambia las IPs por las de tus servidores primarios y secundarios.Cambia el correo electrónico por el tuyo o el administrador del sistema en este tutorial el archivo quedo así:

resource asterisk {
  handlers {
    split-brain "/usr/lib/drbd/notify-split-brain.sh clusteradmin@example.com";
  }

  net {
    after-sb-0pri discard-younger-primary;
    after-sb-1pri discard-secondary;
    after-sb-2pri disconnect;
  }

  on node1 {
    device    /dev/drbd0;
    disk      /dev/hda3;
    address   10.0.1.51:7789;    
    meta-disk internal;
  }
  on node2 {
    device    /dev/drbd0;
    disk      /dev/hda3;
    address   10.0.1.52:7789;
    meta-disk internal;
  }
}

Crea el Recurso llamado asterisk e inicia el servicio de DRDB:

drbdadm create-md asterisk
service drbd start

 

Paso 6 —- Realizar solo en  Servidor Primario.

A continuación crea el UUID llamado Asterisk, y formatea la partición que será usada por el cluster tipo: EXT3

drbdadm disconnect asterisk
drbdadm -- --clear-bitmap new-current-uuid asterisk
drbdadm -- --overwrite-data-of-peer primary asterisk
mkfs.ext3 -m0 /dev/drbd0
drbdadm secondary asterisk
drbdadm detach asterisk
drbdadm up asterisk

Crea el directorio que será usado para el cluster, pero antes marca como nodo primario para poder montarlo:

drbdadm primary asterisk
mkdir /mnt/asterisk

Monta la partición:

mount -t ext3 /dev/drbd0 /mnt/asterisk

Ahora crea el directorio donde estará Mysql:

mkdir /mnt/asterisk/mysql
mkdir /mnt/asterisk/mysql/data
cd /mnt/asterisk/mysql

 

Copia el contenido del directorio de Mysql al nuevo directorio:

cp -aR /var/lib/mysql/* /mnt/asterisk/mysql/data
ls data/

 

Mueve el archivo /etc/my.cnf al nuevo directorio de Mysql:

mv /etc/my.cnf .

Crea un enlace simbólico al nuevo directorio:

ln -s /mnt/asterisk/mysql/my.cnf /etc/

Edita el archivo my.cnf y cambia la directiva de DATADIR: datadir=/mnt/asterisk/mysql/data.


Crea un archivo dummy solo para verificar la replicación en el nodo secundario.

touch test11

Desmonta la partición y marca el nodo como secundario:

cd --
umount /mnt/asterisk
drbdadm secondary asterisk

 

Paso 7 —- Realizar solo en Servidor Secundario

Elimina el archivo /etc/my.cnf

rm -rf /etc/my.cnf

 

Vamos a verificar que los archivos de nuestra partición en el cluster se repliquen. Marca el servidor como primario y crea el mismo directorio:

drbdadm primary asterisk
mkdir /mnt/asterisk

 

Monta la partición y cambiate al directorio:

mount -t ext3 /dev/drbd0 /mnt/asterisk
cd /mnt/asterisk

Haz un enlace simbolico del archo my.cnf al directorio de la partición del cluster:

ln -s /mnt/asterisk/mysql/my.cnf /etc/

Verifica que el archivo my.cnf sea el mismo que editamos anteriormente, es decir, que contenga:  datadir=/mnt/asterisk/mysql/data

Verifica que los archivos de Mysql y el archivo test11 existan en el directorio:

ls

Si los archivos existen la replicación va de maravilla.

Desmonta la partición y marca el nodo como secundario:

cd --
umount /mnt/asterisk
drbdadm secondary asterisk

 

Paso 8 —- Realizar solo en  Servidor Primario.

Cambiate al directorio de rseries, mara el servdiro como primario y monta la partición:

cd /usr/src/asterisk/rseries-1.0.0
drbdadm primary asterisk
mount -t ext3 /dev/drbd0 /mnt/asterisk/

 

Ejecuta el script cretalinks.sh:

./createlinks.sh

Veras una salida como la siguiente:

Elimina el script de asterisk para que no arranque automáticamente cuando inicie el sistema, desmonta la partición y marca el nodo como secundario:

chkconfig --del asterisk
umount /mnt/asterisk
drbdadm secondary asterisk

 

Paso 9 —- Realizar solo en Servidor Secundario

Cambiate al directorio de rseries, marca el nodo como primario, monta la partición y ejecuta el script createlinks.sh:

cd /usr/src/asterisk/rseries-1.0.0
drbdadm primary asterisk
mount -t ext3 /dev/drbd0 /mnt/asterisk/
./createlinks.sh

Veras una salida como en la imagen anterior.

Cambiate al directorio de Asterisk, configura las mismas opciones que se configuraron en el servidor primario. Compilalo solo ejecutando make && make install. Desmonta la partición y marca el nodo como secundario.

cd ../certified-asterisk-1.8.11-cert9
contrib/scripts/get_mp3_source.sh
./configure && make menuselect
make && make install
umount /mnt/asterisk/
drbdadm secondary asterisk


Paso 10 —- Realizar en Ambos Servidores.

Edita el archivo /etc/corosync/corosync.conf. Cambia la opción bindnetaddr, y las opciones memberaddr. Para este tutorial el archivo quedo de la siguiente manera:

totem {
        version: 2
        token: 3000
        token_retransmits_before_loss_const: 10
        join: 60
        consensus: 5000
        vsftype: none
        max_messages: 20
        clear_node_high_bit: yes
        secauth: off
        threads: 0
        rrp_mode: none

        interface {
                ringnumber: 0
                bindnetaddr: 10.0.1.0
                broadcast: yes
                mcastport: 5405
                member {
                        memberaddr: 10.0.1.51
                }
                member {
                        memberaddr: 10.0.1.52
                }
        }
}

aisexec {
        user:   root
        group:  root
}

logging {
        fileline: off
        to_stderr: yes
        to_logfile: no
        to_syslog: yes
        syslog_facility: daemon
        debug: off
        timestamp: on
        logger_subsys {
                subsys: AMF
                debug: off
                tags: enter|leave|trace1|trace2|trace3|trace4|trace6
        }
}

amf {
        mode: disabled
}

Inicia el servicio de Corosync y añade drbd y corosync al startup:

service corosync start
chkconfig drdb on
chkconfig corosync on

Verifica el estado del Cluster con el siguiente comando:

cat /proc/drbd

Veras una imagen como la siguiente:

Veras que se esta sincronizando la particion del cluster, también verás como Secondary/Secondary(no como en la imagen).

Si el proceso de sincronización reporta que tardará mucho tiempo puedes usar este comando para acelerar la velocidad de sincronización:

drbdsetup /dev/drbd0 syncer -r 250M

La velocidad máxima de sincronización dependerá de la velocidad de tus tarjetas de red así como la velocidad de escritura de tus discos duros. Para más información de como calcular la velocidad ve a este enlace.

Una vez que el estado sea UpToDate/UpToDate reinicia los servidores:

reboot

 

Paso 11 —- Realizar solo en  Servidor Primario.

Edita el siguiente codigo para que:

— node1 y node2. Sean los hostnames de tus servidores. En este ejemplo node1 y node2

ip bajo ClusterIP. Sea la Ip de tu Cluster, la IP flotante. En este ejemplo 10.0.1.51

cidr_mask bajo ClusterIP. Sea la mascara de tu red. en este ejemplo de 24bits(255.255.255.0)

– –host_list bajo GatewayStatus. Sea el gateway de tu red. en este ejemplo 10.0.1.1

node node1
node node2
primitive ClusterIP ocf:heartbeat:IPaddr2 
        params ip="10.0.1.50" cidr_netmask="24" 
        op monitor interval="5"
primitive drbd ocf:linbit:drbd 
      params drbd_resource="asterisk" 
      op monitor start-delay="10" interval="5"  
primitive drbd_fs ocf:heartbeat:Filesystem 
      params device="/dev/drbd0" directory="/mnt/asterisk/" fstype="ext3"
primitive mysqld lsb:mysqld
primitive Asterisk ocf:Digium:asterisk 
        op monitor interval="5"
primitive GatewayStatus ocf:pacemaker:ping 
        params host_list="10.0.1.1" multiplier="100" 
        op monitor interval="5" timeout="10"
ms drbd_ms drbd 
        meta master-max="1" master-node-max="1" clone-max="2" clone-node-max="1" notify="true"
clone GatewayStatusClone GatewayStatus
location Asterisk-with-ping Asterisk 
        rule $id="Asterisk-with-ping-rule" -inf: not_defined pingd or pingd lte 0
group mysql drbd_fs ClusterIP mysqld
colocation mysql_on_drbd inf: mysql drbd_ms:Master
order mysql_after_drbd inf: drbd_ms:promote mysql:start
colocation Everything-with-Asterisk inf: ( drbd_ms:Master )  ( ClusterIP drbd_fs )  Asterisk
order  Asterisk-after-Everything inf:   ( drbd_ms:promote ) ( ClusterIP drbd_fs )  Asterisk:start
property $id="cib-bootstrap-options" 
        cluster-infrastructure="openais" 
        expected-quorum-votes="2" 
        stonith-enabled="false" 
        no-quorum-policy="ignore"
rsc_defaults $id="rsc-options" 
        resource-stickiness="99"

Una vez que has editado el archivo Cluster_Mysql_Asterisk.cfg, actualiza la configuración de pacemaker:

crm configure load update Cluster_Mysql_Asterisk.cfg

Si todo sale bien veras una salida como la siguiente y los servicios de MySQL y Asterisk se iniciaran en el nodo primario:

Para verificar que los servicios estén corriendo puedes revisar el estado del cluster con:

crm_mon

Y verás algo así:

Demostración:


El siguiente video muestra las pruebas del cluster. Asterisk esta configurado con «static realtime» obteniendo los datos de una base de datos de MySQL. Este tutorial no cubre la cofiguración de ARA(Asterisk Realtime Architechture).

Herramientas para diagnosticar fallas:

Puedes ejecutar el siguiente comando en el nodo secundario para verificar que la configuración de pacemaker se replico:

crm configure show

Para detener los servicios del cluster:

crm configure property stop-all-resources=true

Para borrar la configuracion del cluster:

crm configure erase

Verificar el estado del cluster:

crm_mon

Verificar el estado de la sincronización y los roles de los servidores:

cat /prco/drbd

Aumentar la velocidad de sincronización:

drbdsetup /dev/drbd0 syncer -r 250M

Como bloquear extensiones al final del día (cerrar extensión cuando el trabajador se retira)

3 Dic

En ocasiones en nuestras oficinas (sobretodo en grandes corporativos), estamos sujetos a presupuestos de llamadas por mes. Esto quiere decir que recibimos una bolsa de minutos a ciertos destinos (para control de gastos) y debemos cuidarlos, ya que si nos los agotamos tendremos que hacer solicitudes de «crédito» interno y ante nuestros superiores parecería como que estamos desperdiciando dinero (llamadas) de la empresa.

Contemplando escenarios similares a este, podemos configurar Asterisk para que las extensiones de cada persona sean bloqueadas al momento en que la persona se retira a su casa. De esta manera, si alguien se queda cercano a su área de trabajo no podrá tomar su teléfono y hacer llamadas al exterior, reduciendo el crédito del trabajador que ya se fue (y ahorrándose el suyo). Al día siguiente, cuando el trabajador regresa, marca un código de desbloqueo junto con su contraseña y su teléfono vuelve a estar habilitado.

¿Cómo hacer eso?

Partiremos de los siguientes supuestos:

  • El contexto al que mis extensiones «normales» tienen derecho es el from-internal.
  • El código para bloquear la extensión (cuando el trabajador se va), será *51.
  • El código para desbloquear la extensión (cuando el trabajador regresa por la mañana), será *52.
  • Todas las extensiones deben tener un buzón asignado. El número de buzón debe ser el mismo que el de la extensión.
  • La marcación que negaremos será solamente la que corresponda a llamadas locales, larga distancia, celulares e internaciones (es decir, todas las que cuesten).
  • La sintaxis que estoy ocupando (same =>) es para Asterisk 1.6.2 o superior.

Toda la magia viene en el archivo extensions.conf (si usas Trixbox/Elastix/FreePBX, debes de hacerlo dentro del archivo extensions_custom.conf)

[from-internal-custom]
; Estos son los patrones para hacer las llamadas
; Si necesitas cerrar más patrones, agrégalos siguiendo el mismo formato
exten => _ZXXXXXXX,1,Macro(permitir)
same => n,Goto(from-internal-additional,${EXTEN},1)
exten => _04[45]ZXXXXXXXXX,1,Macro(permitir)
same => n,Goto(from-internal-additional,${EXTEN},1)
exten => _01ZXXXXXXXXX,1,Macro(permitir)
same => n,Goto(from-internal-additional,${EXTEN},1)
exten => _00ZXXXXXXXX.,1,Macro(permitir)
same => n,Goto(from-internal-additional,${EXTEN},1)

; Este es el código de bloqueo. Es lo que el trabajador marca antes de retirarse
exten => *51,1,Noop(Bloqueando ${CALLERID(num)})
;same => n,VMAuthenticate(${CALLERID(num)})     ; Descomenta esta línea si quieres pedir contraseña también al bloquear
same => n,Set(DB(bloqueo/${CALLERID(num)})=1)
same => n,Playback(vm-goodbye)
same => n,Hangup
; Este es el código de desbloqueo. Es lo que marca a la mañana siguiente
exten => *52,1,Answer
same => n,VMAuthenticate(${CALLERID(num)})
same => n,Noop(${DB_DELETE(bloqueo/${CALLERID(num)})})
same => n,Playback(auth-thankyou)
same => n,Hangup

; El macro-permitir es lo que valida si una extensión tiene o no activada el bloqueo
[macro-permitir]
exten => s,1,Noop(Revisando permisos de ${CALLERID(num)})
same => n,GotoIf($[${DB_EXISTS(bloqueo/${CALLERID(num)})}]?Bloquear:Permitir)
same => n(Bloquear),Noop(Deteniendo el paso de ${CALLERID(num)})
same => n,Playback(pbx-invalid)  ; Reemplazar por cualquier archivo de sonido deseado
same => n,Congestion()
same => n(Permitir),Noop(Permitiendo el paso de ${CALLERID(num)} --> ${MACRO_EXTEN})

Una breve explicación de lo que hace cada cosa:

  • Los patrones de ZXXXXXXX, 01ZXXXXXXXXX, 04[45]ZXXXXXXXXX y 00ZXXXXXX. son para controlar las llamadas locales, larga distancia nacional, celulares y LD internacional respectivamente. Si tienes más patrones de marcación o destinos que desees bloquear (por ejemplo, alguna marcación corta por telular), necesitas agregar el patrón correspondiente siguiendo el mismo formato (tras cada patrón debe haber una continuación same => n,Goto(from-internal-additional,${EXTEN},1) que te permite continuar con el contexto from-internal-additional.
  • *51 y *52 son, como habíamos comentado, los códigos para bloqueo/desbloqueo respectivamente. Puedes reemplazar los Playbacks por lo que tu elijas para dar diferentes mensajes
  • El [macro-permitir] es el que checa el contenido del Asterisk DB. Si la familia bloqueo/CALLERID existe, entonces no se le permiten las llamadas. Si no existe, el macro permite el paso y la llamada continúa de manera normal.
  • Las autenticaciones se hacen con la misma contraseña del buzón de voz, así cada quien tiene la suya, es actualizable por el usuario y solo funciona dentro de su mismo teléfono.
  • Si deseas desbloquear manualmente alguna extensión (ej. alguien olvidó su contraseña), solo debes ejecutar database del bloqueo <ext> dentro del *CLI.

Con esto lograremos que al cierre del turno, el trabajador pueda bloquear su teléfono y reactivarlo a la mañana siguiente con solo ingresar su contraseña.

Debo agregar que funciona tan bien que anoche que lo implementé dejé cerrada mi extensión y hoy no me permitía hacer llamadas, hasta que marqué *52 e introduje mi contraseña nuevamente.

¡Suerte!

Reparar todas las tablas de MySQL en un sistema Vicidial

28 Nov

El domingo pasado recibí el correo de un cliente: su sistema de marcación predictiva con vicidial había tenido fallas eléctricas durante el fin de semana, ocasionando que las tablas de MySQL se corrompieran y que el sistema quedara inservible (al menos lo relacionado con la marcación predictiva).

Tras cerca de una hora reparé todas las tablas usando unos comandos simples como los que siguen:

/etc/init.d/mysql stop
cd /var/lib/mysql/asterisk
myisamchk -r *.MYI
/etc/init.d/mysql start

Sin embargo, me percaté que al hacer esto, estaba solo reparando las tablas una por una. El sistema tenía 4 núcleos, por lo que era posible hacer mucho más trabajo al mismo tiempo (4 tareas a la vez) en vez de ir una por una.

El sistema terminó de manera habitual tras el paso de 1 1/2 hrs. Para evitar que tanto tiempo se perdiera en nuevas ocasiones, decidí crear un script en bash que reparara todas las tablas de manera simultánea (limitándose a 1 proceso por cada núcleo del sistema), y me quedó el código así:

#!/bin/bash

# Editar estos valores:
## CORES 		debe ser igual al número de núcleos en el sistema 
##				(revisar el comando "cat /proc/cpuinfo")
## MYSQLDBDIR	debe ser igual a la carpeta de MySQL que contiene la BD
##				que debamos reparar (default: "/var/lib/mysql/asterisk")

CORES=4
MYSQLDBDIR=/var/lib/mysql/asterisk

# Enlistar los archivos
FILES=`ls $MYSQLDBDIR/*.MYI`
RUNS=0
for i in $FILES
do
	SALIR=0
	while [ $SALIR -eq 0 ]
	do
		# Obtener la cantidad de procesos en el sistema
		PROCS=`ps -eF|grep myisamchk|egrep -v "grep"|wc -l`

		# Ejecutamos el while los procesos activos sean menor a CORES
		if [ $CORES -gt $PROCS ]
		then
			echo `date +"%X"` Revisando/Reparando tabla $i
			myisamchk -r $i &
			# Descansamos 1 segundo
			sleep 1
			SALIR=1
		else
			# Esperamos 3 segundos
			sleep 3
		fi
	done

done

# Ya se terminaron de correr todas las instrucciones de reparación,
# pero existe la posibilidad de que algunas de ellas aún estén ejecutándose.
# Revisamos para ver si hay procesos corriendo y avisar al usuario
while [ $SALIR -eq 0 ]
do
	# Obtener la cantidad de procesos en el sistema
	PROCS=`ps -eF|grep myisamchk|egrep -v "grep"|wc -l`
	if [ $PROCS -gt 0 ]
	then
		echo `date +"%X"` Hay $PROCS procesos corriendo. Esperando 10s...
		sleep 10
	else
		echo "No hay procesos corriendo"
		SALIR=1
	fi
done
echo `date +"%X"` Fin. Ya puedes reiniciar MySQL

Los siguientes pasos son algo obvios:

  1. Hay que guardar este script como un archivo checar.sh
  2. Editamos el archivo y cambiamos el número de cores de nuestro sistema, así como la ubicación de la carpeta de MySQL que contiene los archivos de la BD
  3. Darle permisos de ejecución (chmod 755 checar.sh)
  4. Ejecutarlo: ./checar.sh
El script correrá varios procesos en paralelo para revisar cada uno una tabla. Mientras más núcleos tenga nuestro procesador, más rápido el script aprovechará los recursos para crear la reparación.
Espero les sirva.
¡Suerte!

 

Advertencia: Aunque este script puede ayudarnos a reparar nuestras tablas en caso de algún problema (falla eléctrica, corrupción del sistema de archivos, fallo en el disco duro, etc) no hay nada más seguro que respaldar nuestra información periódicamente. Siempre ten a la mano copias de seguridad de aquella información crítica, ya que no podemos hacernos responsables por la pérdida de información causada por el mal uso de las directivas que se muestran en este artículo. Úsalo bajo tu propio riesgo.

Distribución uniforme del consumo de llamadas entre líneas (AGI-PHP)

27 Nov

Tarde o temprano nos vemos en la necesidad de considerar el gasto individual de las lineas telefónicas conectadas a nuestro Asterisk y encontrar una forma en que las llamadas se distribuyan de forma uniforme entre las lineas, no es lo mismo que 90% de la llamadas al exterior se realicen por la primera linea desocupada a que en caso de tener 4 se repartan en un 25% entre ellas.

Debido a esta necesidad me vi en la necesidad de crear un script en AGI PHP que junto con el dialplan nos haga este trabajo:

Para llevar el contador de las llamadas haremos uso de la asteriskdb, la base de datos integrada en asterisk. Lo que haremos es establecer primero las familias y las llaves que llevaran los datos, usando los siguientes comandos desde adentro del Asterisk CLI (*CLI): Continue reading

Permitiendo llamadas no autenticadas del exterior (con restricciones)

26 Abr

Fuente: http://www.datacenta.com/Pictures/stop.jpg

En este blog y en muchos otros se ha discutido el uso de aplicaciones como fail2ban para impedir ataques de fuerza bruta combinado junto con iptablespara denegar el acceso a fuentes no autorizadas. Existe un caso especial que es cuando tenemos que aceptar las llamadas anónimas del exterior sin importar desde donde se originen, y ese es el caso del que voy a comentar a continuación.

Recibir llamadas anónimas no es malo, pero hay que saber lo que se hace y tomar las precauciones necesarias. Al permitir las llamadas entrantes desde cualquier fuente estamos permitiendo que cualquiera nos contacte via IP, para que así el interesado no tenga que paga costos de LD, ni tampoco tenga que hacer algún setup muy elaborado en su conmutador. Ofrecer un peer de autenticación a cada usuario sería un poco difícil, ya que lo que se espera de estos escenarios es que la comunicación sea en un solo sentido (de afuera nos marcan a nosotros hacia adentro) y también, buscas hacerla lo más fácil de marcar para el usuario final.

En nuestro caso, nosotros permitimos llamadas provenientes del exterior pero solo hacia nuestras extensiones internas. De esta manera, no hay costos relacionados con ataques porque las llamadas nunca vuelven a salir hacia la PSTN. Solo pueden comunicarse a nuestras extensiones internas y DIDs. El único problema, es que a veces los equipos que atacan piensan que al no negar las llamadas, vamos a permitirlas salir, así que nos mandan intentos de ataques de unas 40 llamadas simultáneas, lo que hace que nuestros teléfonos comiencen a sonar una y otra vez.

Para evitar que nuestro conmutador se atiborre de llamadas que no deseamos, pero que al mismo paso permita las llamadas que si queremos, debemos permitir lo siguiente desde nuestro GUI:

  • Permitir llamadas anónimas: si

Y luego hacemos esta combicación de plan de llamadas personalizado y fail2ban (estamos usando FreePBX). Dentro del archivo extensions_custom.conf ponemos:

[ext-did-post-custom]
exten => _X.,1,Macro(contador,${SIPCHANINFO(peerip)},3)
exten => _X.,n,Goto(ext-did-catchall,${EXTEN},1)

[macro-contador]
exten => s,1,GotoIf($[${GROUP_COUNT(${ARG1})}<=${ARG2}]?Permite)
exten => s,n,Noop(LLAMADAS EXCEDIDAS POR ${ARG1}. BLOQUEANDO)
exten => s,n,Congestion()
exten => s,n(Permite),Set(GROUP(${CDR(uniqueid)})=${ARG1})

Lo que estoy haciendo aquí es que siempre que reciba una llamada en mi FreePBX que no esté especificada hacia una extensión y/o DID que tenga dado de alta, el contexto [ext-did-post-custom] lo procesará y hará un análisis del número máximo de llamadas simultáneas que debe permitir.

  • Si se exceden 3 llamadas de acuerdo al ejemplo, debo cortar el paso y enviar un mensaje a consola que diga «LLAMADAS EXCEDIDAS»
  • Si no se excede, permito el paso enviando la llamada hacia la cláusula default [ext-did-catchall]
  • Si recibo llamadas desde una fuente «no autorizada» hacia un número que si existe en mi sistema, el contexto [ext-did] lo procesará de manera normal
  • Si el origen ya fuera autorizado, caerá en el contexto que sea que yo le haya especificado y hará caso omiso de esta configuración

Tras hacer la modificación correspondiente y recargar, el mensaje de «LLAMADAS EXCEDIDAS» ya aparece en mis logs. Ahora debo modificar mi fail2ban para que tome en cuenta este texto y bloquee a nivel de firewall a aquellos que nos manden llamadas en bruto.

Basándome en el artículo de fail2ban anterior que publicamos hace unos meses, modifico el /etc/fail2ban/filter.d/asterisk.conf para que quede así:

failregex = Registration from '.*' failed for '(:[0-9]{1,5})?' - Wrong password
Registration from '.*' failed for '(:[0-9]{1,5})?' - No matching peer found
Registration from '.*' failed for '(:[0-9]{1,5})?' - Device does not match ACL
Registration from '.*' failed for '(:[0-9]{1,5})?' - Username/auth name mismatch
Registration from '.*' failed for '(:[0-9]{1,5})?' - Peer is not supposed to register
LLAMADAS EXCEDIDAS DESDE (:[0-9]{1,5})?
NOTICE.* failed to authenticate as '.*'$
NOTICE.* .*: No registration for peer '.*' (from )
NOTICE.* .*: Host failed MD5 authentication for '.*' (.*)
VERBOSE.* logger.c: -- .*IP/-.* Playing 'ss-noservice' (language '.*')
LLAMADAS EXCEDIDAS POR

Si el fail2ban atrapa estas líneas, bloqueará al host por el tiempo que le hayamos especificado en el archivo jail.conf.

Debo destacar que este es un caso muy especial que pocos querrán utilizar, pero es bueno saber que se puede ser precavido y a la vez, flexible con el uso de tu conmutador. Dado que permitir llamadas anónimas expone tu conmutador, la responsabilidad de este código recae en quien lo usa, por lo que no debes aplicarlo si no estas seguro de que es lo que hará.

¡Suerte!

Explotando vulnerabilidades en teléfonos autenticados: Yealink

24 Abr

Actualización 30/julio/2013: Podemos confimar que al usar el firmware más reciente en este momento (9.70.0.140) la funcionalidad de marcación sin autenticación que provocaba este problema de seguridad viene desactivado, y es necesario configurar el teléfono con la IP autorizada que podrá hacer uso del API, por lo que este problema ya no existe. Sin embargo, nunca está de más asegurar su red y cambiar las contraseñas default del teléfono para impedir cualquier acceso no autorizado.

Yealink T20P: teléfono básico

Hace unos días recibí una llamada de uno de mis clientes. La historia comienza con una de las frases que menos me gusta escuchar en mi medio:

– Christian, ¿puedes venir? Nos hackearon el conmutador de la oficina…

Acudí en menos de una hora al sitio (yo no tenía acceso remoto y ni siquiera tenía conocimiento de este equipo) y me dí a la labor de revisar los logs para tratar de reconstruir el caso. Sin embargo, me encontré con lo siguiente:

  • El equipo tenía servicios de iptables y fail2ban protegiéndolo
  • Las contraseñas de SIP eran seguras
  • No se recibían conexiones ni por SIP ni por HTTPS del exterior
  • Todas las llamadas habían sido originadas desde una extensión
  • Apache no tenía registros de IPs externas, tampoco Asterisk.

Todo parecía indicar que hubiera sido un inside job, y en efecto (al menos parcialmente), así fue.

Por más que busqué no encontré señales de como había logrado entrar, pero las llamadas estaban en el CDR y en el gateway SIP que terminaba la comunicación. ¿Qué ocurrió entonces?

De pronto, el empleado al que pertenece el teléfono cuya cuenta SIP ocasionó todas las llamadas, exclamó lo siguiente:

– Mi teléfono ha estado raro. Varias veces en la mañana estuvo «marcando solo» y yo solamente escuchaba una grabación a veces de que la llamada no había podido ser completada

Momento… ¿marcando solo? ¿Desde cuando los teléfonos se marcan solos? Se trataba de un Yealink T20, el cual es un modelo extremadamente sencillo y económico. ¿Qué funcionalidad podría tener que hiciera que marcara solo?

La respuesta es: los Yealink ofrecen la posibilidad de marcación automática a través de una simple y sencilla petición HTTP que requiere autenticación, pero si nunca cambian la contraseña de usuario y administrador del teléfono (lo cual casi nadie hace), cualquiera puede hacer que el teléfono marque solo hacia cualquier destino.

Si tienen un teléfono Yealink consigo (cualquier modelo), prueben hacer esto en su casa/oficina:

Supongamos que la IP de nuestro Yealink sea 192.168.1.101. Asumimos que en nuestro Asterisk existe un plan de marcación que nos permite marcar larga distancia. Imaginemos el número 018881234567. Ahora bien, abran el siguiente URL en su navegador favorito (recuerden cambiar los valores según corresponda):

http://user:user@192.168.1.101/cgi-bin/ConfigManApp.com?Id=34&Command=1&Number=018881234567&Account=0

¿Y qué ocurre? Simple y sencillamente, su teléfono marcará por ustedes hacia el número indicado usando la primer cuenta que tengan configurado en él. Si Asterisk no los autentica a nivel de usuario, ustedes tienen un severo problema de seguridad ya que cualquier persona podría manipular su teléfono para marcar hacia donde le viniera en gana.

Y bien, ¿qué pasa si abren el URL repetidas ocasiones? Fácil. El teléfono marcará una y otra vez a ese número sin colgar las otras llamadas. De manera que un solo teléfono podría hacer 100 llamadas a Afghanistan, Sierra Leona o cualquier otro destino caro del mundo. Esto es muy fácil de lograr con un script en Linux que invoque al wget. Lo demostré en laboratorio y creanme, los resultados asustan.

Imaginen por un momento que alguien tuviera acceso a su red interna, pero no conoce las contraseñas de los teléfonos. Con este truco no es necesario saberlas, ya que el teléfono se encuentra ya autenticado en su red interna y por lo tanto, las llamadas saldrán aún si ustedes confían en que su firewall no recibirá conexiones del exterior.

Por tal motivo, si ustedes deciden abrir erróneamente los puertos en su firewall y exponen la configuración del teléfono, le están abriendo la puerta a alguien a que haga cientos de llamadas sin autorización. En la historia que les acabo de contar, alguien aprovechó esta vulnerabilidad e hizo más de 40,000 minutos de llamadas a los destinos más caros del mundo en tan solo 12 horas, y sin tener que usar su ancho de banda para generar esas llamadas.

Piénsenlo muy bien antes de que expongan equipos al internet. Hay un mundo peligroso allá afuera, y si no se protegen, más temprano que tarde acabarán siendo víctimas de la cyber delincuencia.

¡Suerte!

La importancia de una línea de código: como hacer debug a tu configuración de Asterisk

20 Mar

El fin de semana por la noche recibí una llamada de un cliente nuestro solicitando apoyo para revisar el estado de su E1. Al parecer habían estado con el servicio de telefonía caido todo ese día, y tras horas de conferencia con su carrier no habían podido encontrar la razón por la cual el servicio del E1 no levantaba. Asterisk podía hacer llamadas entre extensiones, pero no tenían comunicación con el exterior.

Por experiencia, mi primer acercamiento fue revisar el archivo chan_dahdi.conf, que es el responsable del E1. Lo abrí pero no noté nada fuera de lo normal, así que desde la consola de Asterisk procedí a descargarlo de memoria y cargarlo nuevamente, esperando que arrojara algo de información. El resultado fue algo como lo siguiente:

server1*CLI> module load chan_dahdi.so
[Mar 20 02:03:52]   == Registered application 'DAHDISendKeypadFacility'
[Mar 20 02:03:52]   == Registered application 'ZapSendKeypadFacility'
[Mar 20 02:03:52]   == Parsing '/etc/asterisk/chan_dahdi.conf': [Mar 20 02:03:52] Found
[Mar 20 02:03:52] WARNING[9583]: config.c:778 process_text_line: parse error: No category context for line 6 of /etc/asterisk/chan_dahdi.conf
[Mar 20 02:03:52] ERROR[9583]: chan_dahdi.c:12728 setup_dahdi: Unable to load chan_dahdi.conf
[Mar 20 02:03:52]   == Registered channel type 'DAHDI' (DAHDI Telephony Driver w/PRI)
[Mar 20 02:03:52]   == Manager registered action DAHDITransfer
[Mar 20 02:03:52]   == Manager registered action ZapTransfer
[Mar 20 02:03:52]   == Manager registered action DAHDIHangup
[Mar 20 02:03:52]   == Manager registered action ZapHangup
[Mar 20 02:03:52]   == Manager registered action DAHDIDialOffHook
[Mar 20 02:03:52]   == Manager registered action ZapDialOffHook
[Mar 20 02:03:52]   == Manager registered action DAHDIDNDon
[Mar 20 02:03:52]   == Manager registered action ZapDNDon
[Mar 20 02:03:52]   == Manager registered action DAHDIDNDoff
[Mar 20 02:03:52]   == Manager registered action ZapDNDoff
[Mar 20 02:03:52]   == Manager registered action DAHDIShowChannels
[Mar 20 02:03:52]   == Manager registered action ZapShowChannels
[Mar 20 02:03:52]   == Manager registered action DAHDIRestart
[Mar 20 02:03:52]   == Manager registered action ZapRestart
[Mar 20 02:03:52]  Loaded chan_dahdi.so => (DAHDI Telephony w/PRI)

De aquí lo que importa es la línea que nos da el error:

[Mar 20 02:03:52] WARNING[9583]: config.c:778 process_text_line: parse error: No category context for line 6 of /etc/asterisk/chan_dahdi.conf

Tras abrir el archivo chan_dahdi.conf nuevamente y prestar un poco más de atención, las primeras líneas indican lo siguiente:

;autogenerated by /usr/sbin/wancfg_dahdi do not hand edit
;autogenrated on 2011-10-11
;Dahdi Channels Configurations
;For detailed Dahdi options, view /etc/asterisk/chan_dahdi.conf.bak

language=es

[trunkgroups]

[channels]
context=default
usecallerid=yes
hidecallerid=no
callwaiting=yes
usecallingpres=yes
callwaitingcallerid=yes

Y aquí es donde tras poner atención, notamos que el error proviene de una sola línea de código, el language=es está fuera de cualquier contexto, y esto provoca un error de parsing al momento de cargar el módulo, lo que impide que el canal se cargue correctamente y por lo tanto, el E1 nunca levante.

Entonces, ¿Cuales son los pasos para hacer un debug correcto de la configuración de Asterisk?

Los sistemas informáticos, a diferencia de los seres vivos, no cambian por si solos. Si nuestro sistema estaba bien de principio y de pronto algo dejó de funcionar es porque algo cambió. Una manera sencilla de ver los archivos de configuración en el orden en que fueron recientemente modificados es usar el comando ls:

ls -lSt /etc/asterisk | head -n 20

Con este comando ordenaremos los archivos de configuración de más reciente a menos reciente, mostrando solo los últimos 20 modificados. De ahí, podemos ir retrocediendo en nuestros pasos hasta que demos con la línea de código que alteramos y el sistema se restituya tras recargar el módulo o reiniciar Asterisk.

Si tenemos localizado el archivo con el problema, pero no sabemos exactamente en que línea está, el CLI de Asterisk puede resultar muy útil. Solo debemos asegurarnos que el log de eventos se arroja a consola editando el archivo /etc/asterisk/logger.conf

#/etc/asterisk/logger.conf
[general]

[logfiles]
console => warning,error,notice,debug
full => notice,warning,error,verbose

Como se aprecia en el archivo, estamos diciéndole a Asterisk que envie a consola toda la información relacionada con eventos warning, error, notice y debug (nota: el debug arroja MUCHA información, si hemos resuelto el problema lo recomendable es retirarlo del archivo hasta la siguiente vez que lo ocupemos). Recuerden que después de modificarlo deben recargar el logger dentro del Asterisk CLI

*CLI> logger reload

Y por último pueden descargar/cargar el módulo que da problemas en memoria. Este es un ejemplo del módulo de SIP:

amx*CLI> module unload chan_sip.so
Unloaded chan_sip.so
== Unregistered channel type 'SIP'
== Unregistered custom function SIPCHANINFO
== Unregistered custom function SIPPEER
== Unregistered custom function SIP_HEADER
== Unregistered custom function CHECKSIPDOMAIN
== Unregistered application 'SIPDtmfMode'
== Unregistered application 'SIPAddHeader'
== Unregistered application 'SIPRemoveHeader'
== Unregistered RTP glue 'SIP'
== Manager unregistered action SIPpeers
== Manager unregistered action SIPshowpeer
== Manager unregistered action SIPqualifypeer
== Manager unregistered action SIPshowregistry
== Manager unregistered action SIPnotify
amx*CLI> module load chan_sip.so
Loaded chan_sip.so
SIP channel loading...
== Parsing '/etc/asterisk/sip.conf': == Found
== Parsing '/etc/asterisk/sip_general_additional.conf': == Found
== Parsing '/etc/asterisk/sip_general_custom.conf': == Found
== Parsing '/etc/asterisk/sip_nat.conf': == Found
== Parsing '/etc/asterisk/sip_registrations_custom.conf': == Found
== Parsing '/etc/asterisk/sip_registrations.conf': == Found
== Parsing '/etc/asterisk/sip_custom.conf': == Found
== Parsing '/etc/asterisk/sip_additional.conf': == Found
== Parsing '/etc/asterisk/sip_custom_post.conf': == Found
== Parsing '/etc/asterisk/users.conf': == Found
[2012-03-20 01:48:54] WARNING[4896]: chan_sip.c:28479 reload_config: No valid transports available, falling back to 'udp
'.
== SIP Listening on 0.0.0.0:5060
== Using SIP TOS bits 96
== Using SIP CoS mark 4

En este otro ejemplo puede notarse como el módulo SIP arroja un WARNING. Eso es un típico caso de que algo en mi configuración no está 100% bien, por lo que conviene revisarlo.

Este paso de quitar/cargar el módulo en memoria resulta muy adecuado porque la información que arroja ese módulo no se pierde entre toda la demás al momento de hacer un reload. Fíjense en los detalles y pregúntense que quiso decir el CLI cuando les dió un warning o error. Los mensajes suelen ser bastante explícitos, solo depende de ustedes interpretarlos.

¡Suerte!