Kamailio y el modulo LCR en CentOS 7

El modulo LCR de Kamailio, es un modulo complejo de configurar pero muy potente. Pensado para proveedores de llamadas que manejan muchas rutas, gateway, prefijos. Se puede utilizar también para  averiguar si determinadas llamadas entrantes proceden de los Gateway configurados y de esta formas gestionarlas. La función más importante activada por el modulo es load_gws() que lo que hace es guardar en una variable de tipo AVP todos los Gateway que se podrán utilizar para terminar la llamada (según el numero marcado). La función realiza esta clasificación utilizando los siguientes criterios:

  • toma la parte usuario de la Request URI (numero marcado) y busca en las tablas el prefijo que más se acerca a ese numero
  • si encuentra más de un resultado, la regla con la prioridad más alta será procesada primero
  • si hay distintas reglas con la misma prioridad la regla con peso más alto se procesará primero

En el caso en que el parámetro del modulo priority_ordering tiene como valor 1, los criterio de clasificación serán:

  • en base a la prioridad
  • si hay distintas reglas con la misma prioridad se procesará primero las regla con peso más alto

Para la prioridad (entre 0 y 255), más alto el numero indicado, más baja la prioridad; 0 = prioridad más alta, 255 = Prioridad más baja)

Para el peso, más alto el numero (entre 1 y 254), más será utilizado el Gateway en comparación con un Gateway con un valor numérico más bajo.

Una regla puede ser marcada como “stopper”; en este caso no se tomarán en cuenta las demás reglas cuyos prefijos sean más cortos del presente en la regla declarada como stopper.

Ya solo con esta breve introducción, se pueden dar cuenta de la complejidad de las reglas que se pueden configurar. La definición de las reglas y de como serán utilizadas se basa en tres tablas presentes en la base de datos kamailio:

  • lcr_gw donde se configuran los Gateway
  • lcr_rule donde se configuran las reglas
  • lcr_rules_target donde se definen Gateway, prioridad y peso para las reglas definidas

Vamos a empezar con la tabla lcr_gw:

image

  • id numero progresivo asignado automáticamente a cada entrada
  • lcr_id es el campo más importante ya que se repite en las tres tablas y es el que permite la conexión entre ellas; además de eso en la función load_gws() una de las opciones que se indica es propio aquella contenida en el campo lcr_id. Un ejemplo: utilizo el valor uno del campo lcr_id para los Gateway que uso para las llamadas entrantes y utilizo un valor dos para los Gateway que uso para las llamadas salientes.
  • gw_name nombre que se le asigna al Gateway
  • ip_addr dirección IP del Gateway
  • hostname nombre de dominio del Gateway. Si se prefiere indicar el nombre de dominio en lugar de la IP. Si se indica el nombre de dominio no se indica la IP y viceversa
  • port el puerto utilizado por el Gateway
  • params parámetros que se pueden añadir a la Request URI del INVITE creado por Kamailio y enviado  al Gateway. Cada parámetro tiene que empezar con un punto y coma ;
  • uri_scheme vacío = sip 1 =sip 2 = sips
  • transport 1 = udp 2 = tcp 3 = tls 4 = sctp vacío o 0 = nulo
  • strip representa el números de caracteres que se quitarán a la Request URI. Ejemplo: numero marcado 0057123456789 y strip = 2; la nueva Request URI será 57123456789
  • prefix prefijo a adicionar a la Request URI, una vez eliminados el números de caracteres indicados en el campo strip, antes de enviar la solicitud al Gateway. Útil si se trabaja con proveedores que requieren utilizar un prefijo para salir con una ruta en lugar de otra
  • flags banderas numéricas que se pueden asociar al Gateway y almacenar en una variable de tipo AVP y utilizar en el script de Kamailio
  • defunct contiene una marca de tiempo que indica hasta cuando el Gateway será considerado como inactivo. Util cuando se realizan mantenimientos y se quiere desactivar el gateway temporalmente.

