4.4. Conexiones TCP

En esta sección y las siguientes vamos a repasar en profundidad los estados y cómo son gestionados para cada uno de los tres protocolos básicos: TCP, UDP e ICMP. Asimismo, veremos cómo se gestionan por defecto las conexiones si no se pueden incluir en ninguno de los anteriores protocolos. Empezaremos con el protocolo TCP, ya que es un protocolo de "flujos" en sí mismo (stateful protocol: como ya se ha explicado en la introducción, "stateful" implicaría que todos los paquetes de un mismo flujo son considerados como la misma cosa, como un todo que llega "en porciones"); además contiene muchos detalles interesantes respecto a la máquina de estados en iptables.

Una conexión TCP siempre se inicia con el "apretón de manos en tres pasos" (3-way handshake), que establece y negocia la conexión sobre la que se enviarán los datos. La sesión entera se inicia con un paquete SYN, seguido por un paquete SYN/ACK y finalmente por un paquete ACK, para confirmar el establecimiento de la sesión (1-"hola, ¿quieres hablar conmigo?", 2-"de acuerdo", 3-"bien, pues empecemos"). En este momento la conexión se establece y está preparada para empezar a enviar datos. El gran problema es: ¿cómo maneja el seguimiento de conexiones todo este tráfico? En realidad de una forma bastante simple.

Al menos en lo que concierne al usuario, el seguimiento de conexiones trabaja más o menos de la misma manera para todo tipo de conexiones. Échale un vistazo al gráfico de abajo para ver exactamente en qué estado entra el flujo durante las diferentes fases de la conexión. Como puedes observar, desde el punto de vista del usuario el código del seguimiento de conexiones realmente no acompaña al flujo de la conexión TCP. Una vez ha visto un paquete (el SYN), considera la conexión como nueva (NEW). En cuanto ve el paquete de retorno (SYN/ACK), considera la conexión como establecida (ESTABLISHED). Si piensas en éllo un momento, entenderás por qué: con esta particular forma de trabajar puedes permitir a los paquetes NEW y ESTABLISHED que abandonen tu red local, pero permitiendo únicamente a las conexiones ESTABLISHED que vuelvan a la red local y funcionen correctamente. Por el contrario, si la máquina de seguimiento de conexiones tuviera que considerar todo establecimiento de conexión como NEW, no seríamos capaces de detener los intentos de conexión desde el exterior hacia nuestra red local, puesto que tendríamos que permitir el retorno de todos los paquetes NEW. Para complicarlo todo un poco más, existen varios estados internos más dentro del núcleo que se usan para las conexiones TCP, pero que no están disponibles en el espacio de usuario. Básicamente siguen los estándares de estados especificados en RFC 793 - Protocolo de Control de Transmisiones, páginas 21-23 (en inglés). Los tendremos en cuenta un poco más adelante en esta misma sección.

Como puedes ver, desde el punto de vista del usuario es bastante simple. Sin embargo, desde el punto de vista del núcleo el esquema es un poco más complejo. Veamos un ejemplo que ilustra a la perfección cómo cambian los estados de la conexión en la tabla /proc/net/ip_conntrack. El primer estado es registrado a la recepción del primer paquete SYN de una conexión.

tcp      6 117 SYN_SENT src=192.168.1.5 dst=192.168.1.35 sport=1031 \
     dport=23 [UNREPLIED] src=192.168.1.35 dst=192.168.1.5 sport=23 \
     dport=1031 use=1
   

Como puedes ver en la entrada anterior, tenemos un estado concreto en el cual un paquete SYN ha sido enviado (se establece la bandera SYN_SENT) y que todavía no ha recibido ninguna respuesta (de ahí la bandera [UNREPLIED]). El siguiente estado interno se alcanzará cuando se vea un paquete en la otra dirección.

tcp      6 57 SYN_RECV src=192.168.1.5 dst=192.168.1.35 sport=1031 \
     dport=23 src=192.168.1.35 dst=192.168.1.5 sport=23 dport=1031 \
     use=1
   

En este caso hemos recibido el correspondiente paquete SYN/ACK como respuesta. En cuanto llega, el estado cambia una vez más, esta vez a SYN_RECV: indica que el paquete SYN original llegó correctamente y que el paquete SYN/ACK de retorno también ha atravesado correctamente el cortafuegos. Además, esta entrada del seguimiento de conexiones ya ha visto tráfico en ambas direcciones y por éllo se considera que ha obtenido respuesta. Este hecho no es explícito, pero se considera asumido de la misma forma que ocurría con la bandera [UNREPLIED] anterior. El último paso se dará cuando veamos el paquete ACK final del "apretón de manos" (3-way handshake).

