La Función t_relay del Módulo TM en Kamailio

En el ecosistema de Kamailio (anteriormente OpenSER), el módulo TM (Transaction Module) representa uno de los componentes más críticos para el procesamiento de mensajes SIP. Dentro de este módulo, la función t_relay() constituye el mecanismo fundamental para el reenvío stateful de mensajes SIP, implementando la lógica de transacciones definida en el RFC 3261. Este artículo explora en detalle el funcionamiento interno, arquitectura y aplicaciones prácticas de t_relay(), proporcionando una comprensión profunda para administradores e ingenieros de sistemas VoIP.

Antes de profundizar en t_relay(), es esencial comprender el contexto del módulo TM. Este módulo implementa el manejo stateful de transacciones SIP, lo que significa que mantiene el estado de cada transacción desde su inicio hasta su conclusión, contrariamente al procesamiento stateless que caracteriza al proxy básico de Kamailio.

Diferencia entre Stateful y Stateless

Procesamiento Stateless: Kamailio procesa cada mensaje de forma independiente sin mantener memoria de mensajes previos. Utiliza funciones como forward() del módulo core.

Procesamiento Stateful: El módulo TM crea y mantiene estructuras de datos (transacciones) que persisten durante el ciclo de vida completo de una petición-respuesta SIP. Esto permite:

  • Retransmisión automática de mensajes (cumpliendo RFC 3261)
  • Absorción de retransmisiones duplicadas
  • Correlación automática de respuestas con peticiones
  • Implementación de timers SIP (Timer A, B, D, E, F, G, H, I, J, K)
  • Cancelación de transacciones
  • Failover y load balancing avanzado

Definición y Propósito

t_relay()
t_relay(host, port)

La función t_relay() es el punto de entrada principal para el reenvío stateful de mensajes SIP. Su responsabilidad primaria es crear (si no existe) o localizar una transacción SIP y reenviar el mensaje actual al siguiente hop determinado por el algoritmo de routing de Kamailio.

Proceso Interno Paso a Paso

Cuando se invoca t_relay(), el módulo TM ejecuta la siguiente secuencia de operaciones:

1. Creación o Localización de Transacción

El módulo TM examina el mensaje entrante y determina si corresponde a una transacción existente o si debe crear una nueva:

Para peticiones (INVITE, REGISTER, etc.):

  • Calcula un hash basado en Call-ID, CSeq y From-tag
  • Busca en la tabla de transacciones (hash table) si ya existe
  • Si no existe, crea una nueva estructura de transacción en memoria compartida
  • Reserva memoria para almacenar callbacks, timers y estado de la transacción

Para respuestas:

  • Utiliza el branch parameter del Via header para localizar la transacción correspondiente
  • Si encuentra la transacción, actualiza su estado
  • Si no la encuentra (respuesta tardía), generalmente la descarta

2. Clonación del Mensaje

Una vez identificada la transacción, TM realiza una clonación del mensaje SIP:

Original Message → Shared Memory Clone → Transaction Buffer

Esta clonación es crítica porque:

  • Los mensajes originales están en memoria privada del proceso
  • Las transacciones deben persistir entre diferentes procesos worker
  • Permite retransmisiones sin necesidad de volver a procesar el routing script

3. Determinación del Next Hop

t_relay() respeta la decisión de routing tomada previamente en el script de configuración:

  • Si se ha establecido el Destination Set ($du), utiliza esa URI
  • Si se ha modificado el Request-URI ($ru), lo utiliza como destino
  • Si se proporcionó un parámetro proxy_uri a t_relay(), lo usa como override
  • Aplica DNS lookup (NAPTR, SRV, A/AAAA) según la configuración

4. Branch Management (Gestión de Ramas)

Para peticiones iniciales, TM puede crear múltiples branches (ramas) si se ha configurado serial forking o parallel forking:

           ┌─→ Branch 1 → Destino 1
Request ──→│
           │─→ Branch 2 → Destino 2
           └─→ Branch 3 → Destino 3

Cada branch tiene:

  • Un branch ID único (b=xxxxxxxx en Via header)
  • Su propio estado de transacción
  • Timers independientes
  • Buffers de respuesta separados

5. Modificación de Headers SIP

Antes del reenvío, TM modifica automáticamente ciertos headers:

Via Header: Añade un nuevo Via header con:

  • Dirección IP y puerto del socket de salida
  • Branch parameter único (branch=z9hG4bK + random)
  • rport parameter si está habilitado

Max-Forwards: Decrementa el valor en 1 (prevención de loops)

Record-Route: Si record_route() fue llamado previamente, el header ya está presente

Content-Length: Recalcula si el body fue modificado

6. Activación de Timers

TM inicia los timers apropiados según el tipo de transacción:

Para transacciones INVITE:

  • Timer A: Retransmisión inicial (T1 = 500ms por defecto)
  • Timer B: Timeout de transacción (64*T1 = 32s)
  • Timer D: Duración máxima de respuesta final

Para transacciones non-INVITE:

  • Timer E: Retransmisión (similar a Timer A)
  • Timer F: Timeout de transacción (64*T1)

Características de retransmisión:

  • Backoff exponencial: T1, 2T1, 4T1, 8*T1...
  • Hasta Timer B (INVITE) o Timer F (non-INVITE)
  • Automático, no requiere intervención del script

7. Envío del Mensaje

Finalmente, el mensaje es enviado a través del socket de red:

  • Selecciona el socket apropiado (UDP, TCP, TLS, SCTP, WebSocket)
  • Para TCP/TLS, puede reutilizar conexión existente o crear nueva
  • Almacena el mensaje enviado para posibles retransmisiones
  • Registra estadísticas y logs según configuración

Valor de Retorno

t_relay() retorna diferentes valores que indican el resultado de la operación:

  • 1: Éxito, el mensaje fue reenviado correctamente
  • -1: Error al crear la transacción o al enviar el mensaje
  • -2: Error interno (falta de memoria, configuración incorrecta)
  • -6: Error de resolución DNS

Parámetros y otras funciones parecidas

t_relay() sin parámetros

if (t_relay()) {
    xlog("L_INFO", "Message relayed successfully\n");
    exit;
}

Comportamiento: Utiliza el Destination Set previamente configurado en el script mediante funciones como rewritehostport(), $du, o modificación del Request-URI.

t_relay(host, port)

t_relay("192.168.1.100", "5060");

Fuerza el destino independientemente de la URI previamente configurada. Útil para:

  • Routing a un gateway específico
  • Implementación de media servers
  • Bypass de DNS lookup

Integración con Failure Routes

Una de las características más potentes de t_relay() es su integración con failure routes, permitiendo implementar lógica avanzada de failover:

route[RELAY] {
    if (!t_relay()) {
        sl_reply_error();
    }
    exit;
}

failure_route[MANAGE_FAILURE] {
    # Esta ruta se ejecuta si t_relay() recibe 
    # una respuesta de falla (>=300) o timeout
    
    if (t_check_status("408|500|503")) {
        # Timeout o error del servidor
        if (ds_next_dst()) {
            # Intenta siguiente destino en dispatcher
            t_on_failure("MANAGE_FAILURE");
            t_relay();
            exit;
        }
    }
    
    if (t_check_status("486|600")) {
        # User Busy - intenta voicemail
        $ru = "sip:voicemail@" + $rd;
        t_relay();
        exit;
    }
}

Callbacks y Event Routes

TM proporciona varios event routes que se disparan durante el ciclo de vida de la transacción:

event_route[tm:local-request] {
    # Se ejecuta para peticiones generadas localmente
    xlog("L_INFO", "Local request: $rm to $ru\n");
}

event_route[tm:branch-failure] {
    # Se ejecuta cuando un branch específico falla
    xlog("L_WARN", "Branch $T_branch_idx failed: $T_reply_code\n");
}

Diferencias con Otras Funciones de Relay

t_relay() vs forward()

forward() (stateless):

  • No crea transacciones
  • No retransmite automáticamente
  • Menor consumo de memoria
  • No permite failure routes
  • Más rápido (menor overhead)

t_relay() (stateful):

  • Crea y mantiene transacciones
  • Retransmisión automática según RFC 3261
  • Permite failover y load balancing complejos
  • Mayor consumo de memoria
  • Necesario para la mayoría de escenarios de producción

t_relay() vs t_forward_nonack()

t_forward_nonack()
t_forward_nonack(proxy_uri)

