Arquitecturas VoIP Geodistribuidas: EdgeRouter’s como RTPRelay’s locales

Buenas.

Hace ya cierto tiempo comentamos lo interesantes que son los dispositivos de Ubiquiti, dada su apertura y posibilidades en cuanto a despliegue de software. En aquella ocasión, compilamos Kamailio y sus módulos principales para MIPSEL. Esta vez, nos ha tocado para MIPS.

Sentando las bases de la necesidad: Arquitecturas geodistribuidas

A nivel profesional, los que trabajamos en los mundos VoIP y derivados, nos peleamos habitualmente con proyectos internacionales, en los que hay escenarios variados, con sedes grandes distribuidas y, en ocasiones sedes muy pequeñas y/o road-warriors.

El caso es que si a estos panoramas se añaden políticas de atención a clientes «follow the sun» con colas geodistribuidas / prioridades, así como DDIs regionales, el enfoque técnico suele tender a centralizar los servicios, pero garantizar la supervivencia, aunque sea degradada.

En estos casos, y poniendo sobre la mesa las necesidades de DDIs locales, una de las soluciones -en ausencia de líneas PSTN locales- pasa por optar por ITSP’s y tener todo full SIP, sin ningún tramo ni gestión PSTN. Cuando hablamos de soluciones Cloud en Amazon, OVH o similares, siendo la contratación de PSTN’s imposible, es casi el único camino.

¿Pero cual es el problema habitual? El problema habitual es casi siempre de round trip / jitter. Si tenemos un cliente internacional con mucho peso en Europa, y todo montado en un datacenter Europeo profesional,  y tenemos una delegación pequeña en China; cuando contratamos un DDI «Virtual» al ITSP, lo que sucederá muy probablemente, en los casos en los que por definición de lógica de negocio la llamada acabe en China, será lo siguiente:

esquema_problema_rtppath (1)

De este esquema, lo único que está realmente en nuestra mano es el Register(si procede) / Indicación al ITSP para estipular de dónde queremos recibir las llamadas. Una vez que optamos por recibirlas en nuestro Proxy/B2BUA/X, estamos ya «vendidos»: conseguir luego que el terminal remoto tenga audio directo con el ITSP sin hacer el roundtrip completo es una odisea importante en la mayoría de las situaciones

Relays de Audio Locales

En nuestra humilde experiencia, en este tipo de entornos muchas veces se opta por gateways PSTN como comentábamos al principio, siendo el esquema esperado:

esquema_con_gateway

Es decir, se despliega un gateway, y se busca siempre cumplir con el SIP+RTP Trapezoid, para evitar que el media realice dos saltos inter-continentales, independientemente de que tengamos un proxy, un b2bua o una combinación de ambos.

Continuando por este camino, partiendo de la base de haber establecido que queremos que sea un SIP Trunk lo que tengamos en la oficina remota / país lejano, el esquema que proponemos pasaría a ser:

esquema_con_rtp_relay

Lo que hemos ido viendo a lo largo de estos años es que, para determinados países, la contratación directa del SIP Trunk con un proveedor local tiene por lo general mas garantías de calidad. Otra cosa es que nos guste / o no tener los servicios agrupados en un mayorista tipo DIDWW, Voxbone y verlo todo muy claro así como unificar la gestión.

Montando el escenario con Kamailio y Asterisk

Gestionando los Registers

Es probable que el proveedor local requiera que nos registremos, para que «sepa localizarnos», sobre todo en caso de IP’s dinámicas y tal.

En este caso, si tenemos esta necesidad, podemos optar por:

  • Si tenemos un Asterisk en nuestro sistema de telefonía, lanzar el Register desde ahí.
  • Lanzar un register periódico desde Kamailio (con UAC Module).

Generalmente, la mejor opción suele ser optar por lanzarlo desde Kamailio. Así, en caso de darse una caída de conectividad con el sistema central, se mantiene dicho Register periódico.

Opción: Uso de UAC en Kamailio

En el caso de Kamailio, la definición de los Registers que hay que gestionar sólo puede hacerse vía DB, así que teniendo en cuenta que nuestro objetivo es el EdgeRouter, para evitar acceso a I/O y el overhead de algo tan potente como MySQL, optamos por SQLITE3.

