“Atravesando” el NAT con OpenSIPS 1.6 y MediaProxy

Hoy vamos a ver como configurar OpenSIPS 1.6 y MediaProxy para solucionar los problemas que el NAT nos pueda traer.

Configurar el proxy para solucionar los problemas de NAT suele ser de las primeras cosas que se pretende o se hace al configurarlo, pero en muchas ocasiones se olvida el primer paso: entender el NAT. En su día escribí un post con una breve introducción al NAT, pero lo que os recomiendo es que os leáis varias veces éste post de Iñaki Baz Castillo explicando como afecta el NAT al protocolo SIP así así como los distintos tipos de soluciones.

En este post vamos a ver como “arreglar” el NAT con OpenSIPS 1.6 y MediaProxy. El módulo mediaproxy de OpenSIPS nos servirá en combinación con el propio MediaProxy para arreglar el SDP y hacer que el RTP pase por MediaProxy, mientras que para arreglar la parte de señalización utilizaremos el módulo nat_traversal.

El módulo nat_traversal

Este módulos nos servirá para arreglar la cabecera Contact de los mensajes SIP nateados y mantener el NAT abierto haciendo keepalive del lado servidor. El módulo nat_traversal tiene una particularidad que le diferencia de otros módulos similares: fue diseñado para tratar entornos multi-proxy, como por ejemplo el caso en que el proxy que mantiene el keepalive no sea el que gestiona los REGISTER (el registrar). No vamos a entrar en detalle en este post, pero conviene tenerlo en cuenta…

El módulo mediaproxy

Este módulo gestiona la comunicación con el servidor MediaProxy, de manera que el módulo modificara el SDP para que el audio pase por MediaProxy, que actuara de media relay. Como comente en un post anterior MediaProxy soporta ICE, por lo que podremos arreglar el NAT en prácticamente todos los casos (salvo los malditos ALGs).

Antes de empezar

Antes de comenzar la descripción de la configuración necesaria es importante comentar que en este post vamos a utilizar el método fácil. El módulo mediaproxy tiene 2 formas de funcionar:

  1. Funciones use_media_proxy y end_media_session: han de ser llamadas por el usuario, por lo tanto este ha de saber exactamente cuando quiere que MediaProxy comience o acabe su funcionamiento. Grosso modo tendremos que encargarnos del primer INVITE, los in-dialog-requests y el BYE. Lo veremos en otro post 😉
  2. Función engage_media_proxy: utiliza el módulo dialog para llamar a las funciones del punto 1 internamente. El usuario solo tiene que llamar a la función engage_media_proxy en el primer INVITE y olvidarse, el resto se realizara internamente. El hecho de que sea tan fácil también trae algunas limitaciones, que veremos mas adelante.

Arreglando la señalización

Primero vamos a configurar el módulo nat_traversal, con el que podremos arreglar el NAT en la señalización.

loadmodule "nat_traversal.so"
...
modparam("nat_traversal", "keepalive_interval", 60)
modparam("nat_traversal", "keepalive_method", "OPTIONS")
modparam("nat_traversal", "keepalive_extra_headers", "X-Keepalive: yes\r\n")
modparam("nat_traversal", "keepalive_state_file", "/var/run/opensips/keepalive_state")

Con los parámetros indicados haremos que el módulo nat_traversal mande un mensaje SIP OPTIONS cada 60 segundos para mantener el NAT abierto. Ademas, añadiremos la cabecera X-Keepalive, simplemente para identificar estos paquetes mejor.

Para hacer que el módulo comience a enviar los OPTIONS será necesario que llamemos a la función nat_keepalive(). La particularidad de esta función es que no provoca que el mecanismo comience de inmediato, sino que será activado en caso de recibir/generar una respuesta positiva a un REGISTER o SUBSCRIBE y cuando un dialogo comience con un INVITE.

...
if ((method=="REGISTER" || method=="SUBSCRIBE" ||
(method=="INVITE" && !has_totag())) && client_nat_test("3"))
{
nat_keepalive();
}
...

Ademas del keepalive, también tendremos que modificar los mensajes SIP para que la cabecera Contact contenga la IP y puerto reales desde los que vino el paquete en lugar de lo que el terminal del usuario haya puesto. También añadiremos el parámetro rport a la cabecera Via, de manera que OpenSIPS anyadira la IP y el puerto desde los que recibió el paquete a la primera cabecera Via aunque el terminal no indicara soporte para rport. Para ello incluiremos lo siguiente en nuestro request route:

if (client_nat_test("3")) {
force_rport();
fix_contact();
}

Es posible que las respuestas también vengan nateadas, porque el terminal al que llamamos esta detrás de NAT así que tenemos que tratarlas en un onreply route:

if (client_nat_test("1")) {
fix_contact();
}

Ya solo nos queda una cosa para terminar con la señalización: fijar el AVP necesario para que OpenSIPS guarde la dirección IP y puerto desde la que se ha registrado el usuario en la tabla location de su base de datos:

modparam("registrar", "received_avp", "$avp(s:received_uri)")
...
$avp(s:received_uri) = $source_uri;

La pseudovariable $source_uri es exportada por el módulo nat_traversal y contiene la IP, puerto y transporte desde los que se recibió el paquete.

Arreglando el RTP

Una vez tenemos la señalización arreglada necesitamos hacer que el RTP pase por nuestro servidor MediaProxy. Para ello modificaremos el SDP de manera que la IP y puerto desde los que se supuestamente se enviara el RTP sean realmente el servidor de MediaProxy.

Como hemos comentado anteriormente vamos a utilizar el método fácil, por lo que solo tendremos que llamar a la función engage_media_proxy() para el primer invite:

if (is_method("INVITE") && !has_totag()) {
engage_media_proxy();
}

Limitaciones

Todo no puede ser perfecto, por lo que el método que aquí hemos utilizado puede no ser el mejor para todos los casos. En concreto estas son las limitaciones que el sistema utilizado conlleva:

  • Se utilizara siempre MediaProxy: dado que la función engage_media_proxy() utiliza el módulo dialog para funcionar es necesario llamarla al comienzo del dialogo, así que llamarla al recibir un 200 OK nateado seria demasiado tarde.
  • Manejo de múltiples diálogos: tanto OpenSIPS como MediaProxy sufren al recibir múltiples respuestas con SDP a un INVITE (dos 183 por ejemplo), por lo que los resultados son inesperados.

Dado que la mayoría de usuarios están detrás de NAT y que gracias a la arquitectura escalable de MediaProxy podemos ampliar los recursos a medida que los vayamos necesitando, tendremos que valorar la facilidad en la configuración frente a la flexibilidad en la gestión de NAT.