Variante de t_relay() que no procesa mensajes ACK. Los ACK se procesan de forma especial en SIP:

  • ACK para 2xx respuestas es una nueva transacción
  • ACK para respuestas de error (3xx-6xx) es parte de la transacción INVITE original

t_forward_nonack() es útil cuando se quiere procesar ACKs de forma stateless.

t_relay() vs t_replicate()

t_replicate("sip:backup.example.com:5060");

t_replicate() envía una copia del mensaje a un destino adicional sin esperar respuesta. Útil para:

  • Replicación de CDRs
  • Sistemas de logging distribuido
  • Hot standby servers

Gestión de Memoria

El módulo TM utiliza memoria compartida para las transacciones. Parámetros clave:

modparam("tm", "hash_size", 8192)
# Tamaño de la hash table de transacciones
# Debe ser potencia de 2
# Mayor valor = menos colisiones, más memoria

modparam("tm", "timer_partitions", 4)
# Particiona los timers entre múltiples threads
# Mejora rendimiento en sistemas con muchos cores

Control de Timers

modparam("tm", "fr_timer", 30000)
# Final Response timer (30 segundos)
# Tiempo máximo para recibir respuesta final

modparam("tm", "fr_inv_timer", 120000)
# Final Response timer para INVITE (120 segundos)
# INVITE puede tardar más (ring time)

modparam("tm", "restart_fr_on_each_reply", 1)
# Reinicia fr_timer en cada respuesta provisional
# Útil para llamadas de larga duración

Performance para Alto Tráfico

modparam("tm", "auto_inv_100", 1)
# Envía 100 Trying automáticamente para INVITE
# Reduce retransmisiones del UAC

modparam("tm", "auto_inv_100_reason", "Trying")
# Personaliza el reason phrase del 100

modparam("tm", "aggregate_challenges", 1)
# Agrega múltiples desafíos de autenticación
# Útil en escenarios de parallel forking

Casos de Uso Avanzados

Load Balancing con Failover

route[LOADBALANCE] {
    if (!ds_select_dst("1", "4")) {
        # Algoritmo 4: hash sobre Call-ID
        send_reply("503", "Service Unavailable");
        exit;
    }
    
    t_on_failure("LB_FAILURE");
    
    if (!t_relay()) {
        sl_reply_error();
    }
    exit;
}

failure_route[LB_FAILURE] {
    if (t_check_status("(408)|(5[0-9][0-9])")) {
        if (ds_next_dst()) {
            t_on_failure("LB_FAILURE");
            t_relay();
            exit;
        }
    }
}

Serial Forking (Búsqueda Secuencial)

route[SERIAL_FORK] {
    # Obtener múltiples contactos del location table
    if (!lookup("location")) {
        send_reply("404", "Not Found");
        exit;
    }
    
    t_on_failure("NEXT_CONTACT");
    t_relay();
    exit;
}

failure_route[NEXT_CONTACT] {
    if (t_check_status("408|480|486|487")) {
        if (next_contacts()) {
            t_on_failure("NEXT_CONTACT");
            t_relay();
            exit;
        }
    }
}

Parallel Forking (Búsqueda Paralela)

route[PARALLEL_FORK] {
    if (!lookup("location")) {
        send_reply("404", "Not Found");
        exit;
    }
    
    # lookup() ya ha preparado todos los branches
    # t_relay() los enviará en paralelo
    
    t_relay();
    exit;
}

Media Server Integration

route[VOICEMAIL] {
    $ru = "sip:" + $rU + "@voicemail.internal:5070";
    
    # Añadir headers personalizados para el media server
    append_hf("X-Original-To: $tu\r\n");
    append_hf("X-Original-From: $fu\r\n");
    
    t_relay();
    exit;
}

Logs y Trazas

# Activar debug del módulo TM
modparam("tm", "enable_stats", 1)

route {
    xlog("L_INFO", "RELAY: $rm from $si:$sp to $ru\n");
    
    if (t_relay()) {
        xlog("L_INFO", "RELAY: Success, transaction=$T(id_index):$T(id_label)\n");
    } else {
        xlog("L_ERR", "RELAY: Failed, reason=$T_reply_code $T_reply_reason\n");
    }
}

Variables de Transacción

