Enviado por admin el
RTPEngine es probablemente uno de los componentes más críticos y menos comprendidos en infraestructuras VoIP modernas. Desarrollado originalmente por Sipwise y ahora mantenido activamente en GitHub, este proxy de medios se ha convertido en el estándar de facto para manejar escenarios complejos de NAT, transcoding y conversión de protocolos entre clientes legacy UDP y modernos WebRTC. A pesar de su popularidad y del alto volumen de consultas en foros y listas de correo, la documentación clara y estructurada sigue siendo escasa. Este artículo busca llenar ese vacío proporcionando una guía práctica desde la instalación hasta configuraciones avanzadas con ejemplos reales de producción.
¿Por qué RTPEngine y no RTPProxy o MediaProxy?
Antes de sumergirnos en la implementación, es importante entender por qué RTPEngine se ha convertido en la opción preferida. RTPProxy fue durante años el proxy de medios por excelencia para Kamailio y OpenSIPS, pero tiene limitaciones significativas: no soporta nativamente SRTP, carece de capacidades de transcoding, y su arquitectura no está optimizada para el alto rendimiento que demandan las implementaciones modernas. MediaProxy, por su parte, nunca alcanzó la madurez ni el soporte comunitario necesarios. RTPEngine llena estos huecos ofreciendo soporte completo para SRTP/DTLS, transcoding de codecs, bridging entre IPv4/IPv6, manejo inteligente de ICE para WebRTC, y una arquitectura de alto rendimiento basada en kernel forwarding cuando está disponible. Para escenarios donde necesitas que un cliente WebRTC con SRTP se comunique con un servidor Asterisk legacy que solo habla RTP sobre UDP, RTPEngine es prácticamente la única solución viable sin modificar código de aplicación.
Arquitectura y Componentes
RTPEngine consiste en dos componentes principales que trabajan juntos. El daemon rtpengine es el proceso principal que maneja toda la lógica de control: recibe comandos vía protocolo ng (un protocolo binario propietario sobre UDP o UNIX socket), procesa señalización SDP, mantiene tablas de sesiones activas, y coordina el flujo de medios. El módulo de kernel xt_RTPENGINE es opcional pero altamente recomendado para producción, ya que permite forwarding de paquetes RTP directamente en kernel space, reduciendo drásticamente la latencia y el uso de CPU. Cuando el módulo de kernel está disponible, rtpengine solo procesa los primeros paquetes de cada flujo para establecer las reglas de forwarding, y el kernel se encarga del resto. Sin el módulo de kernel, todo el procesamiento ocurre en userspace, que aunque funcional, consume significativamente más recursos en escenarios de alto tráfico.
Instalación en AlmaLinux 9
AlmaLinux 9 se ha convertido en una excelente opción para infraestructura VoIP después de la transición de CentOS. La instalación de RTPEngine en esta distribución requiere algunos pasos específicos debido a dependencias y la necesidad de compilar el módulo de kernel. Primero necesitamos habilitar los repositorios necesarios y asegurarnos de que tenemos las herramientas de compilación. El repositorio EPEL es esencial ya que contiene varias dependencias que no están en los repos base de AlmaLinux.
dnf -y updatesed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
rebootdnf install -y epel-release dnf config-manager --set-enabled crb dnf groupinstall -y "Development Tools"
dnf install --nogpgcheck https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-9.noarch.rpm -y dnf install -y kernel-devel kernel-headers gcc make pkgconfig glib2-devel zlib-devel openssl-devel
dnf install -y pcre-devel libcurl-devel xmlrpc-c-devel libevent-devel json-glib-devel hiredis-devel
dnf install -y iptables-devel libpcap-devel mariadb-devel spandsp-devel bcg729-devel ffmpeg-devel
dnf install -y iptables-legacy-devel libmnl-devel libnftnl-devel pandoc libwebsockets-devel
dnf install -y ncurses-devel opus-devel libjwt-devel mysql-devel libatomic
Una vez que tenemos las dependencias, procedemos a clonar el repositorio oficial y compilar. Es crucial compilar tanto el daemon como el módulo de kernel para obtener el máximo rendimiento.
cd /usr/src
git clone https://github.com/sipwise/rtpengine.git
cd rtpengine/daemon
make
make install
cd ../kernel-module
make
make install
Después de la compilación, necesitamos cargar el módulo de kernel y asegurarnos de que se cargue automáticamente en cada reinicio.
modprobe xt_RTPENGINE
echo "xt_RTPENGINE" > /etc/modules-load.d/rtpengine.conf
lsmod | grep RTPENGINE
Si el último comando muestra el módulo cargado, estás listo para continuar. Ahora creamos el usuario y los directorios necesarios para el servicio.
useradd -r -s /bin/false rtpengine
mkdir -p /var/run/rtpengine /var/log/rtpengine
chown rtpengine:rtpengine /var/run/rtpengine /var/log/rtpengine
Configuración Básica para NAT Traversal
El archivo de configuración principal de RTPEngine se ubica típicamente en /etc/rtpengine/rtpengine.conf. Para un escenario básico de NAT traversal, donde tu servidor tiene una IP privada pero necesita comunicarse con clientes externos, la configuración debe especificar correctamente las interfaces con sus direcciones públicas y privadas. Aquí está un ejemplo funcional comentado:
[rtpengine]
interface = 10.0.1.50!203.0.113.45
listen-ng = 127.0.0.1:2223
port-min = 20000
port-max = 30000
timeout = 60
silent-timeout = 3600
delete-delay = 30
log-level = 6
log-facility = local1
log-facility-cdr = local1
log-facility-rtcp = local1
La línea más crítica aquí es interface = 10.0.1.50!203.0.113.45, donde 10.0.1.50 es la dirección IP privada de tu servidor y 203.0.113.45 es tu IP pública. El formato IP_PRIVADA!IP_PUBLICA le indica a RTPEngine que cuando reciba comandos para sesiones, debe anunciar la IP pública en el SDP pero vincular los sockets a la IP privada. El parámetro listen-ng define dónde RTPEngine escucha comandos de control desde Kamailio u otro proxy SIP. El rango de puertos 20000-30000 define qué puertos UDP usará para los flujos RTP, asegúrate de que tu firewall permita este rango. Los parámetros de timeout controlan cuándo RTPEngine considera una sesión muerta: timeout se aplica cuando hay actividad unidireccional, silent-timeout cuando no hay actividad en absoluto, y delete-delay es el tiempo que espera antes de eliminar completamente una sesión después de recibir un comando delete.
Configuración del Servicio Systemd
Para que RTPEngine se ejecute como un servicio gestionado por systemd, necesitamos crear un archivo de unidad. Crea /etc/systemd/system/rtpengine.service con el siguiente contenido:
[Unit]
Description=RTPEngine Media Proxy
After=network.target
[Service]
Type=forking
PIDFile=/var/run/rtpengine/rtpengine.pid
ExecStart=/usr/bin/rtpengine --config-file=/etc/rtpengine/rtpengine.conf --pidfile=/var/run/rtpengine/rtpengine.pid
ExecStop=/bin/kill -TERM $MAINPID
Restart=always
RestartSec=5
User=rtpengine
Group=rtpengine
[Install]
WantedBy=multi-user.target
Ahora habilitamos e iniciamos el servicio:
systemctl daemon-reload
systemctl enable rtpengine
systemctl start rtpengine
systemctl status rtpengine
Verifica los logs para asegurarte de que inició correctamente:
tail -f /var/log/messages | grep rtpengine
Deberías ver mensajes indicando que RTPEngine inició, cargó el módulo de kernel si está disponible, y está escuchando en la interfaz de control configurada.
Integración con Kamailio: Configuración Básica
La integración de RTPEngine con Kamailio requiere el módulo rtpengine. En tu kamailio.cfg, primero carga el módulo y configura los parámetros de conexión:
loadmodule "rtpengine.so"
modparam("rtpengine", "rtpengine_sock", "udp:127.0.0.1:2223")
modparam("rtpengine", "rtpengine_disable_tout", 20)
modparam("rtpengine", "rtpengine_tout_ms", 1000)
modparam("rtpengine", "rtpengine_retr", 5)
En tu lógica de enrutamiento de INVITE, necesitas llamar a rtpengine_offer para el INVITE inicial y rtpengine_answer para las respuestas. Aquí está un ejemplo básico de manejo de NAT:
route[NATMANAGE] {
if (is_method("INVITE")) {
if (has_body("application/sdp")) {
if (rtpengine_offer("replace-origin replace-session-connection ICE=remove")) {
t_on_reply("NATMANAGE_REPLY");
}
}
}
if (is_method("ACK") && has_body("application/sdp")) {
rtpengine_answer("replace-origin replace-session-connection");
}
if (is_method("BYE|CANCEL")) {
rtpengine_delete();
}
}
onreply_route[NATMANAGE_REPLY] {
if (has_body("application/sdp")) {
rtpengine_answer("replace-origin replace-session-connection ICE=remove");
}
}
Las flags "replace-origin" y "replace-session-connection" le indican a RTPEngine que reemplace las direcciones IP en los campos o= y c= del SDP con la dirección que RTPEngine está usando. La flag "ICE=remove" elimina los atributos ICE del SDP, útil cuando estás haciendo bridge entre un cliente WebRTC que usa ICE y uno legacy que no lo entiende.
Configuración Avanzada para WebRTC
El escenario más complejo y común que encontrarás es permitir que clientes WebRTC se comuniquen con infraestructura SIP tradicional. Los navegadores web que usan WebRTC requieren SRTP obligatorio y típicamente DTLS para el intercambio de claves, mientras que la mayoría de servidores Asterisk y teléfonos IP tradicionales solo hablan RTP sin cifrar sobre UDP. RTPEngine es perfectamente capaz de hacer este bridge, pero requiere configuración específica tanto en RTPEngine como en Kamailio.
Primero, tu configuración de RTPEngine necesita certificados TLS válidos para DTLS. Genera o copia tus certificados:
mkdir -p /etc/rtpengine/certs
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout /etc/rtpengine/certs/rtpengine-key.pem \
-out /etc/rtpengine/certs/rtpengine-cert.pem \
-subj "/C=US/ST=State/L=City/O=Organization/CN=rtpengine.domain.com"
chown -R rtpengine:rtpengine /etc/rtpengine/certs
chmod 600 /etc/rtpengine/certs/rtpengine-key.pem
Actualiza tu rtpengine.conf para incluir la ruta a los certificados:
[rtpengine]
interface = 10.0.1.50!203.0.113.45
listen-ng = 127.0.0.1:2223
port-min = 20000
port-max = 30000
timeout = 60
silent-timeout = 3600
log-level = 6
log-facility = local1
certificate = /etc/rtpengine/certs/rtpengine-cert.pem
key = /etc/rtpengine/certs/rtpengine-key.pem
El parámetro dtls-passive es crucial: le indica a RTPEngine que actúe como servidor DTLS, esperando que el cliente (navegador WebRTC) inicie el handshake DTLS. En Kamailio, tu lógica de enrutamiento para manejar llamadas WebRTC necesita detectar el tipo de cliente y aplicar las flags apropiadas. Aquí está un ejemplo más completo:
route[NATMANAGE] {
if (is_method("INVITE")) {
if (has_body("application/sdp")) {
$var(rtpengine_flags) = "replace-origin replace-session-connection";
# Detectar si el origen es WebRTC
if (search("mozilla|chrome|safari") ||
search_body("a=rtcp-mux") ||
search_body("a=fingerprint")) {
$var(is_ws_origin) = 1;
$var(rtpengine_flags) = $var(rtpengine_flags) + " DTLS=passive SDES-off ICE=remove RTP/AVP";
}
# Detectar si el destino es WebRTC
if ($rU =~ "^webrtc_") {
$var(is_ws_dest) = 1;
if ($var(is_ws_origin) == 1) {
# WebRTC to WebRTC
$var(rtpengine_flags) = "replace-origin replace-session-connection DTLS=passive ICE=force";
} else {
# SIP to WebRTC
$var(rtpengine_flags) = "replace-origin replace-session-connection DTLS=passive ICE=force RTP/SAVPF";
}
}
if (rtpengine_offer("$var(rtpengine_flags)")) {
t_on_reply("NATMANAGE_REPLY");
}
}
}
if (is_method("ACK") && has_body("application/sdp")) {
rtpengine_answer("replace-origin replace-session-connection");
}
if (is_method("BYE|CANCEL")) {
rtpengine_delete();
}
}
onreply_route[NATMANAGE_REPLY] {
if (has_body("application/sdp")) {
$var(rtpengine_flags) = "replace-origin replace-session-connection";
if ($var(is_ws_origin) == 1 && $var(is_ws_dest) == 0) {
# WebRTC origin to SIP destination - need to convert back to RTP/AVP
$var(rtpengine_flags) = $var(rtpengine_flags) + " RTP/AVP DTLS-off ICE=remove";
} else if ($var(is_ws_origin) == 0 && $var(is_ws_dest) == 1) {
# SIP origin to WebRTC destination - need SRTP
$var(rtpengine_flags) = $var(rtpengine_flags) + " RTP/SAVPF DTLS=passive ICE=force";
}
rtpengine_answer("$var(rtpengine_flags)");
}
}
Las flags clave para WebRTC son: DTLS=passive indica que RTPEngine debe actuar como servidor DTLS; ICE=force agrega atributos ICE al SDP incluso si no estaban presentes originalmente; RTP/SAVPF especifica el perfil de transporte seguro que WebRTC requiere; RTP/AVP es el perfil estándar no seguro para SIP legacy; SDES-off desactiva SDES key exchange cuando usas DTLS. La combinación correcta de estas flags permite que RTPEngine haga la conversión transparente entre los dos mundos.
Escenario Real: Asterisk con Voicemail y Clientes WebRTC
Un caso de uso extremadamente común pero problemático es cuando tienes clientes WebRTC que necesitan dejar o escuchar mensajes de voz en un servidor Asterisk que solo habla RTP/UDP sin cifrado. Este escenario requiere que RTPEngine haga conversión SRTP-to-RTP en el camino hacia Asterisk y RTP-to-SRTP en el camino de vuelta. La configuración que mostré anteriormente maneja esto, pero hay sutilezas importantes. Cuando un cliente WebRTC llama a una extensión de voicemail, el flujo es: Cliente WebRTC -> Kamailio -> RTPEngine -> Asterisk. En el INVITE del cliente, Kamailio detecta que es WebRTC (por la presencia de atributos ICE o DTLS en el SDP) y llama a rtpengine_offer con flags que le dicen a RTPEngine que acepte SRTP/DTLS del cliente pero ofrezca RTP simple a Asterisk. Cuando Asterisk responde con su SDP que especifica RTP/AVP, Kamailio llama a rtpengine_answer con flags que convierten esa respuesta a RTP/SAVPF con DTLS para el cliente WebRTC. El audio fluye entonces: Cliente (SRTP) -> RTPEngine (descifra) -> Asterisk (RTP) -> RTPEngine (cifra) -> Cliente (SRTP). Un problema común aquí es olvidar que Asterisk necesita tener las direcciones IP correctas en su configuración. En sip.conf o pjsip.conf, necesitas configurar externip y localnet correctamente si Asterisk está detrás de NAT:
[general]
externip = 203.0.113.45
localnet = 10.0.0.0/255.255.0.0
nat = yes
Sin esta configuración, Asterisk puede generar SDP con direcciones IP privadas que RTPEngine no puede manejar correctamente. Otra trampa común es el codec negotiation: los navegadores modernos prefieren Opus, pero muchas configuraciones de Asterisk no lo tienen habilitado. Asegúrate de que tus extensiones de Asterisk permitan codecs compatibles con WebRTC:
[webrtc_client]
type=friend
context=default
disallow=all
allow=opus
allow=ulaw
allow=alaw
dtlsenable=no ; RTPEngine maneja DTLS, no Asterisk
encryption=no ; RTPEngine maneja SRTP, no Asterisk
Pruebas y Verificación
Una vez que tienes todo configurado, necesitas validar que funciona. La herramienta rtpengine-ctl te permite consultar el estado de RTPEngine y ver sesiones activas. Para ver todas las sesiones actuales:
rtpengine-ctl list
Para ver estadísticas detalladas de una sesión específica:
rtpengine-ctl query <call-id>
Durante una llamada activa, puedes usar tcpdump para verificar el flujo de medios. Para ver paquetes RTP en la interfaz donde RTPEngine está escuchando:
tcpdump -i eth0 -n udp portrange 20000-30000 -v
Si todo está funcionando correctamente, deberías ver tráfico UDP bidireccional en puertos dentro del rango configurado. Para verificar que el módulo de kernel está siendo usado:
cat /proc/rtpengine/0/list
Este archivo muestra todas las reglas de forwarding activas en el módulo de kernel. Si está vacío pero tienes llamadas activas, el módulo de kernel no se está usando y todo el procesamiento está en userspace. Para pruebas WebRTC, puedes usar el demo de JsSIP o Sipml5. Configura un cliente WebRTC para registrarse contra tu Kamailio, y luego intenta llamar a una extensión Asterisk tradicional. Usa las herramientas de desarrollo del navegador (F12) para ver la consola JavaScript y cualquier error WebRTC. Los errores comunes incluyen: fingerprint mismatch si hay problemas con los certificados DTLS; failed to set remote description si el SDP está malformado; ICE failed si hay problemas de conectividad de red. Monitorea los logs de Kamailio durante la llamada:
tail -f /var/log/kamailio.log | grep rtpengine
Deberías ver los comandos offer, answer, y delete siendo enviados a RTPEngine con sus respectivas flags. Si ves errores como timeout talking to RTPEngine, verifica que RTPEngine está corriendo y que listen-ng en rtpengine.conf coincide con rtpengine_sock en kamailio.cfg.
Troubleshooting Común
El audio de un solo sentido es probablemente el problema más reportado con RTPEngine. Las causas comunes incluyen: configuración incorrecta de interface con la IP pública/privada; firewall bloqueando el rango de puertos RTP; direcciones IP en SDP que no coinciden con la realidad de red; problemas de routing asimétrico donde los paquetes de ida y vuelta toman caminos diferentes. Para diagnosticar, captura el SDP en diferentes puntos del flujo y compara las direcciones IP y puertos. Sin audio en absoluto usualmente indica que RTPEngine no está recibiendo los comandos de control, o que el módulo de kernel no está cargado y hay un problema de performance. Verifica que rtpengine-ctl list muestra las sesiones activas. Problemas de calidad de audio como choppy audio o paquetes perdidos pueden deberse a: latencia de red excesiva; CPU overload en el servidor RTPEngine; problemas de codec negotiation donde hay transcoding innecesario; tamaño de paquete incorrecto (ptime). Usa rtpengine-ctl stats para ver estadísticas de paquetes perdidos y latencia. Para problemas específicos de WebRTC, los síntomas incluyen: ICE connection failed que generalmente significa problemas de firewall o routing; DTLS handshake failed que indica problemas de certificados o incompatibilidad de versiones; fingerprint verification failed que significa que el SDP fue modificado en tránsito o hay un MITM. Habilita log-level = 7 en rtpengine.conf para debugging detallado, pero recuerda bajarlo a 5 o 6 en producción para evitar I/O excesivo.
Escalamiento y Alta Disponibilidad
Para entornos de producción con alto volumen de llamadas, una sola instancia de RTPEngine eventualmente se convertirá en un cuello de botella. RTPEngine soporta múltiples estrategias de escalamiento. La más simple es el escalamiento vertical: aumentar CPU, RAM y ancho de banda de red en el servidor. RTPEngine con el módulo de kernel puede manejar miles de llamadas concurrentes en hardware moderno, pero hay límites. El escalamiento horizontal implica correr múltiples instancias de RTPEngine y distribuir la carga entre ellas. Kamailio soporta esto mediante el módulo rtpengine con múltiples sockets:
modparam("rtpengine", "rtpengine_sock", "udp:10.0.1.50:2223 udp:10.0.1.51:2223")
modparam("rtpengine", "rtpengine_allow_op", 1)
Con esta configuración, Kamailio distribuye llamadas entre los dos servidores RTPEngine automáticamente. Para alta disponibilidad real, necesitas considerar qué pasa cuando un servidor RTPEngine falla. Las sesiones activas en ese servidor se perderán, pero nuevas llamadas irán al servidor sobreviviente. Una arquitectura más sofisticada implica usar un balanceador de carga en frente de múltiples instancias RTPEngine, pero esto requiere que el balanceador mantenga session affinity para que todos los mensajes de control de una sesión específica vayan al mismo servidor RTPEngine. Otra consideración importante en producción es el monitoreo. Implementa checks de health que consulten rtpengine-ctl stats periódicamente y alerten si: el número de sesiones activas es anormalmente alto o bajo; hay un incremento en paquetes perdidos; el CPU load del servidor es excesivo; el módulo de kernel no está cargado. Herramientas como Nagios, Zabbix, o Prometheus pueden integrarse fácilmente mediante scripts que parsean la salida de rtpengine-ctl.
Conclusión
RTPEngine es una herramienta indispensable en infraestructuras VoIP modernas, especialmente cuando necesitas bridging entre protocolos legacy y WebRTC. Aunque la curva de aprendizaje inicial puede ser empinada debido a la documentación fragmentada, una vez que entiendes los conceptos fundamentales de interfaces públicas/privadas, flags de control, y conversión de protocolos, se convierte en una solución extremadamente poderosa y confiable. Los ejemplos de este artículo están basados en configuraciones de producción reales que manejan miles de llamadas diarias, y deberían darte una base sólida para implementar tu propia infraestructura. Recuerda siempre probar exhaustivamente en un ambiente de staging antes de desplegar a producción, especialmente cuando estás haciendo conversiones SRTP/RTP que son críticas para la privacidad de las comunicaciones.
Comentarios recientes