El primer paso es crear el fichero DB y la tabla:

# sqlite3 /etc/kamailio/kamailio.db
sqlite> CREATE TABLE `uacreg` (
   ...>   `id` int(10) NOT NULL,
   ...>   `l_uuid` varchar(64) NOT NULL DEFAULT '',
   ...>   `l_username` varchar(64) NOT NULL DEFAULT '',
   ...>   `l_domain` varchar(128) NOT NULL DEFAULT '',
   ...>   `r_username` varchar(64) NOT NULL DEFAULT '',
   ...>   `r_domain` varchar(128) NOT NULL DEFAULT '',
   ...>   `realm` varchar(64) NOT NULL DEFAULT '',
   ...>   `auth_username` varchar(64) NOT NULL DEFAULT '',
   ...>   `auth_password` varchar(64) NOT NULL DEFAULT '',
   ...>   `auth_proxy` varchar(64) NOT NULL DEFAULT '',
   ...>   `expires` int(11) NOT NULL DEFAULT '0');
sqlite>

Nota: Es prácticamente igual que si fuera creada con kamdbctl pero quitando el auto_increment en el ID, que parece ser que no lo soporta bien.

A continuación, la configuración del módulo para su DB_URL es tal que:

modparam("uac", "reg_db_url", "sqlite:///etc/kamailio/kamailio.db")
modparam("uac", "reg_contact_addr", "192.168.1.2:5060")

Nota: el contact address, será el contact que envíe el módulo en los re-registros periódicos, podemos optar por poner nuestra IP Pública, si la conocemos y puerto nateado. Aunque, sinceramente, lo mejor suele ser optar por dejar la IP Privada, ya que el ITSP que hayamos escogido detectará la situación de NAT y actuará en consecuencia 🙂

Y con esto sería todo 🙂 Para probarlo, añadimos una entrada en la tabla uacreg:

sqlite> insert into uacreg values(1,1,'alice','alicedomain','alice','alicedomain','asterisk','alice','alicesecret','sip:10.10.0.201',120);

Capturando, vemos como Kamailio lanza automáticamente el Register y responde correctamente al desafío de autenticación:

register_flow

Opción: Uso de Register vía Outbound Proxy en Asterisk

En el caso de Asterisk, sería tan sencillo como:

register => [email protected]:password@IP_DEL_PROXY/XXXX

Añadiendo eso en sip.conf, generamos un register periódico desde Asterisk con username «user», contraseña «password», request domain del Register «myitsp.com», enviado vía «IP_DEL_PROXY» y como contact XXXX. A modo de ejemplo, en este caso con un Asterisk en 10.10.9.45 y en 10.10.0.201 un supuesto Proxy (no activo ahora mismo):

register

Comentarios sobre ambas opciones

En ambas opciones, quizás nos interese tocar el contact, si el ITSP utiliza el username del contact para meter la llamada (en lugar del DID, también habitual), quizás queramos adaptarlo.

Tráfico saliente

Lado Asterisk

La gestión del tráfico saliente, en el caso de Asterisk, es realmente trivial utilizar un outbound proxy como salida.

Tanto si usamos Realtime como si usamos static definition en sip.conf, el atributo a configurar es «outboundproxy». A modo de ejemplo:

[myitsp]
type=peer
host=myitsp.con
outboundproxy=10.10.0.201
disallow=all
allow=alaw

Con esta definición, cualquier llamada que lancemos:

exten => ........ Dial(SIP/myitsp/0034944048182)

Será lanzada con request uri en el INVITE: «[email protected]» pero enviada físicamente a 10.10.0.201

Lado Kamailio

En el Kamailio, prácticamente nos vale la misma configuración default, dado que lo estamos haciendo de la forma oficial y el routing a dominos externos está permitido por defecto:

# dispatch requests to foreign domains
route(SIPOUT);

En este ejemplo, hemos cogido el mismo FQDN de pruebas (myitsp.com) y 10.10.9.45 es el B2BUA (Asterisk) y 10.10.1.220 el Kamailio:

invite_via_outbound_proxy

 

Tráfico entrante

Lado Asterisk

De nuevo, en el lado de Asterisk la configuración es casi trivial. Atendiendo a cómo realiza Asterisk el matching, aunque tengamos definido el peer de salida, hay que definirlo también en entrada:

[myitsp-in]
type=peer
host=10.10.0.201
disallow=all
allow=alaw
insecure=port,invite
context=incoming (...o el que sea)

 

Y con esa definición sería todo. Cualquier llamada entrante desde el ITSP será enviada al Kamailio, que la mandará a este Asterisk y matcheará con «myitsp-ín». También se se enviará al context «incoming», donde tendremos que darle el tratamiento correspondiente.

Lado Kamailio

En el lado Kamailio, se recibirán las requests iniciales (INVITE) que inician el diálogo. Hay que cerciorarse de validar que vengan del ITSP y de encaminarlas a dónde corresponda, en nuestro caso al B2BUA Asterisk.

Para ello, basándonos en la configuración default que se instala:

[...]
        # record routing for dialog forming requests (in case they are routed)
        # - remove preloaded route headers
        remove_hf("Route");
        if (is_method("INVITE|SUBSCRIBE"))
                record_route();

        # account only INVITEs
        if (is_method("INVITE")) {
                setflag(FLT_ACC); # do accounting
        }

        # dispatch requests to foreign domains
        route(SIPOUT);
[...]

 

Podemos dejarlo, de forma simple para ir probando con algo así como:

[...]
        if (is_method("INVITE|SUBSCRIBE"))
                record_route();

        # account only INVITEs
        if (is_method("INVITE")) {
                setflag(FLT_ACC); # do accounting
        }

        if (is_method("INVITE") && ($si=="10.10.9.45"))
        {
                xlog("L_INFO","Routing $rm from $fu (IP:$si:$sp) to $ru\n");
                $du="sip:10.10.1.2";
                route(SIPOUT);
        }
[...]

Nota: Se podría hacer con variables / más elegante, es únicamente para ilustrar.

Con esta prueba, lanzando una prueba nos queda un flujo tipo:

callflow_routed

 

Gestión del Media

Bueno, hasta aquí hemos llegado y en cuanto a la señalización pura, no habría que resolver grandes desafíos. Lo podríamos complementar con las lógicas de failover que comentamos en su momento para tener un escenario más real. ¿Pero qué pasa con el media? ¿No hemos iniciado esta travesía para lidiar con el media y para evitar eso que decíamos del roundtrip? Efectivamente, así es, y sirva esta parte final de este post para comentar estos aspectos 😉

Antes de nada, pongamos sobre la mesa el comportamiento de los elementos implicados:

Comportamiento de Asterisk esperado

chan_sip (1.8.x)

En el caso de Asterisk, si se cumple que la llamada en sí no se esté grabando y que no tenga que hacer transcoding, que el peer/friend no esté definido con NAT=YES y, por último, que no tenga que escuchar tonos dtmf por rfc2833 para lanzar features por ejemplo => intentará liberar el media path.

Todo esto siempre que los dos interlocutores estén definidos con directmedia=yes o directmedia=update. El comportamiento por defecto es el de generar un reinvite en cuanto se establece el diálogo, cursando únicamente unos pocos paquetes rtp.

Sirva este pantallazo comentado de SNGREP, son su flamante nuevo soporte para RTP para ilustrarlo:

sngrep_liberacion_audio

Evitando ReInvites: DirectRTPSetup

Desde tiempos de 1.8.x, existe un parámetro en sip.conf con nombre directrtpsetup comentado en la documentación oficial y en el propio sip.conf de samples:

;directrtpsetup=yes             ; Enable the new experimental direct RTP setup. This sets up
                                ; the call directly with media peer-2-peer without re-invites.
                                ; Will not work for video and cases where the callee sends
                                ; RTP payloads and fmtp headers in the 200 OK that does not match the
                                ; callers INVITE. This will also fail if directmedia is enabled when
                                ; the device is actually behind NAT.

