Enviado por admin el
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:
- Siempre usar
advertiseen las directivaslistenpara anunciar la IP pública mientras se escucha en la IP privada - Configurar rtpengine con las interfaces correctas usando la notación
internal/IP_PRIVADA;external/IP_PRIVADA!IP_PUBLICA - Detectar NAT agresivamente usando múltiples tests y aplicar las correcciones apropiadas
- Mantener los NAT bindings abiertos con keepalives frecuentes
- 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.
1 comentario
Problemas con ACK en INVITE -> 200 SDP
Enviado por mario.lenis el
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()?