Configuración de Kamailio 5.8 con rtpengine en AWS EC2: Guía completa para Proxy SIP en la nube

Implementar un proxy SIP en AWS EC2 presenta desafíos únicos debido a la arquitectura de red de Amazon. La combinación de Kamailio como proxy SIP y rtpengine para el manejo de medios RTP es una solución robusta y escalable, especialmente cuando se trabaja con PBX en la nube. Este artículo detalla paso a paso cómo configurar esta solución, manejando correctamente las IPs elásticas y los problemas de NAT.

El problema del NAT en AWS EC2

Las instancias EC2 tienen una característica particular: no conocen directamente su IP pública (Elastic IP). La instancia solo ve su IP privada en la interfaz de red, mientras que AWS realiza una traducción NAT 1:1 entre la IP elástica y la IP privada. Esto crea complicaciones para protocolos como SIP que embeben direcciones IP en sus mensajes.

Topología típica

Internet/Clientes NAT → [IP Elástica] → AWS NAT → [IP Privada EC2] → Kamailio/rtpengine → PBX Cloud
                         (1.2.3.4)                  (172.31.0.10)

1. Configuración de la instancia EC2

Primero, lanzamos una instancia EC2 con las siguientes características:

# Recomendaciones mínimas:
- Tipo: t3.medium (2 vCPU, 4GB RAM) o superior
- OS: Ubuntu 22.04 LTS o Debian 12 (mejor)
- Storage: 20GB SSD mínimo
- VPC: Con subnet pública

2. Asignación de IP Elástica

# Desde AWS CLI
aws ec2 allocate-address --domain vpc
aws ec2 associate-address --instance-id i-xxxxx --allocation-id eipalloc-xxxxx

3. Configuración de Security Groups

# Reglas de entrada necesarias
- UDP 5060 (SIP)
- TCP 5061 (SIP TLS) - opcional
- UDP 10000-20000 (RTP)
- TCP 22 (SSH para administración)

Instalación de Kamailio 5.8

# Agregar repositorio de Kamailio
wget -O- https://deb.kamailio.org/kamailiodebkey.gpg | sudo apt-key add -
echo "deb http://deb.kamailio.org/kamailio58 $(lsb_release -sc) main" | \
  sudo tee /etc/apt/sources.list.d/kamailio.list

# Actualizar e instalar
sudo apt-get update
sudo apt-get install -y kamailio kamailio-mysql-modules \
  kamailio-tls-modules kamailio-presence-modules \
  kamailio-xml-modules kamailio-websocket-modules

# Instalar módulos adicionales para NAT
sudo apt-get install -y kamailio-extra-modules

Instalación de rtpengine

# Dependencias
sudo apt-get install -y debhelper default-libmysqlclient-dev \
  gperf iptables-dev libavcodec-dev libavfilter-dev \
  libavformat-dev libavutil-dev libbencode-perl \
  libcrypt-openssl-rsa-perl libcrypt-rijndael-perl \
  libhiredis-dev libio-multiplex-perl libio-socket-inet6-perl \
  libiptc-dev libjson-glib-dev libnet-interface-perl \
  libpcap0.8-dev libsocket6-perl libspandsp-dev \
  libswresample-dev libsystemd-dev libxmlrpc-core-c3-dev \
  markdown dkms module-assistant keyutils libnfsidmap2 \
  libtirpc3 nfs-common rpcbind

# Clonar y compilar rtpengine
git clone https://github.com/sipwise/rtpengine.git
cd rtpengine
git checkout mr11.5.1  # Versión estable compatible
dpkg-buildpackage -b -us -uc
cd ..
sudo dpkg -i ngcp-rtpengine-daemon_*.deb ngcp-rtpengine-iptables_*.deb \
  ngcp-rtpengine-kernel-dkms_*.deb

Archivo principal: /etc/kamailio/kamailio.cfg

#!KAMAILIO

# Definición de IPs - CRÍTICO para AWS
#!define PRIVATE_IP "172.31.0.10"    # IP privada de la instancia
#!define PUBLIC_IP "1.2.3.4"          # IP Elástica asignada
#!define PBX_IP "10.0.1.50"           # IP del PBX en la nube