tcp      6 431999 ESTABLISHED src=192.168.1.5 dst=192.168.1.35 \
     sport=1031 dport=23 src=192.168.1.35 dst=192.168.1.5 \
     sport=23 dport=1031 use=1
   

En este último ejemplo ya hemos visto el paquete ACK final y la conexión a entrado en el estado ESTABLISHED (establecida), al menos hasta donde llega el control de los mecanismos internos de iptables. Después de unos pocos paquetes más, la conexión se convertirá en [ASSURED] ("asegurada"), tal como se ha mostrado en la introducción del capítulo.

Cuando una conexión TCP se cierra, lo hace de la siguiente manera y tomando los siguientes estados:

Como puedes ver, en realidad la conexión nunca se cierra hasta que se envía el último paquete ACK. Ten en cuenta que este gráfico sólo muestra cómo se cierra una conexión en circunstancias normales. También puede cerrarse enviando un paquete RST ("reset", reiniciar) si la conexión se tiene que rechazar. En este caso la conexión será cerrada tras un periodo de tiempo predeterminado.

En condiciones normales, cuando una conexión TCP se cierra, entra en el estado TIME_WAIT ("tiempo de espera"), que por defecto es de 2 minutos. Este lapso de tiempo se emplea para que todos los paquetes que se han quedado "atascados" de alguna manera puedan atravesar igualmente el conjunto de reglas, incluso después de que la conexión se haya cerrado; de esta forma se dispone de una especie de "buffer"/colchón de tiempo para que los paquetes que se han quedado parados en algún enrutador congestionado, puedan llegar al cortafuegos o al otro extremo de la conexión sin problemas.

Si la conexión se reinicia por un paquete RST, el estado cambia a CLOSE ("cerrar"). Esta orden implica que la conexión por defecto dispone de 10 segundos antes de que se cierre definitivamente. Los paquetes RST no piden consentimiento de ninguna clase y cortan la conexión directamente.

También hay otros estados además de los que ya se han comentado. A continuación tienes la lista completa de los posibles estados que puede tomar un flujo TCP y sus tiempos límites.

Table 4-2. Estados internos

EstadoTiempo límite
NONE30 minutos
ESTABLISHED5 días
SYN_SENT2 minutos
SYN_RECV60 segundos
FIN_WAIT2 minutos
TIME_WAIT2 minutos
CLOSE10 segundos
CLOSE_WAIT12 horas
LAST_ACK30 segundos
LISTEN>2 minutos

Estos tiempos no son de ninguna manera definitivos, ya que pueden cambiar con las revisiones del núcleo, además de poderse cambiar a través del sistema de ficheros proc mediante las variables /proc/sys/net/ipv4/netfilter/ip_ct_tcp_*. Sin embargo, en la práctica los valores por defecto deberían estar bastante bien definidos. Los tiempos se indican en "jiffies" (centésimas de segundo), es decir, 3000 significa 30 segundos.

Note

Ten en cuenta que la parte del espacio de usuario de la máquina de estados no se fija en las banderas TCP establecidas en los paquetes TCP. Hacer éso normalmente es una mala idea, ya que puedes querer permitir que los paquetes en el estado NEW puedan atravesar el cortafuegos, pero cuando especificas la bandera NEW lo que en la mayoría de las ocasiones quieres permitir son los paquetes SYN.

En la implementación actual de los estados no ocurre ésto, ya que incluso un paquete sin ningún valor establecido o sin una bandera ACK, se considerará como NEW. Este tipo de comparación puede ser útil en sistemas con cortafuegos redundantes, pero en general es una malísima idea en tu red personal, dónde sólo tienes un cortafuegos. Para evitar este comportamiento puedes usar el comando explicado en la sección Paquetes cuyo estado es NEW pero cuyo bit SYN no se ha establecido del apéndice Problemas y preguntas frecuentes. Otra forma de conseguirlo es instalando la extensión tcp-window-tracking que encontrarás en el patch-o-matic, que permitirá que el cortafuegos sea capaz de hacer el seguimiento de los estados en función de las configuraciones de las ventanas TCP.