Seguimos con la tabla lcr_rule:

image

  • id numero progresivo asignado automáticamente a cada entrada
  • lcr_id se indica el mismo numero entero del Gateway y/o grupos de Gateway que en la tabla lcr_gw tienen el mismo numero. De esta forma la regla aplicará solamente a esos Gateway
  • prefix el prefijo al que aplicará la regla; ejemplo 57310 (celulares Claro Colombia)
  • from_uri expresión regular aplicada a la cabecera From de la Solicitud SIP. De esta forma es posible aplicar la regla solamente a determinadas extensiones/dominios configurados
  • request_uri  expresión regular aplicada a la Request URI (que corresponde a la cabecera To de la solicitud). De esta forma la regla aplicará solamente a determinadas Request URI
  • stopper como ya se ha dicho, si este campo está activado (valor 1)  no se tomarán como posibles destinos Gateway cuyos prefijos sean más cortos del que está presente en la regla declarada como stopper
  • enabled si la regla está activa (valor 1) o no (valor 0)

Terminamos con la tabla lcr_rule_target:

image

  • id numero progresivo asignado automáticamente a cada entrada
  • lcr_id se indica el mismo numero entero del Gateway y/o grupos de Gateway que en la tabla lcr_gw tienen el mismo numero. De esta forma la regla aplicará solamente a esos Gateway
  • rule_id id de la regla presente en la tabla lcr_rule al que aplicará las opciones definidas en esta tabla
  • gw_id id del Gateway al que aplicará la entrada. El Gateway debe pertenecer al mismo lcr_id declarado en la regla)
  • priority la prioridad asociada a la regla
  • weight el peso asociado a la regla

Ahora que conocemos las tablas que conforman la configuración, podemos añadir las primeras entradas:

mysql -u root -p

MariaDB [(none)]> use kamailio

Primero los dos Gateway que se utilizarán:

MariaDB [kamailio]> insert into lcr_gw (lcr_id,gw_name,ip_addr,port,uri_scheme,transport) values ('1','voztovoice1','5.6.7.8','5060','1','1');

MariaDB [kamailio]> insert into lcr_gw (lcr_id,gw_name,ip_addr,port,uri_scheme,transport) values ('1','voztovoice2','8.7.6.5','5060','1','1');

Modificar 5.6.7.8 y 8.7.6.5 con direcciones IP validas asociadas a Gateway reales. Ambos pertenecerán al “grupo” 1 el esquema será sip y protocolo de transporte udp. Se continua con las reglas que tienen que personalizar según el país en que la van a utilizar. En mi caso, para Colombia, voy a añadir dos reglas: la primer para marcar a un prefijo de celular Movistar y la segunda para marca a cualquier celular de Colombia (todos los celulares empiezan con 3).

MariaDB [kamailio]> INSERT INTO lcr_rule (lcr_id, prefix) VALUES ('1', '57318');

MariaDB [kamailio]> INSERT INTO lcr_rule (lcr_id, prefix) VALUES ('1', '573');

Importante destacar que el valor lcr_id es el mismo presente en la tabla lcr_gw; esto quiere decir que las dos reglas aplicarán a todos los gateway de ese “grupo”

Se continua con los “blancos” de cada regla:

MariaDB [kamailio]> INSERT INTO lcr_rule_target (lcr_id, rule_id, gw_id, priority, weight) VALUES (1, 1, 1, 1, 1);

MariaDB [kamailio]> INSERT INTO lcr_rule_target (lcr_id, rule_id, gw_id, priority, weight) VALUES (1, 2, 2, 1, 1);

Con la primera entrada se define que el grupo de gateway es lcr_id 1, la regla será aquella con id 1 en la tabla lcr_rule y el gateway utilizado será el que tiene id 1. La prioridad será 1 y un peso 1. La segunda entrada utilizará también el grupo de Gateway con lcr_id 1, el id de la regla de la tabla lcr_rule será el 2, el gateway utilizado será aquello con id 2 y prioridad y peso serán iguales a la primera entrada. Más adelante veremos como se utilizarán estas reglas.