# Parámetros globales
debug=2
log_stderror=no
memdbg=5
memlog=5
log_facility=LOG_LOCAL0
fork=yes
children=4
tcp_children=4

# Configuración de escucha - usa IP privada pero anuncia la pública
listen=udp:PRIVATE_IP:5060 advertise PUBLIC_IP:5060

# Alias para la IP pública
alias=PUBLIC_IP
alias=PUBLIC_IP:5060

# Configuración del puerto
port=5060
mpath="/usr/lib/x86_64-linux-gnu/kamailio/modules/"

# Módulos necesarios
loadmodule "mi_fifo.so"
loadmodule "kex.so"
loadmodule "corex.so"
loadmodule "tm.so"
loadmodule "tmx.so"
loadmodule "sl.so"
loadmodule "rr.so"
loadmodule "pv.so"
loadmodule "maxfwd.so"
loadmodule "usrloc.so"
loadmodule "registrar.so"
loadmodule "textops.so"
loadmodule "textopsx.so"
loadmodule "siputils.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "ctl.so"
loadmodule "cfg_rpc.so"
loadmodule "mi_rpc.so"
loadmodule "nathelper.so"
loadmodule "rtpengine.so"
loadmodule "htable.so"
loadmodule "pike.so"
loadmodule "dispatcher.so"

# Parámetros de módulos
modparam("mi_fifo", "fifo_name", "/var/run/kamailio/kamailio_fifo")
modparam("rr", "enable_full_lr", 1)
modparam("rr", "append_fromtag", 1)
modparam("tm", "failure_reply_mode", 3)
modparam("tm", "fr_timer", 30000)
modparam("tm", "fr_inv_timer", 120000)

# Configuración NAT Helper
modparam("nathelper", "natping_interval", 30)
modparam("nathelper", "ping_nated_only", 1)
modparam("nathelper", "sipping_bflag", 7)
modparam("nathelper", "sipping_from", "sip:ping@PUBLIC_IP")
modparam("nathelper", "received_avp", "$avp(RECEIVED)")

# Configuración rtpengine
modparam("rtpengine", "rtpengine_sock", "udp:127.0.0.1:2223")
modparam("rtpengine", "rtpengine_retr", 2)
modparam("rtpengine", "rtpengine_tout_ms", 2000)

# Dispatcher para PBX
modparam("dispatcher", "list_file", "/etc/kamailio/dispatcher.list")
modparam("dispatcher", "flags", 2)
modparam("dispatcher", "xavp_dst", "_dsdst_")
modparam("dispatcher", "xavp_ctx", "_dsctx_")

# Tabla hash para detección de NAT
modparam("htable", "htable", "natping=>size=8;autoexpire=640;")

# Lógica de ruteo principal
request_route {
    # Validaciones iniciales
    if (!sanity_check("17895", "7")) {
        xlog("L_WARN", "Malformed SIP message from $si:$sp\n");
        exit;
    }

    # Control de bucles
    if (!mf_process_maxfwd_header("10")) {
        sl_send_reply("483", "Too Many Hops");
        exit;
    }

    # Manejo de CANCEL
    if (is_method("CANCEL")) {
        if (t_check_trans()) {
            route(RELAY);
        }
        exit;
    }

    # Manejo de OPTIONS (keepalive)
    if (is_method("OPTIONS")) {
        sl_send_reply("200", "OK");
        exit;
    }

    # Detección de NAT
    route(NATDETECT);

    # Manejo de diálogos en curso
    if (has_totag()) {
        route(WITHINDLG);
        exit;
    }

    # Record-routing para todos los mensajes iniciales
    if (is_method("INVITE|SUBSCRIBE|NOTIFY")) {
        record_route();
    }

    # REGISTER - reenviar al PBX
    if (is_method("REGISTER")) {
        route(REGISTRAR);
        exit;
    }

    # INVITE - procesar llamada
    if (is_method("INVITE")) {
        route(INVITE);
        exit;
    }

    # Otros métodos
    route(RELAY);
}

# Ruteo para REGISTER
route[REGISTRAR] {
    # Agregar información de NAT al Contact
    if (nat_uac_test("19")) {
        fix_nated_contact();
        force_rport();
        add_contact_alias();
    }
    
    # Reenviar al PBX
    $du = "sip:PBX_IP:5060";
    route(RELAY);
}