Desde nuestra más humilde experiencia, aunque la feature la marquen como «experimental», llevamos ya unos cuantos proyectos de envergadura con muchísimo tráfico PBX Style (grupos de salto, redirecciones, capturas, ….) y la verdad es que no hemos tenido nunca ningún problema activando dicha setting.

 

CHAN_PJSIP

Con PJSIP el comportamiento esperado es el mismo que con directmedia=yes y sin directrtpsetup (no soportado). Es decir, si apostamos por este channel driver para gestionar nuestra parte SIP, tenemos que si o si lidiar con ReInvites en cada establecimiento de llamada.

RTPPRoxy / RTPEngine

Para este tipo de entornos y desafíos, Kamailio de por sí no basta, requiere de un agente de media, en este caso un Media Proxy / RTP Proxy para poder conectar el audio hacia/desde el ITSP con el servidor central y/o los terminales de la delegación concreta.

¿Cual escogemos? ¿Cómo lo instalamos? ¿Dónde debemos activarlo?

Desde el punto de vista técnico, RTPEngine es el presente y futuro, con packet relaying a nivel de kernel y soporte de muchas features increíbles como la repacketización o el briding DTLS/RTP por ejemplo.

Sin embargo, en nuestro caso nos centraremos en RTPProxy, por estar disponible en los repositorios de Debian para MIPS/MIPSEL de forma oficial y por su facilidad de despliegue en nuestro objetivo final: EdgeRouter de Ubiquiti.

Sea como fuere, lo que si es pseudo-compartido entre ellos es el comportamiento:

  • En muchas ocasiones (siempre que hay NAT), el proxy de media no sabe realmente a que puerto / IP tiene que mandar el media.
  • Así que generalmente, se señaliza (SDP) contra una IP / Puerto y se espera a recibir tráfico RTP ahí, una vez recibido, se queda «locked» a ese punto y el media se gestiona contra dicha IP/Puerto.

A nivel de configuración con Kamailio, aparte de cargar el módulo y activarle el control (UNIX Socket / UDP Control), es necesario invocarlo para que realice el rewrite del SDP:

  • En las requests que contengan SDP (INVITE generalmente).
  • En las replys que contengan SDP (200 OK / 183 Session Progress generalmente).
    • (on reply route)

Para invocarlo, podemos hacer uso de la función rtpproxy_manage directamente, tanto en la request route como en reply route:

    rtpproxy_manage('coraf');

En este caso, para ilustrar los flags usados:

c - flags to change the session-level SDP connection (c=) IP if media-description also includes connection information.
o - flags that IP from the origin description (o=) should be also changed.
r - flags that IP address in SDP should be trusted. Without this flag, rtpproxy ignores address in the SDP and uses source address of the SIP message as media address which is passed to the RTP proxy.
a - flags that UA from which message is received doesn't support symmetric RTP. (automatically sets the 'r' flag)
f - instructs rtpproxy to ignore marks inserted by another rtpproxy in transit to indicate that the session is already goes through another proxy. Allows creating a chain of proxies.

Es decir, queremos que se modifique el connection information (en otro caso no tendría sentido nada de esto), así como el origin (no es estrictamente necesario) y, sobre todo, lo mas importante: la opción ‘r’.

Con la opción ‘r’, nos fiamos de lo que indique el SDP. Lo podemos hacer tranquilamente ya que:

  • El ITSP señalizará bien su extremo de audio, con una IP pública accesible.
  • Asterisk hará tb su parte correctamente, primero señalizándose contra si mismo (locuciones iniciales, colas, etc …) y finalmente señalizando contra el terminal local.

Así como la opción ‘a’, que evita intentar simpre symmetric RTP  (a parte de que fuerza obviamente la opción r), dado que no estamos en un escenario de NAT con ventana abierta para un puerto de salida, activando estas dos opciones conseguimos que se libere bien el media path:

esquema_logos_rtprelayers_

Nota: ¿Que sucede con los Re-Invites?

Los ReInvites deben ser gestionados exactamente igual, dado que siempre que sea Asterisk quien señalice el Audio, los SIPEndpoints son accesibles, con lo que no hay riesgo.

 

Pruebas de Rendimiento con Kamailio + RTPProxy en EdgeRouter Lite