Happy NAT traversing!

  • Hola Saul, se te olvidó agregar el link de Iñaki Baz Castillo, para leer la explicación sobre el Nat 😉

    Saludos.

  • saghul

    Aupa Juanmi,

    Parece que el wordpress me la había jugado, ya está arreglado 🙂

  • Gracias 😉

  • Raúl Alexis Betancor Santana

    Estupenda y clara explicación, incluido la aclaración sobre las limitaciones del desastroso módulo dialog.

    A ver para cuando el post que explique el uso del otro método, el de las funciones separadas.

    Saludos

  • Pelayo

    Hola Saúl,

    vaya, como se echan de menos ese tipo de artículos. De OpenSIPS se algo pero de MediaProxy casi nada, para llevar a cabo esta integración entiendo que habrá que instalar antes MediaProxy en la misma máquina que el SER y llevar a cabo algún tipo especial de configuración verdad?

    Gracias y salu2.

  • saghul

    @Pelayo: Solo el dispatcher tiene que estar en la misma máquina que OpenSIPS. El Relay, que es el que se encarga realmente del RTP puede ir instalado en otra máquina.

  • Ramsesaguirre

    Que tal esta muy interesante tu post pero yo realmente soy nuevo en el uso de sip proxy me gustaria saber como puede conectar openser con asterisk, tengo una pequeña pc donde lo tengo instalado mi escenario es el siguiente:

    Quiero configurar:

    OpenSER (Kamailio) -> Asterisk

    Openser para el registro de los usuarios y Asterisk para hacer llamadas entre extensiones, voicemail y salas de conferencias dentro de la misma pc

    Saludos
    ramsesaguirre@yahoo.com.mx

  • Monica

    hola, tengo un openips en la ip 209.190.200.50 y dos usaurios detras de nat, quiero conectar las dos, pero me quedo sin audio, segui este tutorial, pero no pude instalar el mediaproxy, podes hacer un tutorial de instalar mediaproxy en un centos5 32bits?
    graias

  • Anónimo

    CentOS 5 tiene librerías de hace 5 años, MediaProxy 2.5 no puede funcionar con ellas. Tus opciones son usar MediaProxy 2.4.4 o CentOS 6.

    Saludos,

  • Monica

    hola, gracias por su pronta ayuda. Fantastico tu bog, estoy mirando todos los posts.
    Te comento, estoy empesando con opensips, y ya lo tengo andando, con lo seguiente. 1 opensips, 2 asterisk con a2billing y un server para mysql.
    Te queria consultar, sobre la parte de NAT, para mi projecto, que configuracion recomendas, y que modulo? Saludos

  • Monica

    hola, pude instalar el mediaproxy, y cuando inicio con el comando media-dispatcher, no me sale ningun error.

    pero cuando inicio opensips me sale
    ERROR:mediaproxy:mediaproxy_connect: failed to connect to /var/run/mediaproxy/dispatcher.sock: No such file or directory

    verifico el direcctorio /var/run/mediaproxy/, existe pero no tiene nada.

    saludos

  • Monica

    mira lo que me sale

    Starting MediaProxy Dispatcher 2.4.4
    using _sigchld
    error: Traceback (most recent call last):
    error: File “/usr/bin/media-dispatcher”, line 56, in ?
    error: from mediaproxy.dispatcher import Dispatcher
    error: File “/usr/lib/python2.4/site-packages/mediaproxy/dispatcher.py”, line 36, in ?
    error: from mediaproxy.tls import X509Credentials, X509NameValidator
    error: File “/usr/lib/python2.4/site-packages/mediaproxy/tls.py”, line 12, in ?
    error: from gnutls import crypto
    error: File “build/bdist.linux-i686/egg/gnutls/crypto.py”, line 11, in ?
    error: File “build/bdist.linux-i686/egg/gnutls/validators.py”, line 9, in ?
    error: File “build/bdist.linux-i686/egg/gnutls/constants.py”, line 38, in ?
    error: File “build/bdist.linux-i686/egg/gnutls/library/__init__.py”, line 139, in ?
    error: File “build/bdist.linux-i686/egg/gnutls/library/errors.py”, line 15, in ?
    error: File “build/bdist.linux-i686/egg/gnutls/library/functions.py”, line 124, in ?
    error: File “/usr/lib/python2.4/site-packages/ctypes/__init__.py”, line 353, in __getattr__
    error: func = self.__getitem__(name)
    error: File “/usr/lib/python2.4/site-packages/ctypes/__init__.py”, line 358, in __getitem__
    error: func = self._FuncPtr((name_or_ordinal, self))
    error: AttributeError: /usr/lib/libgnutls.so.13: undefined symbol: gnutls_certificate_get_x509_cas

  • Anónimo

    Eso es que MediaProxy no ha arrancado correctamente. Puedes arrancarlo en foreground con –no-fork. Si tienes dudas puedes escribir a la lista de OpenSIPS, allí te ayudaremos entre todos 🙂

  • Anónimo

    Comprueba que tienes la versión adeucada de GNUTLS y python-gnutls.

  • Anónimo

    Yo utilizaría MediaProxy y NAT_traversal, pero trabajo en ello, así que mi opinión es un poco parcial 😉

  • Monica

    bueno, me imagino que ya estoy molestando con tantas preguntas. Pero empece a estudiar el opensips hace unos 10 días, y estoy enamorada. Pero son tantas las dudas…..

    como dije abajo, mi idea es poner 4 servidores,
    1 siprproxy – openips,
    1 database – mysql
    2 mediaproxy – asterisk

    y a2billing para tarifar las llamadas.

    en este momento tengo los 3 servidores en producción, 2 asterisk y uno mysql.

    mi principal duda es:

    en que opensips me puede ayudar a mejorar, aparte de load_balancer y register?

    dejo mi MSN para si alguien me quiere dar una mano…..jejejejeje monica@addphone.net

    saludos

  • Monica

    hola, si lo tengo instalado, el archivo /usr/local/lib/libgnutls.so existe.
    instale gnutls-2.4.2 y python-gnutls-1.1.9

    saludos

  • Anónimo

    Hum, puede que necesites una versión más reciente de GNUTLS. No uso CentOS, así que no se decirte. Si que he probado MediaProxy en CentOS 6, pero en el 5 ni idea… Igual te sirve este tutorial creado por un usuario: http://kb.smartvox.co.uk/index.php/opensips/installing-mediaproxy-2-centos-5-64bit/