# Ruteo para INVITE
route[INVITE] {
    # Manejo de NAT en INVITE
    if (nat_uac_test("19")) {
        fix_nated_contact();
        force_rport();
        add_contact_alias();
    }
    
    # Aplicar rtpengine
    route(RTPENGINE);
    
    # Enviar al PBX
    $du = "sip:PBX_IP:5060";
    route(RELAY);
}

# Detección de NAT
route[NATDETECT] {
    if (nat_uac_test("19")) {
        if (is_method("REGISTER")) {
            fix_nated_register();
        } else {
            fix_nated_contact();
        }
        force_rport();
        xlog("L_INFO", "NAT detected for $fu from $si:$sp\n");
        setflag(5); # Flag para NAT
    }
}

# Manejo de rtpengine
route[RTPENGINE] {
    if (!has_body("application/sdp")) {
        return;
    }
    
    # Determinar dirección del flujo RTP
    if (isflagset(5)) {
        # Cliente detrás de NAT hacia PBX
        rtpengine_manage("replace-origin replace-session-connection " +
                         "direction=external direction=internal " +
                         "media-address=PUBLIC_IP");
    } else if ($si == "PBX_IP") {
        # PBX hacia cliente (posiblemente NAT)
        rtpengine_manage("replace-origin replace-session-connection " +
                         "direction=internal direction=external " +
                         "media-address=PUBLIC_IP");
    } else {
        # Por defecto
        rtpengine_manage("replace-origin replace-session-connection " +
                         "media-address=PUBLIC_IP");
    }
}

# Manejo de diálogos establecidos
route[WITHINDLG] {
    if (!has_totag()) return;
    
    if (loose_route()) {
        # Manejo de NAT en respuestas
        if (is_reply() && nat_uac_test("1")) {
            fix_nated_contact();
        }
        
        # Aplicar rtpengine para reINVITEs y respuestas con SDP
        if (is_method("INVITE|UPDATE|ACK") || 
            (is_reply() && has_body("application/sdp"))) {
            route(RTPENGINE);
        }
        
        route(RELAY);
    } else {
        if (is_method("ACK")) {
            if (t_check_trans()) {
                route(RELAY);
                exit;
            }
        }
        sl_send_reply("404", "Not Found");
    }
    exit;
}

# Relay final
route[RELAY] {
    if (!t_relay()) {
        sl_reply_error();
    }
}

# Manejo de respuestas
reply_route {
    # Detectar NAT en respuestas
    if (nat_uac_test("1")) {
        fix_nated_contact();
    }
    
    # Aplicar rtpengine a respuestas con SDP
    if (has_body("application/sdp")) {
        route(RTPENGINE);
    }
    
    # Log de respuestas
    if (status=~"[12][0-9][0-9]") {
        xlog("L_INFO", "Successful response $rs $rr from $si\n");
    }
}

# Manejo de fallos
failure_route[MANAGE_FAILURE] {
    if (t_is_canceled()) {
        exit;
    }
    
    # Reintentar si hay timeout
    if (t_check_status("408|503")) {
        t_relay();
        exit;
    }
}

Archivo dispatcher.list

# /etc/kamailio/dispatcher.list
# Configuración del PBX destino
# setid(int) destination(sip uri) flags(int,opt) priority(int,opt) attributes(str,opt)
1 sip:10.0.1.50:5060 0 0 duid=pbx1;maxload=100

Archivo: /etc/rtpengine/rtpengine.conf

[rtpengine]
# Interfaces de red - CRÍTICO para AWS
interface = internal/172.31.0.10;external/172.31.0.10!1.2.3.4

# Puerto de control
listen-ng = 127.0.0.1:2223

# Rango de puertos RTP
port-min = 10000
port-max = 20000

# Logs
log-level = 6
log-facility = local5
log-facility-cdr = local6
log-facility-rtcp = local7

# Timeouts
timeout = 60
silent-timeout = 3600
final-timeout = 10800

# Opciones de NAT
tos = 184
delete-delay = 30
timeout-socket = 0

# Tabla de ruteo del kernel
table = 0

# No usar fallback
no-fallback = false

# Grabación (opcional)
# recording-dir = /var/spool/rtpengine
# recording-method = proc

Script de inicio para rtpengine

