6. Control de congestión en TCP

El control de congestión evita que la red se sature cuando varios flujos compiten por el mismo enlace. TCP ajusta su ventana de congestión en función de las pérdidas y del tiempo que tardan los ACK en regresar.

Desde RFC 5681 el algoritmo base combinó slow start, congestion avoidance, fast retransmit y fast recovery. Versiones modernas como CUBIC o BBR parten de esos principios pero aplican métodos estadísticos más sofisticados para adaptarse a enlaces de alta velocidad.

6.1 ¿Qué es la congestión?

Una red congestionada no puede atender todas las solicitudes que recibe. Los buffers de routers se llenan, aumentan las colas y finalmente se descartan paquetes. TCP interpreta la pérdida o los grandes retrasos como señales para disminuir el ritmo de envío antes de que la red colapse.

  • Si se pierden pocos paquetes, basta con reducir temporalmente la ventana.
  • Si los RTT se disparan, el emisor asume que hay encolamientos y evita crecer.
  • Cuando desaparece la congestión, la ventana vuelve a ampliarse para recuperar el rendimiento.

6.2 Slow start

Slow start inicia cada conexión con una ventana de congestión (cwnd) pequeña, tradicionalmente de uno o dos MSS. Tras cada ACK, la ventana se duplica aproximadamente, lo que produce un crecimiento exponencial.

Este comportamiento dura hasta que la cwnd alcanza el umbral de slow start (ssthresh) o hasta que ocurre una pérdida. El propósito es descubrir rápidamente el ancho de banda disponible sin saturar la red de inmediato.

6.3 Congestion avoidance

Cuando cwnd supera ssthresh, el crecimiento pasa a ser lineal. Por cada RTT exitoso la ventana aumenta en aproximadamente un MSS. Este modo busca estabilidad: evita oscilaciones bruscas y mantiene el flujo cerca del punto óptimo de utilización.

Si se detecta pérdida por timeout, TCP considera que hubo congestión severa, por lo que reduce ssthresh a la mitad de la ventana actual y reinicia slow start desde un valor más pequeño.

6.4 Fast retransmit y fast recovery

Tres ACK duplicados son una señal temprana de pérdida. Fast retransmit reenvía el segmento faltante sin esperar al temporizador. Luego fast recovery baja ssthresh y reduce cwnd, pero evita regresar al crecimiento exponencial si la red parece estable.

  1. ssthresh ← cwnd / 2
  2. cwnd ← ssthresh + 3 × MSS (compensa los segmentos en vuelo)
  3. Por cada ACK duplicado adicional se incrementa cwnd en un MSS.
  4. Cuando llega el ACK del segmento perdido, cwnd vuelve a ssthresh y retoma congestion avoidance.

Este ciclo permite recuperarse rápidamente de pérdidas aisladas sin reducir drásticamente el rendimiento.

6.5 Adaptación del caudal

El caudal efectivo es proporcional a cwnd/RTT. Si la ventana se reduce o el RTT aumenta, el throughput cae. Los emisores modernos estiman el RTT con filtros exponenciales para reaccionar a variaciones sin dejarse llevar por picos espurios.

Combinar control de congestión con control de flujo asegura que la cantidad de datos aceptados por el receptor sea coherente con la capacidad de la red que los transporta.

6.6 Representación gráfica típica

El patrón clásico de cwnd es un diente de sierra: crece linealmente, se reduce a la mitad ante pérdida y vuelve a crecer. Analizar la pendiente de ese diente permite estimar si la conexión está limitada por el ancho de banda disponible o por la configuración del host.

Herramientas como Wireshark permiten graficar cwnd en Statistics > TCP Stream Graphs > Time Sequence Graph (Stevens).

6.7 Simulación en Python

El siguiente script modela slow start y congestion avoidance, incluyendo eventos de pérdida por timeout y por ACK duplicados:

Crecimiento de cwnd

def simular_cwnd(rangos, mss=1, ssthresh=16):
    cwnd = mss
    for evento in rangos:
        if evento == "ack":
            if cwnd < ssthresh:
                cwnd *= 2  # slow start
            else:
                cwnd += mss  # congestion avoidance
        elif evento == "triple_ack":
            ssthresh = max(mss, cwnd // 2)
            cwnd = ssthresh + 3 * mss
        elif evento == "timeout":
            ssthresh = max(mss, cwnd // 2)
            cwnd = mss
        yield cwnd, ssthresh, evento

secuencia = ["ack"] * 6 + ["triple_ack"] + ["ack"] * 4 + ["timeout"] + ["ack"] * 4
for valor, umbral, evento in simular_cwnd(secuencia):
    print(f"Evento={evento:11s} -> cwnd={valor:02d}, ssthresh={umbral:02d}")

Ajustando la lista de eventos se puede observar cómo cada tipo de pérdida modifica la ventana, lo que ayuda a interpretar capturas reales.

6.8 Observación con PowerShell

Windows ofrece contadores de rendimiento y cmdlets que exponen métricas relacionadas con la congestión.

Cmdlets y contadores

Get-NetTCPConnection -State Established |
  Select-Object -First 5 -Property LocalPort, RemotePort, CongestionControlAlgorithm

Get-Counter -Counter "\TCPv4\Connection Failures" -SampleInterval 1 -MaxSamples 5

El primer comando revela qué algoritmo está usando Windows (por ejemplo, CUBIC). El segundo permite detectar fallas relacionadas con congestión prolongada cuando los valores crecen de manera sostenida.

6.9 Relación entre pérdida y rendimiento

Un modelo simplificado establece que el throughput de TCP es proporcional a MSS / (RTT * sqrt(p)), donde p es la probabilidad de pérdida. Esto explica por qué enlaces de alta latencia con pérdidas leves pueden experimentar caídas dramáticas en el ancho de banda utilizable.

Para mitigar este efecto se emplean colas con Active Queue Management (AQM) como RED o CoDel, que intentan señalar congestion antes de que los buffers se llenen.

6.10 Buenas prácticas

  • Monitorear cwnd y los RTT en servidores críticos para detectar cuellos de botella.
  • Habilitar algoritmos modernos (CUBIC, BBR) si el sistema operativo los soporta y el entorno lo amerita.
  • Configurar AQM en routers para reducir pérdidas masivas y mantener latencias controladas.

Comprender el control de congestión permite explicar variaciones de rendimiento que no se deben a la aplicación sino a la red subyacente.