La configuración del modulo LCR no está presente en el archivo de configuración predefinido de Kamailio. Empezamos con la configuración del modulo:

nano kamailio.cfg

en el bloque se los módulos, se añade:

# ----- lcr params -----
loadmodule "lcr.so"
modparam("lcr","db_url", DBURL)
modparam("lcr", "lcr_count", 10)
modparam("lcr", "gw_uri_avp", "$avp(i:709)")
modparam("lcr", "ruri_user_avp", "$avp(i:500)")
modparam("lcr", "tag_avp", "$avp(lcr_tag)")
modparam("lcr", "flags_avp", "$avp(i:712)")
modparam("lcr", "lcr_rule_hash_size", 1024)
modparam("lcr", "lcr_gw_count", 256)
modparam("lcr", "dont_strip_or_prefix_flag", -1)

Una explicación de las distintas lineas:

1. una descripción
2. se carga el modulo
3. se configura la URL para la conexión a la base de datos kamailio (DBURL es la variable que contiene el enlace de la conexión)
4. valor máximo que se puede utilizar en el campo lcr_id
5. variable de tipo AVP donde la función load_gws (de la que hablaremos más adelante) guardará la información de los Gateway encontrados para una determinada regla/llamada
6. variable de tipo AVP donde la función next_gw (de la que hablaremos más adelante) guardará la parte usuario de la Request URI para la sucesiva llamada
7. variable de tipo AVP donde las funciones next_gw y from_gw guardarán el valor del campo tag de la tabla lcr_gw
8. variable de tipo AVP donde las funciones next_gw y from_gw guardarán el valor del campo flags de la tabla lcr_gw
9. tamaño de la tabla HASH donde se guardarán las reglas definidas en la tabla lcr_rule. El proceso se basa en el prefijo presente en la tabla de las reglas. Más alto el numero (el valor tiene que ser una potencia de 2) más memoria necesaria pero menos problemas de posibles choques entre los prefijos.
10. Numero máximo de Gateway que se pueden configurar en la tabla lcr_gw (el valor tiene que ser una potencia de 2)
11. si esta parámetro se define y su valor se añade al campo flags de la tabla lcr_gw para un determinado Gateway, para ese Gateway no se realizará la operación de quitar el numero de caracteres definido en el campo strip y añadir el prefijo presente en el campo prefix. Valor -1 = desactivado. Cualquier valor entero = activado

Como seguramente se han dato cuenta, no hay forma de saber si un determinado Gateway está activo o no. Es posible enviar paquetes OPTIONS a los gateway pero solamente si se han marcado como inactivos, utilizando, en el script de Kamailio, la función inactivate_gw() y definiendo los siguientes parámetros del modulo:

modparam("lcr", "defunct_capability", 1)
modparam("lcr", "lcr_id_avp", "$avp(s:lcr_id_avp)")
modparam("lcr", "defunct_gw_avp", "$avp(s:defunct_gw_avp)")
modparam("lcr", "ping_interval", 15)
modparam("lcr", "ping_inactivate_threshold", 3)
modparam("lcr", "ping_valid_reply_codes", "404")
modparam("lcr", "ping_from", "sip:sip10.voztovoice.org")

Una vez que el Gateway conteste correctamente a los paquetes OPTIONS y sea marcado como activo nuevamente, no recibirá más OPTIONS. En teoría este tipo de comportamiento tiene su logica ya que en el caso del modulo LCR lo que se busca es terminar la llamada por lo menos con un gateway de los configurados.