#!/bin/bash
# /usr/local/bin/start-rtpengine.sh

# Cargar módulo del kernel si está disponible
modprobe xt_RTPENGINE 2>/dev/null || true

# Limpiar reglas iptables anteriores
iptables -D INPUT -p udp -j RTPENGINE --id 0 2>/dev/null || true
ip6tables -D INPUT -p udp -j RTPENGINE --id 0 2>/dev/null || true

# Agregar reglas iptables para forwarding de RTP
iptables -I INPUT -p udp -j RTPENGINE --id 0
ip6tables -I INPUT -p udp -j RTPENGINE --id 0

# Iniciar rtpengine
/usr/bin/rtpengine --config-file=/etc/rtpengine/rtpengine.conf --pidfile=/var/run/rtpengine.pid

Detección mejorada de NAT

# Agregar a kamailio.cfg
route[NATDETECT_ADVANCED] {
    # Test 1: Via header != source IP
    if (nat_uac_test("1")) {
        setflag(5);
        xlog("L_INFO", "NAT Test 1: Via differs from source\n");
    }
    
    # Test 2: Contact tiene IP privada
    if (nat_uac_test("2")) {
        setflag(5);
        xlog("L_INFO", "NAT Test 2: Private IP in Contact\n");
    }
    
    # Test 4: Top Via tiene private IP
    if (nat_uac_test("4")) {
        setflag(5);
        xlog("L_INFO", "NAT Test 4: Private IP in top Via\n");
    }
    
    # Test 8: SDP tiene IP diferente a señalización
    if (nat_uac_test("8")) {
        setflag(6);
        xlog("L_INFO", "NAT Test 8: SDP IP differs\n");
    }
    
    # Test 16: Source port != Via port
    if (nat_uac_test("16")) {
        setflag(5);
        xlog("L_INFO", "NAT Test 16: Port mismatch\n");
    }
}

Manejo de NAT simétrico

# Para clientes con NAT simétrico
route[SYMMETRIC_NAT] {
    if (is_method("REGISTER")) {
        # Forzar reutilización del socket
        force_rport();
        add_contact_alias();
        
        # Reducir tiempo de expiración para mantener NAT abierto
        if ($hdr(Expires) > 120) {
            $hdr(Expires) = "120";
        }
    }
    
    if (is_method("INVITE")) {
        # Agregar headers para identificar NAT
        append_hf("X-NAT-Type: Symmetric\r\n");
        
        # Configuración especial de rtpengine
        rtpengine_manage("replace-origin replace-session-connection " +
                         "symmetric ICE=force media-address=PUBLIC_IP");
    }
}

Interconexión con otros servidores SIP

# Lista de IPs de carriers/peers confiables
modparam("permissions", "db_url", "")
modparam("permissions", "db_mode", 0)

route[FROM_CARRIER] {
    # Verificar si viene de un carrier conocido
    if ($si == "CARRIER_IP_1" || $si == "CARRIER_IP_2") {
        # No aplicar detección de NAT a carriers
        resetflag(5);
        
        # Configuración especial de rtpengine para carriers
        rtpengine_manage("trust-address replace-origin " +
                         "replace-session-connection " +
                         "direction=external direction=internal " +
                         "media-address=PUBLIC_IP");
        
        return;
    }
}

Comandos útiles de Kamailio

# Estado del servicio
kamctl stats

# Monitoreo en tiempo real
kamctl monitor

# Ver diálogos activos
kamctl dialog show

# Ver registros activos
kamctl ul show

# Reload de configuración
kamctl reload

Logs de debugging

# Kamailio logs
tail -f /var/log/syslog | grep kamailio

# rtpengine logs
tail -f /var/log/syslog | grep rtpengine

# Ver estadísticas de rtpengine
echo "list sessions all" | nc -u 127.0.0.1 2223

Script de test de conectividad

#!/bin/bash
# test_sip_nat.sh

PUBLIC_IP="1.2.3.4"
LOCAL_IP="172.31.0.10"

echo "Testing SIP connectivity..."

# Test SIP UDP
nc -u -z -v $PUBLIC_IP 5060
nc -u -z -v $LOCAL_IP 5060

# Test rango RTP
for port in 10000 10001 10002; do
    timeout 1 nc -u -z -v $PUBLIC_IP $port