El módulo TM expone múltiples pseudovariables para inspección:

$T(id_index)        # Índice de la transacción en la hash table
$T(id_label)        # Label único de la transacción
$T(reply_code)      # Código de la última respuesta
$T(reply_reason)    # Reason de la última respuesta
$T(branch_index)    # Índice del branch actual
$T_req(hdrs)        # Headers de la petición original
$T_rpl(hdrs)        # Headers de la respuesta

Estadísticas en Tiempo Real

# Mediante kamcmd
kamcmd tm.stats
kamcmd tm.hash_stats

# Información de transacciones activas
kamcmd tm.t_list

Problemas Comunes

Problema: Transacciones que nunca se limpian (memory leak)

# Solución: Verificar que los timers están correctamente configurados
modparam("tm", "fr_timer", 30000)
modparam("tm", "wt_timer", 10000)  # Wait timer

Problema: Retransmisiones excesivas

# Verificar que auto_inv_100 está activo
modparam("tm", "auto_inv_100", 1)

# Y que los timers no son demasiado agresivos
modparam("tm", "retr_timer1", 500)  # T1 = 500ms
modparam("tm", "retr_timer2", 4000) # T2 = 4s

Problema: Failure route no se ejecuta

# Asegurarse de registrar la failure route ANTES de t_relay()
route[RELAY] {
    t_on_failure("MANAGE_FAILURE");  # ANTES
    t_relay();                        # DESPUÉS
}

 

Mejores Prácticas 

1. Siempre Verificar el Valor de Retorno

if (!t_relay()) {
    # Manejar el error apropiadamente
    xlog("L_ERR", "Relay failed for $rm from $si\n");
    sl_reply_error();
}
exit;  # IMPORTANTE: exit después de t_relay()

2. Usar exit Después de t_relay()

Una vez que t_relay() tiene éxito, el mensaje ha sido enviado y no debe continuar el procesamiento:

# CORRECTO
if (t_relay()) {
    exit;
}

# INCORRECTO - puede causar comportamiento inesperado
t_relay();
# más código aquí (NO HACER ESTO)

3. Configurar Failure Routes Apropiadamente

route[RELAY] {
    # Registrar failure route solo cuando sea necesario
    if ($rU =~ "^[0-9]+$") {  # Solo para números
        t_on_failure("PSTN_FAILURE");
    }
    t_relay();
    exit;
}

4. Dimensionar Correctamente el Hash Table

Para un servidor con millones de transacciones por día:

# Regla general: hash_size >= transacciones_por_segundo * avg_duration
# Ejemplo: 1000 TPS * 30s avg = 30000 transacciones concurrentes
modparam("tm", "hash_size", 32768)  # Siguiente potencia de 2

5. Monitorear Estadísticas

event_route[core:worker-one-init] {
    # Log estadísticas cada 60 segundos
    $var(interval) = 60;
    route(LOG_TM_STATS);
}

route[LOG_TM_STATS] {
    $var(current) = $stat(tm:current_transactions);
    $var(total) = $stat(tm:total_transactions);
    xlog("L_INFO", "TM Stats: current=$var(current), total=$var(total)\n");
    
    # Reprogramar
    timer_period("LOG_TM_STATS", $var(interval));
}

Conclusión

La función t_relay() del módulo TM es el corazón del procesamiento stateful en Kamailio. Su correcta comprensión y utilización es fundamental para construir sistemas VoIP robustos, escalables y conformes con los estándares SIP.

Los aspectos clave a recordar son:

  • t_relay() crea y gestiona transacciones completas según RFC 3261
  • Implementa retransmisión automática y gestión de timers
  • Permite failover y load balancing sofisticados mediante failure routes
  • Requiere más recursos que el procesamiento stateless pero proporciona funcionalidad crítica
  • Debe ser correctamente dimensionado y monitoreado en entornos de producción

La maestría en el uso de t_relay() y el módulo TM en general distingue a los administradores experimentados de Kamailio y es esencial para implementaciones de nivel enterprise.

Vota el Articulo: 

Sin votos (todavía)
Evalúa la calidad del articulo
Suscribirse a Comentarios de "La Función t_relay del Módulo TM en Kamailio" Suscribirse a VozToVoice - Todos los comentarios