De las funciones activadas por el modulo, utilizaremos:

  • load_gws(lcr_id[, uri_user[, caller_uri]]) Esta función carga todas los posibles gateway que se pueden utilizar para terminar la llamada (utilizando los prefijos) y los ordena en base a la longitud del prefijo, prioridad y peso. Guarda el resultado en la variable $avp(i:709) definida en el parámetro  gw_uri_avp. Acepta las siguientes opciones:
    • lcr_id que corresponde al valor presente en la tabla lcr_gw y permite la conexión con las tablas lcr_rule y lcr_rule_target. Puede ser el valor o una pseudo variable
    • uri_user si se indica se utilizará este valor en lugar de la parte usuario de la Request URI. Puede ser una pseudo variable
    • caller_uri puede contener un valor alfanumérico o una pseudo variable; si no se indica su valor por defecto será nulo
  • next_gw() una vez encontrados todos los posibles destinos a través de la función load_gws, se utiliza la función next_gw para ir utilizándolos. Esta función lo que hace es sacar el primer valor encontrado en la variable $avp(i:709) (el primer gateway seleccionado), elimina ese valor de la variable y con los datos contenidos modifica la Request URI de forma que la solicitud SIP vaya al primer gateway seleccionado. Si la llamada al primer gateway falla y se vuelve a utilizar  nuevamente la función next_gw(), ésta sacará el segundo destino presente en la variable $avp(i:709) y así a seguir hasta cuando hayan destinos disponibles

Ahora falta modificar el script de Kamailio. En el archivo predefinido, después de este bloque:

# user location service
route(LOCATION);

añadimos:

# lcr service
route(LCR);

y antes del bloque que inicia con:

route[RELAY] {

añadimos:

route[LCR] {
if(!is_method("INVITE")) return;
if(!load_gws(1, $rU, $fu)){
    xlog("L_NOTICE", "No se seleccionaron Gateway");
    sl_send_reply("500", "Server Internal Error - Cannot load gateways");
    exit;
  } else {
        xlog("L_NOTICE","GW Seleccionado '$avp(i:709)'");
        xlog("L_NOTICE","Cabecera To: $tu");
  }
  if(!next_gw()) {
    xlog("L_NOTICE","No hay más gateway para procesar la solicitud");
    sl_send_reply("503", "Service not available, no gateways found");
    exit;
  } else {
        xlog("L_NOTICE","Llamando el primer gateway seleccionado");
        xlog("L_NOTICE","Variable ruri_user_avp: '$avp(i:500)'");
        xlog("L_NOTICE","Cabecera To: después de la función next_gw: $tu");
        xlog("L_NOTICE","Llamada de $fu a $ru");
        t_on_failure("LCR_FAILURE");
        route(RELAY);
        exit;
  }
}

en la segunda linea la lógica es: si la solicitud SIP no es un INVITE terminar el procesamiento de la subruta. En el primer bloque: si la función load_gws no devuelve un resultado, se contesta con error 500 y se termina el procesamiento de la solilcitud; en caso contrario se envía al LOG de Kamailio las dos lineas que siguen en else. En el segundo bloque si la función next_gw no devuelve ningún resultado significa que no hay gateway disponibles para terminar la llamada, se devuelve error 503. Si hay gateway disponibles se envían las cuatro lineas que siguen al LOG de Kamailio y se procesa la solicitud de forma stateful. Si desde el gateway se recibe una respuesta => a 300, se envía la solicitud a la ruta de tipo failure con nombre LCR_FAILURE. Como esa ruta no existe, hay que crearla. Al final del archivo, añadimos:

failure_route[LCR_FAILURE] {
       if (t_is_canceled()) {
                exit;
        }

        if (t_check_status("408|[56][09][09]"))  {
                if(next_gw()) {
                        xlog("L_NOTICE","Variable ruri_user_avp: '$avp(i:500)'");
                xlog("L_NOTICE","Cabecera To: después de la función next_gw: $tu");
                xlog("L_NOTICE","Llamada de $fu a $ru");
                t_on_failure("LCR_FAILURE");
                route(RELAY);
                exit;
                }
                else {
                        send_reply("404", "No More Gateways");
                        exit;
                }
        }
}

En primer bloque se averigua si la solicitud ha sido anulada (CANCEL). En el segundo bloque con la función  t_check_status se controla el tipo de error que se ha recibido desde el Gateway. Si el error es 408 o un error desde 500 a 699, se ejecuta nuevamente la función next_gw para cargar el gateway que sigue en la lista de los seleccionados con la función load_gws. Si hay uno disponible se repite el envío de la solicitud de forma stateful y se repite nuevamente la linea t_on_failure. Si el segundo gateway también falla se continua con los que siguen hasta agotar las opciones disponibles. Una vez que no haya más gateway disponibles se devolverá el error 404 "No More Gateways" y se terminará el procesamiento de la solicitud SIP.

Para saber si una llamada es entre extensiones o saliente, personalmente he activado el modulo URI_DB. En el bloque de los modulos añadimos:

# ---- uri_db ----

loadmodule "uri_db.so"
modparam("uri_db", "db_url", DBURL)
modparam("uri_db", "db_table", "subscriber")
modparam("uri_db", "use_uri_table", 0)

y luego en la sub ruta:

route[LOCATION] {

después de este bloque:

#!ifdef WITH_ALIASDB
        # search in DB-based aliases
        if(alias_db_lookup("dbaliases")) {
                route(SIPOUT);
        }
#!endif

añadimos:

if (!does_uri_exist()) {
                return;
                exit;
        }

cuya logica es: si la parte usuario de la Request URI (antes de la arroba) no es un usuario existente en la tabla subscriber, se termina el procesamiento de la subruta LOCATION y se pasa a la subruta LCR que es la que sigue en la ruta principal. La función does_uri_exist es activada por el modulo URI_DB. Se guardan los cambios y se reinicia Kamailio:

systemctl restart kamailio

Para ver la lista de gateway configurados con relativas opciones:

kamcmd lcr.dump_gws

Para las reglas:

kamcmd lcr.dump_rules

Para recargar la configuración del modulo en el caso se realicen modificaciones en las tablas del modulo:

kamcmd lcr.reload

Ahora se abre un softphone configurado con una extensión presente en Kamailio y se realiza una primera llamada utilizando un numero con prefijo 57318. En este caso la función load_gws seleccionará los dos gateway y los ordenará de la siguiente manera: primero el gateway con prefijo 57318 (id 1) y luego, si el primero falla, el gateway con prefijo 573 (id 2). Para ver la selección en el LOG de Kamailio, aumentamos el nivel de debug:

kamcmd cfg.set core debug 3

y luego utilizamos este comando:

tail -f /var/log/kamailio.log | grep lcr

para seleccionar solamente las lineas relacionadas con el modulo LCR. Llamando el numero es resultado será:

gw

primero se seleccionará el gateway con id 1 y luego el gateway con id 2. Si se marca 573101234567, por ejemplo, el resultado será:

image

se seleccionará solamente el gateway con id 2 ya que el primero con el prefijo 57318 no empareja con el prefijo marcado mientras 573 si. Ahora se pueden realizar algunas pruebas. Activar en la primera regla (57318) el campo stopper:

MariaDB [kamailio]> update lcr_rule set stopper='1' where id='1';

Recargar las reglas:

kamcmd lcr.reload
kamcmd lcr.dump_rules

En este caso, como la regla 57318 se ha configurado como stopper si marcamos nuevamente el numero 573181234567, se debería seleccionar solamente el primer gateway:

image

esto porque el campo stopper activado no permite la busqueda de reglas cuyo prefijos sean más cortos del prefijo donde se ha activado el stopper. De hecho si modificamos la segunda regla:

MariaDB [kamailio]> update lcr_rule set prefix='573181' where id='2';

Recargamos las reglas:

kamcmd lcr.reload
kamcmd lcr.dump_rules

y marcamos el numero 573181234567, el resultado será:

image

En este caso el segundo Gateway será seleccionado por primero ya que el prefijo 573181 se acerca más al numero marcado del prefijo 57318. Ahora volvemos a poner la tabla como estaba inicialmente y modificamos la IP del primer servidor para ver si el respaldo funciona correctamente:

MariaDB [kamailio]> update lcr_rule set prefix='573' where id='2';

MariaDB [kamailio]> update lcr_rule set stopper='0' where id='1';

MariaDB [kamailio]> update lcr_gw set ip_addr='1.2.3.4' where id='1';

Se pone una IP completamente inexistente y se recarga la configuración:

kamcmd lcr.reload
kamcmd lcr.dump_rules

ahora como el primer gateway no funcionará, se debería seleccionar el segundo:

kamcmd cfg.set core debug 2

tail -f /var/log/kamailio.log

Se marca 573181234567. El resultado:

NOTICE: <script>: GW Seleccionado '1|sip:|0|||67305985||5060||;transport=udp|0'
NOTICE: <script>: Cabecera To: sip:573181234567@sip10.voztovoice.org
NOTICE: <script>: Llamando el primer gateway seleccionado
NOTICE: <script>: Variable ruri_user_avp: '573181234567'
NOTICE: <script>: Cabecera To: después de la función next_gw: sip:573181234567@sip10.voztovoice.org
NOTICE: <script>: Llamada de sip:1000@sip10.voztovoice.org a sip:573181234567@1.2.3.4:5060;transport=udp
NOTICE: <script>: GW Seleccionado '1|sip:|0|||67305985||5060||;transport=udp|0'
NOTICE: <script>: Cabecera To: sip:573181234567@sip10.voztovoice.org
NOTICE: <script>: Llamando el primer gateway seleccionado
NOTICE: <script>: Variable ruri_user_avp: '573181234567'
NOTICE: <script>: Cabecera To: después de la función next_gw: sip:573181234567@sip10.voztovoice.org
NOTICE: <script>: Llamada de sip:1000@sip10.voztovoice.org a sip:573181234567@1.2.3.4:5060;transport=udp
NOTICE: <script>: Variable ruri_user_avp: '573181234567'
NOTICE: <script>: Cabecera To: después de la función next_gw: sip:573181234567@sip10.voztovoice.org
NOTICE: <script>: Llamada de sip:1000@sip10.voztovoice.org a sip:573181234567@173.256.200.157:5060;transport=udp

Se intenta sacar la llamada por el primer gateway y como falla se saca por el segundo. Se vuelve a poner la IP correcta en la tabla lcr_gw:

MariaDB [kamailio]> update lcr_gw set ip_addr='1.2.3.4' where id='1';

Si recuerdan en la tabla lcr_rule es posible utilizar una expresión regular para el campo from_uri; esto significa que si la cabecera From de la solicitud empareja con el valor contenido en el campo, la llamada se cursará sino no. Este escenario se puede utilizar, por ejemplo, para diferenciar las reglas por usuarios o dominios. Vamos a añadir la siguiente expresión regular en las dos reglas presentes:

MariaDB [kamailio]> update lcr_rule set from_uri='sip:.*@sip11\.dominio\.org';

Con esta regla solamente las extensiones que pertenecen al subdominio sip11.dominio.org (en un escenario multidominio) podrán utilizar las dos reglas configuradas. Se actualiza la configuración:

kamcmd lcr.reload
kamcmd lcr.dump_rules

kamcmd cfg.set core debug 3

tail -f /var/log/kamailio.log | grep lcr

Si se marca el numero 573181234567 desde la extensión 1000 de otro dominio (ejemplo sip10.dominio.org), el resultado:

gw

No habrán gateway disponibles. Si se configura la extensión 1000 del segundo dominio y se marca el mismo numero, la llamada debería funcionar.

¿Que opinan?

Distribuir contenido Distribuir contenido