done

# Test de OPTIONS
sipsak -s sip:test@$PUBLIC_IP -v

Optimizaciones para producción

Tunning del kernel

# /etc/sysctl.d/99-kamailio.conf
# Aumentar buffers de red
net.core.rmem_max = 12582912
net.core.wmem_max = 12582912
net.ipv4.udp_rmem_min = 8192
net.ipv4.udp_wmem_min = 8192

# Conexiones concurrentes
net.ipv4.ip_local_port_range = 10000 65000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30

# Protección DDoS básica
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 4096
net.ipv4.tcp_synack_retries = 2

Configuración de logrotate

# /etc/logrotate.d/kamailio
/var/log/kamailio/*.log {
    daily
    rotate 30
    compress
    missingok
    notifempty
    sharedscripts
    postrotate
        /bin/kill -HUP `cat /var/run/kamailio/kamailio.pid 2>/dev/null` 2>/dev/null || true
    endscript
}

Casos de uso comunes y soluciones

1. Audio unidireccional

# Forzar modo asimétrico en rtpengine
route[FIX_ONEWAY_AUDIO] {
    if (is_method("INVITE") && has_body("application/sdp")) {
        # Agregar flag para casos problemáticos
        if ($fu =~ "problematic_user") {
            rtpengine_manage("asymmetric trust-address " +
                             "replace-origin replace-session-connection " +
                             "media-address=PUBLIC_IP");
        }
    }
}

2. Registro intermitente

# Ajustar NAT keepalive para usuarios problemáticos
route[AGGRESSIVE_KEEPALIVE] {
    if (is_method("REGISTER") && nat_uac_test("19")) {
        # Keepalive cada 20 segundos para NATs agresivos
        modparam("nathelper", "natping_interval", 20)
        
        # Forzar re-registro frecuente
        if (!search("^Expires: *[0-9]+")) {
            append_hf("Expires: 60\r\n");
        }
    }
}

3. Llamadas que se cortan a los 30 segundos

# Problema típico de ACK no llegando por NAT
route[FIX_ACK_ROUTING] {
    if (is_method("ACK") && $si != "PBX_IP") {
        # Forzar ruteo a través del proxy
        if (!t_check_trans()) {
            # ACK sin transacción - posible problema de NAT
            force_rport();
            fix_nated_contact();
            
            # Reescribir destino basado en registro
            if (lookup("location")) {
                route(RELAY);
                exit;
            }
        }
    }
}

La configuración de Kamailio con rtpengine en AWS EC2 requiere especial atención al manejo de IPs elásticas y NAT. Los puntos clave son:

  1. Siempre usar advertise en las directivas listen para anunciar la IP pública mientras se escucha en la IP privada
  2. Configurar rtpengine con las interfaces correctas usando la notación internal/IP_PRIVADA;external/IP_PRIVADA!IP_PUBLICA
  3. Detectar NAT agresivamente usando múltiples tests y aplicar las correcciones apropiadas
  4. Mantener los NAT bindings abiertos con keepalives frecuentes
  5. Monitorear constantemente logs y estadísticas para detectar problemas tempranamente

Esta configuración proporciona una base sólida para un proxy SIP en producción en AWS, manejando correctamente tanto clientes detrás de NAT como la interconexión con PBX y otros servidores SIP. La clave del éxito está en entender cómo AWS maneja las IPs y adaptar la configuración de Kamailio y rtpengine para trabajar con esta arquitectura.

Vota el Articulo: 

Sin votos (todavía)
Evalúa la calidad del articulo

1 comentario

Problemas con ACK en INVITE -> 200 SDP

Saludos. 

Seguí este instructivo y tengo un error que no logro resolver, cuando hago una llamada, el media server (asterisk) responde con un 200 OK SDP, Kamailio reenvia este mensaje sin modificar el record-route

Por loque cuando lo recibe el softphone, el ACK lo intenta enviar a IP 10.0.255.139,

Mismo paquete recibido en el softphone

Respuesta:

Me hace falta un record_route()?

Suscribirse a Comentarios de "Configuración de Kamailio 5.8 con rtpengine en AWS EC2: Guía completa para Proxy SIP en la nube" Suscribirse a VozToVoice - Todos los comentarios