En su momento os hablábamos de EdgeRouter-X. Hoy va a ser lo contrario. En esta ocasión hemos optado directamente por EdgeRouter Lite. Principalmente, hemos tomado esta camino porque por un coste económico razonable (sigue siendo un precio imbatible) tenemos mas espacio, un rootfs intercambiable, así como 2 núcleos MIPS. Por otra parte, es algo mas grande que el EdgeRouter-X, y tiene más apariencia de «sólido» 😉 Además, tenemos en cuenta que luego siempre nos gusta Netflow / captagent y derivados …

root@ubnt:/home/ubnt# cat /proc/cpuinfo  | egrep "(proce|model)"
processor		: 0
cpu model		: Cavium Octeon+ V0.1
processor		: 1
cpu model		: Cavium Octeon+ V0.1

Preparando el entorno

RTPProxy

Como suele ser habitual en EdgeOS, lo primero es habilitar los repos de Debian:

configure
set system package repository wheezy components 'main contrib non-free'
set system package repository wheezy distribution wheezy 
set system package repository wheezy url http://http.us.debian.org/debian
set system package repository wheezy-security components main
set system package repository wheezy-security distribution wheezy/updates
set system package repository wheezy-security url http://security.debian.org
commit
save
exit

Una vez hecho esto, lo primero es instalar y configurar RTPProxy:

apt-get install rtpproxy

[...]

root@ubnt:~# cat /etc/default/rtpproxy 
CONTROL_SOCK=udp:127.0.0.1:22222
EXTRA_OPTS="-l 10.10.0.153"

Una vez configurado, tenemos RTPProxy listo para dar servicio y ser controlado por el puerto UDP22222 de localhost.

Kamailio

Para instalar Kamailio, el gran Kaian nos lo ha dejado muy cómodo. Tras pasar por la fábrica Jenkins, os lo hemos dejado listo para consumir en el repo público:

configure
set system package repository irontec components 'main extra'
set system package repository irontec distribution kamailio43-wheezy 
set system package repository irontec url http://packages.irontec.com/debian
commit
save
exit

y con ello, basta con:

apt-get update && apt-get install kamailio

Resultados

Tras lanzar una batería de pruebas de concurrencia, la esperada tabla resumida de nuestras pruebas:

edge_router3_test_RTP

 

Es decir, podemos comentar que a partir de los 10 diálogos concurrentes con RTP, empiezan a perder rendimiento y lo esperado es tener una merma progresiva en lo que a calidad perceptible se refiere (MOS / Mean Opinion Score).

Eso sí, estamos hablando del modelo básico, cercano a los 70€ en precio 😉 Si optamos por el PRO seguramente no tengamos ningún problema de este estilo.

Pero si nos mantenemos por debajo de ese umbral, lo esperado es tener una calidad perceptible cercana a la máxima que da el codec (3,92 en el caso de G729A, que es el que hemos probado).

Despedida y cierre

La verdad es que nos ha quedado un artículo similar al de los mecanismos de supervivencia que comentamos hace unos meses, pero queremos insistir humildemente en lo interesante que es la contratación de servicios de ITSP locales en lugar de optar por un revendedor mayor (aceptando los contras de perder gestión de trunks centralizada y depender de un ITSP que igual no es tan estable en el tiempo 😉 ).

En nuestra opinión, cuando se habla de proyectos Internacionales sin posibilidad de líneas físicas locales, y localizados en países como China, Tailandia o Colombia, este mecanismo nos está dando más que buenos resultados. De hecho, es un caso que hemos comentado recientemente en nuestra web, así que no dudéis en echarle un vistazo.

 



¿Te gusta este post? Es solo un ejemplo de cómo podemos ayudar a tu empresa...
Sobre Gorka Gorrotxategi

CTO en Irontec, en el frente técnico desde un par de lustros ya, para todo lo que tenga que ver con Networking, VoIP y Sistemas, en ese orden :D) Desde @zetagor escribo algo, pero poco verbose la verdad

1 Comentario

¿Por qué no comentas tú también?


  • Mola Zgor. Bien explicado todo.

    manwe Hace 8 años Responde


Queremos tu opinión :)