Copyright © 2001-2003 Oskar Andreasson
Se concede permiso para copiar, distribuir y/o modificar este documento según las condiciones de la Licencia de Libre Documentación de GNU (GNU Free Documentation License), Versión 1.1; siendo las Secciones Invariables (Invariant Sections) la "Introducción" y todos sus sub-apartados, con la Portada indicando "Autor Original: Oskar Andreasson" y sin texto en la Contraportada. Una copia de esta licencia en castellano se incluye en el apartado "GNU Free Documentation License" (también se incluye la versión oficial en inglés).
Todos los "scripts" del presente tutorial quedan cubiertos por la Licencia Pública General de GNU (GNU General Public License). Los "scripts" son de código libre (free source); puedes redistribuirlos y/o modificarlos siguiendo las condiciones de la Licencia Pública General de GNU (GNU General Public License) publicada por la Fundación del Software de Libre Distribución (Free Software Foundation), versión 2 de la Licencia.
Los "scripts" se ofrecen con la esperanza de que sean útiles, pero SIN NINGUNA GARANTíA; ni siquiera garantía implícita por COMPRA-VENTA o ADECUACIóN A UN PROPóSITO PARTICULAR. Para más detalles, referirse a la Licencia Pública General de GNU (GNU General Public License) [se incluye la versión en castellano].
Deberías haber recibido una copia de la Licencia Pública General de GNU (GNU General Public License) con este tutorial, en la sección titulada "GNU General Public License" (en inglés y en castellano); si no es así, comunícalo a la Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
Por encima de todo quiero dedicar este documento a mi maravillosa novia Ninel. Nunca podré apoyarte tanto como tú me has apoyado a mí. Deseo hacerte al menos tan feliz como tú me has hecho a mí.
También quiero dedicar este trabajo a todos los programadores y encargados del mantenimiento de Linux por su trabajo tan increíblemente duro. Gracias a éllos se hace posible éste maravilloso sistema operativo.
Quiero expresar mi agradecimiento a Oskar Andreasson por este tutorial. Gracias a él todos aquellos que deseamos conocer los secretos de IPtables y por ende los cortafuegos, tenemos una buena guía para empezar y adquirir un nivel bastante aceptable. Asímismo quiero dar las gracias a mis compañeros de traducción, por la paciencia y tesón demostrados. Por último, me atrevo a pedir a todo aquél que lea esta traducción que sea condescendiente con nosotros: hemos traducido el tutorial de la mejor manera que hemos sabido, pero pueden haber fallos y agradeceríamos vuestras sugerencias y correcciones.
Soy alguien con demasiados ordenadores viejos en sus manos. Tengo mi propia red de área local (LAN) y deseo que todas las máquinas puedan conectarse a Internet, al tiempo que la red interna permanece suficientemente segura. En este sentido, iptables es una buena mejora del antiguo ipchains. Con ipchains puedes construir una red medianamente segura desechando cualquier paquete entrante (de Internet) no destinado a unos puertos determinados. Sin embargo hay temas, como el FTP pasivo, o DCC saliente en IRC, que plantean problemas. Estos servicios asignan puertos en el servidor, informan al cliente sobre ellos y entonces esperan a que el cliente conecte. Al principio tuve algunos problemas con el código de iptables y de alguna manera pensé que no estaba listo para publicarlo y utilizarlo en entornos productivos. Sin embargo, ahora recomendaría actualizarse a cualquiera que esté utilizando ipchains (o incluso el antiguo ipfwadm), a menos que sea feliz con los resultados que está obteniendo y no necesite ninguna de las características adicionales que ofrece iptables.
Este documento se ha escrito únicamente para empezar a conocer el maravilloso mundo de iptables. Nunca se planteó para albergar información sobre fallos de seguridad específicos en iptables o Netfilter. Si encuentras fallos o comportamientos extraños en iptables o cualquiera de sus componentes, debes contactar con la lista de correo de Netfilter y comentarles el problema. Éllos podrán decirte si en realidad es un fallo o si ya está corregido. Aunque son muy raros los fallos de seguridad encontrados en iptables o en Netfilter, todavía se cuelan uno o dos de vez en cuando y se detallan convenientemente en la página principal de Netfilter, que es donde debes ir para encontrar información sobre estos temas.
Lo anterior también implica que los conjuntos de reglas (rule-sets) disponibles en este tutorial no se han escrito para remediar fallos en Netfilter. Su finalidad principal es mostrar cómo crear reglas fácilmente que solucionen todos los problemas con que nos podamos encontrar. Por ejemplo, este tutorial no explica cómo cerrar el puerto HTTP simplemente porque el servidor web Apache sea vulnerable en su versión 1.2.12 (en realidad veremos cómo cerrarlo, pero no por este motivo).
Este documento se ha escrito simplemente para ofrecer un manual bueno y sencillo sobre cómo empezar con iptables, pero siendo al mismo tiempo lo más completo posible. No contiene ningún objetivo o regla de concordancia que estén en el "patch-o-matic" (la sección de la web de Netfilter que se encarga de ofrecer los últimos desarrollos aplicables al núcleo de iptables), simplemente porque requeriría demasiado esfuerzo mantener actualizada una lista así. Si necesitas información sobre estas actualizaciones, deberías leer (y entender al 100%) los comentarios que acompañan a cada parche y cualquier otro documento disponible en la web de Netfilter.
Este documento requiere algunos conocimientos sobre Linux/Unix, programación en consola (shell scripting), algunos conocimientos básicos sobre las interioridades del núcleo, así como saber compilar tu propio núcleo.
He intentado por todos los medios eliminar todos los requisitos previos para aprovechar al máximo este documento, pero ciertamente es casi imposible no necesitar ciertos conocimientos previos.
Para hacer referencia a comandos, archivos y otras informaciones específicas se emplean las siguientes convenciones tipográficas.
Los ejemplos de código y los resultados de los comandos (las salidas en pantalla) se muestran con tipografía de
ancho fijo(tipo Courier), mientras que los comandos escritos por el usuario se muestran en
ancho fijo y negrita:
[blueflux@work1 neigh]$ ls
default eth0 lo
[blueflux@work1 neigh]$
Todos los comandos y nombres de programa se muestran en negrita.
Todo aquello referente al sistema como el hardware (la máquina en sí), las interioridades del núcleo (kernel) o entes abstractos como la interfaz de bucle local (loopback interface) se muestran en cursiva.
La respuesta del ordenador se muestra así en el texto.
Los archivos y las rutas de acceso se muestran así: /usr/local/bin/iptables.
Bueno, encontraba un gran vacío en los CÓMOs (HOWTO's) existentes, en los cuales faltaba informacion sobre las funciones "iptables" y "Netfilter" del nuevo kernel 2.4.x de Linux. Entre otras cosas, voy a intentar responder las preguntas que algunos podéis tener sobre las nuevas posibilidades, como la comparación por estado (state matching). Muchas de estas cuestiones se ilustrarán con un ejemplo rc.firewall.txt que podéis usar en vuestros scripts /etc/rc.d/. [Sí, este archivo se basa en el COMO sobre enmascaramiento (masquerading), para aquellos que lo reconozcan].
Además hay un pequeño guión que he escrito por si te lías tanto como yo durante la configuración. Está disponible como rc.flush-iptables.txt.
He consultado a Marc Boucher y otras personas del equipo del núcleo de NetFilter. Les agradezco de todo corazón su trabajo y su ayuda en este tutorial, que escribí para boingworld.com y ahora lo mantengo en mi propio sitio web frozentux.net. Este documento os guiará paso a paso a través del proceso de configuración y espero que os ayude a comprender algo más acerca del paquete iptables. Basaré la mayor parte del estudio desarrollado aquí en el ejemplo del archivo rc.firewall, ya que encuentro en ese ejemplo una buena vía para aprender a usar iptables. He decidido estudiar sólamente las cadenas básicas y desde ahí adentrarme en todas y cada una de las cadenas atravesadas en su orden correspondiente. De esta forma el tutorial es un poquito mas complicado de seguir, aunque también es la forma más lógica. Siempre que encuentres algo difícil de comprender, simplemente vuelve a este tutorial.
Este documento contiene algunos términos que pueden necesitar explicaciones más detalladas antes que los leas. Esta sección intentará cubrir los más obvios y cómo los he escogido para usarlos en el documento.
DNAT - Destination Network Address Translation (Traducción de la Dirección de Red de Destino). DNAT se refiere a la técnica de traducir la dirección IP de destino de los paquetes, o sea, de cambiarla. Se emplea conjuntamente con SNAT para permitir a varios hosts (máquinas) compartir una sola dirección IP de conexión con Internet entre todos ellos y al mismo tiempo que todavía puedan seguir ofreciendo funciones de servidor. Normalmente ésto se consigue asignando diferentes puertos a la dirección IP externa (la dirección enrutable) y a continuación informando al encaminador/enrutador (router) de Linux dónde debe enviar el tráfico.
SNAT - Source Network Address Translation (Traducción de la Dirección de Red de Origen). Se refiere a las técnicas empleadas para traducir la dirección de origen de un paquete en otra distinta. Como en DNAT, se emplea para que varios hosts puedan compartir una misma dirección IP de salida a Internet. Estas técnicas se emplean debido a que cada vez hay menos direcciones de Internet disponibles a causa de la propia estructura de IPv4 (la versión 6 del protocolo IP, IPv6, solucionará este problema).
Flujo (de datos) (Stream) - Este término se refiere a una conexion que envía y recibe paquetes relacionados entre sí de algún modo. Básicamente, he usado este término para cualquier conexión que envíe 2 ó más paquetes en ambas direcciones. En TCP ésto puede corresponder a una conexión que envía un paquete SYN y entonces responde con un paquete SYN/ACK; pero también puede ser equivalente a una conexión que envía un paquete SYN y responde con un mensaje ICMP de servidor (host) inalcanzable. En otras palabras, uso este término muy libremente.
Estado (State) - Este término se refiere a en qué estado se encuentra el paquete, de acuerdo con el estándar RFC 793 - Transmission Control Protocol o bien con los estados de usuario empleados en Netfilter/iptables. Ten en cuenta que los estados usados interna y externamente no siguen totalmente la especificación RFC 793. La razón principal es que Netfilter tiene hacer varias suposiciones acerca de las conexiones y los paquetes.
Espacio de usuario (User space) - Con este término me refiero a cualquier cosa que tenga lugar fuera del núcleo. Por ejemplo, la ejecución de iptables -h tiene lugar fuera del núcleo, mientras que iptables -A FORWARD -p tcp -j ACCEPT tiene lugar (parcialmente) dentro del núcleo, puesto que se añade una nueva regla al conjunto de reglas.
Espacio del núcleo (Kernel space) - Esto es más o menos lo opuesto al espacio de usuario. Ésto implica las acciones que tienen lugar dentro del núcleo y no fuera de él.
El objetivo de este capítulo es iniciarte y ayudarte a entender el papel que hoy día tienen Netfilter e iptables dentro de Linux. Este capítulo debería conseguir que estés listo para experimentar e instalar tu cortafuegos. Dándole el tiempo necesario y con la perseverancia adecuada, conseguirás que funcione exactamente como desees que lo haga.
El paquete de espacio de usuario de iptables se puede descargar desde la página de inicio de Netfilter. El paquete iptables también utiliza las capacidades del espacio del núcleo, las cuales pueden configurarse durante la ejecución de make configure. Los pasos necesarios se discutirán a continuación.
Para ejecutar lo más básico de iptables tienes que configurar las siguientes opciones en el núcleo mientras ejecutas make config o uno de sus comandos relacionados:
CONFIG_PACKET - Esta opción permite que las aplicaciones y las utilidades que lo necesiten puedan trabajar directamente con distintos periféricos de red. Ejemplos de estas utilidades son tcpdump o snort.
![]() | En sentido estricto, CONFIG_PACKET no es necesario para que iptables funcione, pero puesto que tiene tantos usos diferentes, he decidido incluirlo. Si crees que no lo necesitas, no lo incluyas. |
CONFIG_NETFILTER - Esta opción se requiere cuando vas a utilizar tu ordenador como cortafuegos o como puerta de enlace (gateway) con Internet. En otras palabras, es imprescindible para que funcione cualquier cosa de las que se explican en este tutorial. Entiendo que éso es lo que deseas, ya que estás leyendo el tutorial.
Y, por supuesto, necesitas añadir los controladores (drivers) necesarios para que tus interfases funcionen correctamente, es decir, el adaptador Ethernet y las interfases PPP y SLIP. Todo lo anterior sólo añade un poco de lo más básico de iptables. En realidad no serás capaz de hacer nada realmente productivo, ya que sólo añade la estructura básica al núcleo. Si quieres utilizar las opciones más avanzadas de iptables, tendrás que configurar las opciones necesarias en el núcleo. A continuación te mostraré las opciones disponibles en una versión 2.4.9 básica del núcleo y las explicaré brevemente:
CONFIG_IP_NF_CONNTRACK - Este módulo es necesario para efectuar el seguimiento de las conexiones. El seguimiento de las conexiones lo emplean, entre otros, la traducción de direcciones (NAT) y el enmascaramiento (Masquerading). Si necesitas proteger con un cortafuegos las máquinas de una red local, definitivamente debes marcar esta opción. Por ejemplo, este módulo lo necesita el script rc.firewall.txt para funcionar.
CONFIG_IP_NF_FTP - Este módulo es necesario si quieres hacer seguimiento de conexiones en las conexiones FTP. Puesto que estas conexiones son bastante difíciles de monitorizar en condiciones normales, el conntrack necesita lo que se denomina un asistente y esta opción lo compila en el núcleo. Si no añades este módulo no serás capaz de hacer transferencias FTP correctamente a través del cortafuegos o la puerta de enlace.
CONFIG_IP_NF_IPTABLES - Esta opción es necesaria si quieres realizar algún tipo de filtrado, enmascaramiento (masquerading) o traducción de direcciones (NAT). Añade toda la estructura de identificación de iptables al núcleo. Sin ésto, no serás capaz de hacer nada en absoluto con iptables.
CONFIG_IP_NF_MATCH_LIMIT - Este módulo no es imprescindible, pero se emplea en el ejemplo rc.firewall.txt. Esta opción añade la comparación LIMIT (límite), ofreciendo la posibilidad de controlar el número de paquetes por minuto que se deben comparar, gobernado por la regla adecuada. Por ejemplo, con -m limit --limit 3/minute compararíamos un máximo de 3 paquetes por minuto. Mediante este módulo también podemos evitar ciertos ataques de denegación de servicios (en inglés: Denial of Service attacks, DoS attacks).
CONFIG_IP_NF_MATCH_MAC - Este módulo nos permite comparar paquetes basándonos en las direcciones físicas MAC: cada adaptador de red Ethernet tiene su propia dirección MAC, distinta a la de cualquier otro adaptador, aunque sea de la misma marca y modelo. Así, por ejemplo podremos bloquear paquetes en función de la dirección MAC utilizada y bloquear ordenadores concretos puesto que la dirección MAC de esos ordenadores raramente cambia (ya que raramente se sustituye el adaptador Ethernet por uno nuevo). No se utiliza esta opción ni en el ejemplo rc.firewall.txt ni en ningún otro sitio.
CONFIG_IP_NF_MATCH_MARK - Nos permite utilizar la comparación MARK. Por ejemplo, podemos utilizar el objetivo MARK para marcar paquetes, de forma que más adelante se puedan comparar y filtrar paquetes dependiendo de si tienen la marca o no. De hecho esta opción es la comparación MARK y más adelante veremos el objetivo MARK.
CONFIG_IP_NF_MATCH_MULTIPORT - Este módulo permite que comparemos paquetes con un amplio rango de puertos de origen o de destino. Normalmente ésto no sería posible, pero con este módulo sí lo es.
CONFIG_IP_NF_MATCH_TOS - Con esta comparación podemos comparar paquetes en base a su campo TOS, es decir, su Tipo de Servicio (Type Of Service). El tipo de servicio se puede establecer mediante determinadas reglas en la tabla mangle y mediante los comandos ip/tc.
CONFIG_IP_NF_MATCH_TCPMSS - Esta opción nos ofrece la posibilidad de comparar los paquetes TCP en función de su campo MSS.
CONFIG_IP_NF_MATCH_STATE - Aquí tenemos una de las mayores novedades respecto a ipchains. Con este módulo podemos realizar comparaciones por flujos de paquetes (stateful matching). Por ejemplo, si en una conexión TCP ya hemos visto tráfico en dos direcciones, los paquetes que les sigan serán considerados como ESTABLISHED (establecido), aplicándoles por éllo las mismas acciones que a los paquetes que iniciaron el flujo. Este módulo se usa ampliamente en el ejemplo rc.firewall.txt.
CONFIG_IP_NF_MATCH_UNCLEAN - Este módulo nos brinda la posibilidad de comparar paquetes IP, TCP, UDP e ICMP que no cumplen con las normas o son inválidos. En condiciones normales se desecharán estos paquetes, pero nunca sabremos si son legítimos o no. Además, ten en cuenta que esta comparación todavía está en fase experimental y puede que no funcione correctamente en todos los casos.
CONFIG_IP_NF_MATCH_OWNER - con esta opción tendremos la oportunidad de comparar en base al propietario de la conexión. Por ejemplo, podremos permitir acceso a Internet únicamente al usuario "root". Este módulo se escribió para mostrar lo que se podía lograr con el nuevo iptables. Ten en cuenta que esta comparación es experimental y puede que no le funcione bien a todo el mundo.
CONFIG_IP_NF_FILTER - este módulo añade la tabla filter básica que permitirá efectuar el filtrado IP. En la tabla filter encontraremos las cadenas INPUT, FORWARD y OUTPUT. Este módulo es necesario si pretendemos hacer algún tipo de filtrado en los paquetes que recibamos y/o enviemos.
CONFIG_IP_NF_TARGET_REJECT - este objetivo nos permite especificar que se debe enviar un mensaje de error ICMP como respuesta a los mensajes entrantes, en lugar de simplemente desecharlos e ignorarlos. Ten en cuenta que las conexiones TCP, al contrario que las ICMP y las UDP, siempre se reinician o rechazan con un paquete TCP RST.
CONFIG_IP_NF_TARGET_MIRROR - sirve para permitir a los paquetes que sean devueltos ("rebotados") al remitente. Por ejemplo, si configuramos un objetivo MIRROR en el puerto de destino HTTP, en nuestra cadena INPUT, y alguien intenta acceder a este puerto, le devolveremos sus paquetes y como resultado probablemente acabará viendo su propia pagina web inicial (homepage).
CONFIG_IP_NF_NAT - este módulo permite que se efectúe la traducción de dirección de red (network address translation), o NAT, en sus diferentes variantes. La opción nos da acceso a la tabla nat en iptables y es necesaria si queremos hacer reenvío a puertos (port forwarding), enmascaramiento (masquerading), etc. Ten en cuenta que esta opción no es imprescindible para el cortafuegos y el enmascaramiento de una LAN, pero deberías tenerlo activo a no ser que seas capaz de asignar direcciones IP únicas para cada uno de los hosts. Así pues, esta opción es necesaria para que el script de ejemplo rc.firewall.txt funcione correctamente, y es ciertamente imprescindible si no puedes asignar direcciones IP únicas a cada host.
CONFIG_IP_NF_TARGET_MASQUERADE - este módulo añade el objetivo MASQUERADE. Por ejemplo, si no sabemos qué dirección IP tenemos para conectar a Internet, ésta será la forma ideal de conseguir la IP en vez de utilizar DNAT o SNAT. En otras palabras, si utilizamos DHCP, PPP, SLIP o cualquier otra conexión que nos asigne una IP, necesitamos utilizar este objetivo en lugar de SNAT. El enmascaramiento produce una carga en el sistema algo mayor que NAT, pero funcionará sin que necesitemos conocer previamente la dirección IP.
CONFIG_IP_NF_TARGET_REDIRECT - este objetivo es útil al emplearlo junto a proxies de aplicación, por ejemplo. En vez de dejar simplemente que el paquete pase, lo remapeamos para que se dirija a nuestra máquina local. En otras palabras, de esta forma tenemos la posibilidad de crear un proxy transparente.
CONFIG_IP_NF_TARGET_LOG - esta opción añade el objetivo LOG y su funcionalidad a iptables. Podemos utilizar este módulo para registrar determinados paquetes en el syslogd y así ver qué les está pasando. Ésto sólo ya es inestimable de cara a las auditorías de seguridad, forenses o de depuración de errores del script que estés escribiendo.
CONFIG_IP_NF_TARGET_TCPMSS - esta opción se puede emplear para evitar a los Proveedores de Servicios de Internet (ISPs) y a los servidores que bloquean los paquetes "ICMP Fragmentation Needed". Los efectos de esta acción pueden ser páginas web que no lleguen, pequeños correos que sí lleguen mientras que los grandes no lo consigan, las conexiones ssh funcionan pero las conexiones scp se pierden ("mueren") tras el saludo inicial, ... En estos casos podemos utilizar el objetivo TCPMSS para superar el problema ajustando nuestro MSS (tamaño máximo de segmento) al PMTU (Path Maximum Transmit Unit, unidad máxima de transmisión). De esta forma seremos capaces de trabajar con lo que los autores de Netfilter llaman (en la ayuda para la configuración del kernel) "criminally brain-dead ISPs or servers", algo así como "servidores o ISPs absolutamente descerebrados".
CONFIG_IP_NF_COMPAT_IPCHAINS - añade un modo de compatibilidad con el obsoleto ipchains. No confíes en este módulo como solución a largo plazo para resolver la migración de los núcleos 2.2 a los 2.4, ya que es probable que desaparezca con la llegada del núcleo 2.6.
CONFIG_IP_NF_COMPAT_IPFWADM - módulo de compatibilidad con el obsoleto ipfwadm. Ni se te ocurra recurrir a este módulo como solución a largo plazo.
Como puedes observar hay un buen puñado de opciones. Acabo de explicar brevemente los tipos de comportamiento extras que puedes esperar con cada módulo, aunque sólo se trata de las opciones disponibles en un núcleo 2.4.9 simple, sin ningún extra. Si quieres ver más opciones, te recomiendo que mires las funciones existentes en el "patch-o-matic" (POM) de la zona de usuario de Netfilter, pues encontrarás montones de opciones extras para el núcleo. Los parches del POM son opciones extra que supuestamente se añadirán al núcleo en el futuro, pero que todavía no se han desarrollado lo suficiente como para añadírselos. Las razones pueden ser variadas, como que el parche todavía no sea estable, o que Linus Torvalds no pueda mantenerlo, o incluso que no quiera introducir el parche en el desarrollo del núcleo por ser todavía experimental.
Necesitarás tener compiladas en el núcleo las opciones siguientes, o bien tenerlas como módulos, para que el script rc.firewall.txt pueda funcionar. Si necesitas ayuda acerca del resto de opciones necesarias para los otros scripts, léete el capítulo sobre los scripts de ejemplo.
CONFIG_PACKET
CONFIG_NETFILTER
CONFIG_IP_NF_CONNTRACK
CONFIG_IP_NF_FTP
CONFIG_IP_NF_IRC
CONFIG_IP_NF_IPTABLES
CONFIG_IP_NF_FILTER
CONFIG_IP_NF_NAT
CONFIG_IP_NF_MATCH_STATE
CONFIG_IP_NF_TARGET_LOG
CONFIG_IP_NF_MATCH_LIMIT
CONFIG_IP_NF_TARGET_MASQUERADE
Como mínimo necesitarás lo anterior para utilizar el script rc.firewall.txt. Para el resto de scripts explicaré lo que necesitan en sus respectivas secciones. Por el momento intentemos centrarnos en el ejemplo principal.
Para empezar veremos cómo se compila el paquete iptables. Es importante comprender que la mayor parte de la configuración y compilación de iptables va de la mano de la configuración y compilación del núcleo (kernel). Determinadas distribuciones de Linux vienen con el paquete iptables preinstalado, como ocurre por ejemplo con Red Hat. Sin embargo en esta distribución por defecto está desactivado y ahora veremos cómo activarlo.
Empieza por descomprimir (desempaquetar) el paquete iptables. Para este ejemplo hemos utilizado el paquete iptables 1.2.6a y el núcleo 2.4 sin extras. Descomprime como es habitual, utilizando el comando bzip2 -cd iptables-1.2.6a.tar.bz2 | tar -xvf - (aunque también puedes teclear tar -xjvf iptables-1.2.6a.tar.bz2, que con las versiones más recientes de tar dará el mismo resultado). El paquete debería haberse descomprimido en un nuevo directorio llamado iptables-1.2.6a. Para una explicación más completa de la compilación y la forma de hacer que funcione el programa, léete el fichero iptables-1.2.6a/INSTALL.
Tras ésto, tienes la opción de configurar e instalar módulos/opciones extra en el núcleo. El procedimiento que describiremos sólo comprobará e instalará parches estándar que están pendientes de incluirse en el núcleo; existen otros parches de tipo experimental que sólo estarán disponibles al seguir otro tipo de configuración.
![]() | Algunos de estos parches son realmente experimentales y no sería una buena idea instalarlos. Sin embargo, hay montones de comparaciones y objetivos extremadamente interesantes en este punto de la instalación, así que no tengas miedo de, al menos, echarles un vistazo. Para empezar a instalar, ejecutaremos un comando similar al siguiente desde el directorio raíz del paquete iptables: |
make pending-patches KERNEL_DIR=/usr/src/linux/
La variable KERNEL_DIR debería indicar la localización
actual de tu núcleo. Normalmente este directorio será /usr/src/linux/,
aunque puede ser diferente y lo más probable es que ya sepas dónde se encuentra
el código fuente del núcleo.
![]() | Con el comando anterior sólo añadiremos determinados parches que de todas formas están a punto de entrar en el núcleo. Puede que hayan más parches y añadidos que los desarrolladores de Netfilter estén a punto de añadir al núcleo, pero que todavía no se encuentran listos para dar este paso. Una forma de instalar estos extras es mediante el siguiente comando: |
make most-of-pom KERNEL_DIR=/usr/src/linux/
De esta forma se preguntará si se instalan algunos añadidos de lo que en la jerga de Netfilter se llaman patch-o-matic, pero eludiendo los parches más extremos, ya que pueden causar la desintegración del núcleo. Date cuenta que se ha dicho "se preguntará" porque ésto es lo que hará el comando: preguntará antes de cambiar algo en el código fuente del núcleo. Para poder instalar todo el material presente en el patch-o-matic necesitarás ejecutar el siguiente comando:
make patch-o-matic KERNEL_DIR=/usr/src/linux/
No olvides leer completamente la ayuda de cada parche antes de hacer nada. Algunos parches destruyen a otros parches, mientras que otros pueden destruir el núcleo si se usan junto a otros determinados parches del patch-o-matic, etc.
![]() | No es imprescindible seguir los pasos anteriores los pasos anteriores, por éllo puedes ignorarlos si no quieres "parchear" tu núcleo. Sin embargo, hay cosas muy interesantes en el patch-o-matic a las que sería conveniente echar un vistazo, por lo que no estaría de más ejecutar los comandos aunque sólo fuera para ver lo que contienen. |
En este punto ya has terminado la parte de la instalación referida al patch-o-matic y es momento de compilar un nuevo núcleo utilizando los nuevos parches que hayas añadido al código fuente. No olvides configurar de nuevo el núcleo, ya que probablemente los nuevos parches no se habrán añadido aún a las opciones configuradas. Por otra parte, si quieres puedes compilar primero el programa iptables y dejar para el final la compilación del núcleo.
Compila ahora el programa de zona de usuario iptables. Para éllo ejecuta el siguiente comando:
make KERNEL_DIR=/usr/src/linux/
La aplicación de zona de usuario debería compilarse correctamente. Si no es así, tendrás que buscar soluciones por tí mismo, o puedes subscribirte a la lista de correo de Netfilter (en inglés), dónde tendrás la oportunidad de pedir que alguien te ayude a solucionar tus problemas. Puede que haya algo que no vaya bien durante la instalación, pero no te preocupes: intenta pensar de manera lógica sobre el problema y averigua qué es lo que va mal, o pídele a alguien que te ayude.
Si todo ha ido bien, estarás preparado para instalar los archivos binarios. Para éllo, escribe:
make install KERNEL_DIR=/usr/src/linux/
En principio todo debería funcionar en el programa. Para utilizar los cambios efectuados en iptables deberías recompilar y reinstalar tu núcleo y sus módulos, si es que todavía no lo has hecho. Para una explicación más detallada de la instalación a partir del código fuente de las aplicaciones de zona de usuario, lee el archivo INSTALL que viene con el código fuente, pues contiene excelente información sobre todo lo referente a la instalación.
La distribución Red Hat en su versión 7.1 viene precompilada con un núcleo 2.4.x que contiene a Netfilter e iptables. Además, contiene los programas básicos de zona de usuario, así como los ficheros de configuración necesarios para ejecutarlos. Sin embargo, los de Red Hat han desactivado el paquete completo y en su lugar utilizan el módulo ipchains por compatibilidad con versiones anteriores. Molesto, por no decir otra cosa, y fuente de multitud de consultas en las listas de correo preguntando por qué no funciona iptables. Así pues, echemos un vistazo a cómo desactivar el módulo ipchains y cómo instalar iptables en su lugar.
![]() | La instalación por defecto de Red Hat 7.1 incluye una decepcionante versión antigua de las aplicaciones de zona de usuario, por lo que es posible que desees compilar una nueva versión de estas aplicaciones, además de instalar un núcleo nuevo y personalizado antes de exigirle el máximo rendimiento a iptables. |
Antes de empezar tendrás que desactivar los módulos de ipchains de manera que no vuelvan a arrancar de ahora en adelante. Para conseguirlo deberás cambiar algunos nombres de fichero en los subdirectorios existentes en /etc/rc.d/. Con el siguiente comando tendrás suficiente:
chkconfig --level 0123456 ipchains off
Una vez ejecutado modificarás todos los enlaces virtuales ("soft links") que apuntan al script /etc/rc.d/init.d/ipchains y los convertirás en K92ipchains. La primera letra, que por defecto es una S, le indica a los scripts de inicio que se ejecuten. Al cambiarla por una K se le indica a los scripts que terminen el servicio, o que no lo ejecuten si no estaba arrancado. De esta forma el servicio ya no arrancará nunca.
Sin embargo, para detener un servicio que se está ejecutando en este momento necesitamos introducir otro comando: service, que puede utilizarse para actuar sobre servicios actualmente en ejecución. Así pues, para parar el servicio ipchains escribiremos:
service ipchains stop
Por último, para arrancar el servicio iptables necesitamos saber en qué niveles de ejecución ("run-levels") queremos que se inicie. Normalmente serán los niveles 2, 3 y 5, que se usan para:
2. Multiusuario sin NFS o lo mismo que el nivel 3 si no existe ninguna red.
3. Modo multiusuario completo, es decir, el nivel de ejecución habitual en el que se trabaja.
5. X11. Este modo se utiliza cuando al arrancar el sistema entras directamente en las Xwindows (el modo gráfico). Actualmente cada vez son más las distribuciones que entran por defecto en este modo.
Para conseguir que iptables se ejecute en estos niveles de ejecución, escribiremos:
chkconfig --level 235 iptables on
Es decir, instruímos a iptables para que se ejecute en los niveles 2, 3 y 5. Si quieres que se ejecute en cualquier otro nivel, deberás añadirlo al comando anterior. Sin embargo, ninguno de los otros niveles debería usarse para ejecutar iptables, por lo que en realidad no necesitas activarlo en éllos: el nivel 1 es el modo monousuario (1 sólo usuario), empleado cuando necesitas arreglar una instalación. El nivel 4 no debería usarse y el nivel 6 es para apagar el ordenador.
Para activar el servicio iptables, ejecutaremos:
service iptables start
No hay ninguna regla en el script de iptables. Para añadirlas en Red Hat 7.1 normalmente se emplean dos métodos. El primero es editar el script /etc/rc.d/init.d/iptables, teniendo como efecto secundario no deseado el hecho de que borrarás todas las reglas si actualizaste el paquete iptables mediante un RPM. El otro método consistiría en cargar/crear el conjunto de reglas y luego guardarlo con el comando iptables-save, de forma que después se carguen automáticamente mediante los scripts rc.d.
Primero explicaremos cómo configurar iptables mediante el socorrido método de copiar y pegar al script "init.d" de iptables. Para añadir reglas que se tengan que ejecutar cuando el sistema arranca el servicio, añádelas en la sección "start)", o en la función "start()". Ten en cuenta que si añades las reglas en la sección "start)" no debes olvidar parar la función "start()" dentro de esa misma sección "start)". Además, tampoco olvides editar la sección "stop)" para indicarle al script qué debe hacer, por ejemplo cuando es sistema se esté cerrando (apagando), o cuando se entre en un nivel de ejecución que no necesite de iptables. Por último, no olvides repasar las secciones "restart" y "condrestart". También debes tener en cuenta que todo este trabajo no servirá de nada si, por ejemplo, actualizas automáticamente tus paquetes mediante el Red Hat Network, o si actualizas mediante el paquete RPM de iptables.
En cuanto al segundo método de realizar la configuración, se basa en los siguientes pasos: primero crea un conjunto de reglas en un fichero de script de línea de comandos (un script de shell), o bien directamente con iptables, que cumpla con tus necesidades y empieza a experimentar con él durante un tiempo. Cuando tengas una configuración que funcione correctamente (o al menos que tú creas que no tiene errores), utiliza el comando iptables-save. Puedes utilizar: iptables-save > /etc/sysconfig/iptables, con lo que grabarías el conjunto de reglas en el fichero /etc/sysconfig/iptables. Este fichero lo usará en el futuro el script rc.d de iptables para cargar de nuevo el conjunto de reglas. Otra forma de conseguir un resultado equivalente es mediante el comando service iptables save, que automáticamente guardará el script en el fichero /etc/sysconfig/iptables. Así, la próxima vez que reinicies el sistema, el script rc.d de iptables utilizará el comando iptables-restore para cargar el conjunto de reglas desde el fichero /etc/sysconfig/iptables. Atención: no mezcles ambos métodos, ya que se pueden perjudicar gravemente el uno al otro y dejar la configuración del cortafuegos completamente inútil.
Cuando hayas terminado todos estos pasos, puedes desinstalar los paquetes ipchains e iptables preinstalados. De esta forma no permitiremos al sistema que mezcle las nuevas aplicaciones de zona de usuario de iptables con las viejas versiones preinstaladas. Sin embargo, este paso sólo es necesario si vas a instalar iptables desde el código fuente. No es raro que el paquete nuevo se mezcle con el viejo, puesto que la instalación basada en rpm instala el paquete en directorios no estándar, de forma que la nueva instalación no sobreescribirá a la vieja. Para efectuar la desinstalación, escribe:
rpm -e iptables
Y como no tiene mucho sentido mantener instalado ipchains cuando no lo vas a utilizar nunca más, desinstálalo de la misma forma:
rpm -e ipchains
Tras todo ésto, habrás finalizado la actualización del paquete iptables mediante el código fuente, habiendo seguido las instrucciones del código. Además ningún viejo ejecutable, librería o fichero auxiliar permanecerá instalado.
En este capítulo hablaremos de cómo atraviesan los paquetes las diferentes cadenas y en qué orden. También analizaremos el orden en que las tablas son atravesadas. Más adelante veremos cuán valioso resulta ésto para escribir nuestras propias reglas. También veremos los momentos en que ciertos componentes, también dependientes del núcleo, entran en escena (por poner un ejemplo, las diferentes decisiones de enrutado). Ésto es especialmente necesario si pretendemos escribir reglas en iptables para poder cambiar las reglas/patrones de enrutado de los paquetes, o sea, si pretendemos conocer el por qué y el cómo los paquetes son enrutados, al hacer por ejemplo DNAT y SNAT. Y por supuesto, no olvidaremos los bits TOS (Type Of Service, o tipo de servicio).
Cuando un paquete entra en el cortafuegos (firewall), alcanza el hardware y es procesado en el núcleo por su driver correspondiente. Después el paquete empieza a recorrer una serie de etapas en el núcleo antes de ser enviado a la aplicación adecuada (localmente), reenviada hacia otro host, o cualquier otra operación.
En primer lugar, echemos un vistazo a un paquete destinado a nuestro propio host local (nuestra máquina). Recorrerá los siguientes pasos antes de ser entregado a la aplicación que lo requiere:
Table 3-1. Host local de destino (nuestra propia máquina)
| Etapa | Tabla | Cadena | Comentario |
|---|---|---|---|
| 1 | En los cables (por ejemplo Internet) | ||
| 2 | Llega a la interfaz de red (por ejemplo eth0) | ||
| 3 | mangle | PREROUTING | Esta cadena se usa normalmente para modificar/"deformar" (mangle) paquetes, es decir, cambiar el TOS y cosas así. |
| 4 | nat | PREROUTING | Esta cadena se usa principalmente para la traducción de direcciones de red de destino (DNAT, Destination Network Address Translation). Debes evitar filtrar en esta cadena ya que será puenteada (bypassed) o esquivada en ciertos casos. |
| 5 | Decisión de enrutamiento, o sea, ¿está el paquete destinado a nuestro host local o debe ser reenviado?, ¿hacia dónde?. | ||
| 6 | mangle | INPUT | En este punto se alcanza la cadena INPUT de la tabla mangle. Usaremos esta cadena para modificar/"retocar" paquetes después de que hayan sido enrutados, pero antes de que se envíen al proceso de destino. |
| 7 | filter | INPUT | Aquí es donde filtraremos todo el tráfico entrante destinado a nuestro host local. Ten en cuenta que todo el tráfico entrante pasa a través de esta cadena, sin importar la interfaz por la que entre o de dónde proceda. |
| 8 | Proceso/aplicación local (es decir, programa cliente/servidor) |
Date cuenta que esta vez el paquete ha atravesado la cadena INPUT en lugar de la cadena FORWARD y ésto es bastante lógico. Lo más probable es que ahora al principio sea lo único que te parecerá realmente lógico acerca de cómo se atraviesan las tablas y cadenas, pero conforme sigas pensando en éllo con el paso del tiempo, lo irás viendo todo aún más claro.
Ahora nos centraremos en los paquetes que salen de nuestro host y las etapas por las que pasan.
Table 3-2. Host local de origen (nuestra própia máquina)
| Etapa | Tabla | Cadena | Comentario |
|---|---|---|---|
| 1 | Proceso/aplicación local (es decir, programa cliente/servidor) | ||
| 2 | Decisión de enrutamiento. Qué dirección de origen usar, qué interfaz de salida usar, y otra información que necesita ser recopilada. | ||
| 3 | mangle | OUTPUT | Aquí es donde se modifican los paquetes; se sugiere que no filtres en esta cadena porque pueden producirse efectos secundarios. |
| 4 | nat | OUTPUT | Esta cadena puede ser usada para hacer NAT a los paquetes que salen desde el firewall. |
| 5 | filter | OUTPUT | Aquí es donde filtramos los paquetes salientes de nuestro host local. |
| 6 | mangle | POSTROUTING | La cadena POSTROUTING de la tabla mangle se usa principalmente cuando queremos modificar los paquetes antes de que dejen nuestro host, pero después de tomar las decisiones de enrutamiento. Esta cadena será alcanzada tanto por los paquetes que atraviesan el cortafuegos, como por los generados por él mismo. |
| 7 | nat | POSTROUTING | Aquí es donde efectuamos la traducción de las direcciones de red de origen (SNAT, Source Network Address Translation) como ya se ha descrito anteriormente. Es conveniente que no filtres en esta cadena ya que pueden producirse efectos secundarios y determinados paquetes podrían colarse incluso aunque se haya establecido la política DROP (desechar) como política por defecto. |
| 8 | Sale al exterior por alguna interfaz (por ejemplo, eth0) | ||
| 9 | En los cables (por ejemplo, Internet) |
En este ejemplo estamos asumiendo que el paquete está destinado a otro host de otra red. El paquete sigue los siguientes pasos, de esta manera:
Table 3-3. Paquetes Reenviados (Forwarded)
| Etapa | Tabla | Cadena | Comentario |
|---|---|---|---|
| 1 | En los cables (es decir, Internet) | ||
| 2 | Llega hasta la interfaz de red (es decir, eth0) | ||
| 3 | mangle | PREROUTING | Esta cadena se usa normalmente para modificar paquetes, o sea, para cambiar el TOS y acciones similares. |
| 4 | nat | PREROUTING | Esta cadena se usa principalmente para hacer DNAT (traducción de dirección de destino). El SNAT (traducción de dirección de origen) se realiza más adelante. Debes evitar filtrar en esta cadena ya que en ciertos casos será puenteada o esquivada. |
| 5 | Decisión de enrutamiento, o sea, ¿el paquete está destinado a nuestro propio host local, o debe ser reenviado?, ¿hacia dónde?. | ||
| 6 | mangle | FORWARD | El paquete es enviado a la cadena FORWARD de la tabla mangle. Ésto puede aprovecharse para necesidades muy específicas dónde queremos modificar paquetes después de la decisión de enrutamiento inicial, pero antes de la última decision de enrutamiento, hecha justo antes de que el paquete sea enviado. |
| 7 | filter | FORWARD | El paquete es enrutado hacia la cadena FORWARD. Solamente los paquetes reenviados pasan por aquí y es donde hacemos todo el filtrado. Ten en cuenta que todos los paquetes reenviados (en cualquier dirección) pasan por aquí, así que necesitarás pensar en éllo cuando escribas tu conjunto de reglas. |
| 8 | mangle | POSTROUTING | Esta cadena se usa para efectuar los tipos específicos de modificación de paquetes (packet mangling) que queramos llevar a cabo después de que todos los tipos de decisiones de enrutamiento se hayan tomado, pero estando el paquete aún en esta máquina. |
| 9 | nat | POSTROUTING | Esta cadena debe ser usada principalmente y sobretodo para efectuar SNAT. Debes evitar filtrar aquí ya que ciertos paquetes podrían "pasar por delante" de la cadena sin ni siquiera rozarla. Aquí es también dónde se realiza el enmascaramiento (Masquerading). |
| 10 | Sale por la interfaz de salida (por ej. eth1). | ||
| 11 | En los cables de nuevo (es decir, la red local). |
Como puedes ver hay bastantes pasos que dar. El paquete puede ser bloqueado en cualquiera de las cadenas de iptables o en algún otro sitio si está malformado; sin embargo, estamos principalmente interesados en la parte relacionada con iptables. Ten en cuenta que no hay cadenas o tablas específicas para interfases diferentes ni nada por el estilo. La cadena FORWARD es siempre atravesada por todos los paquetes que son reenviados a través de este cortafuegos/router.
![]() | ¡No uses la cadena INPUT para filtrar en el escenario anterior! INPUT está pensada sólamente para nuestro host local, no para hacer enrutamientos a ningún otro destino. |
Hemos visto hasta ahora cómo las cadenas son atravesadas en tres escenarios independientes. Si deseáramos hacer un buen esquema de todo ésto, podría asemejarse a algo como:

Para aclarar esta imagen considera lo siguiente: si llega un paquete que no está destinado a nuestra máquina local, en la primera decisión de enrutamiento será dirigido hacia la cadena FORWARD. Por otra parte, si el paquete está destinado a una dirección IP que está siendo escuchada por nuestra máquina local, el paquete sería enviado a la cadena INPUT y después a la máquina local.
También conviene advertir el hecho de que los paquetes pueden estar destinados a la máquina local y, sin embargo, la dirección de destino ser modificada por la cadena PREROUTING haciendo NAT. Dado que ésto tiene lugar antes de la primera decisión de enrutamiento, el paquete será revisado tras dicho cambio. Debido a ésto, el camino a seguir por el paquete (debido a una modificación de la IP de destino) podría cambiar antes de que la decisión de enrutamiento sea tomada. Date cuenta que todos los paquetes irán a través de un camino u otro de esta imagen. Si haces DNAT devolviendo el paquete a la misma red por la que vino, seguirá viajando por el resto de cadenas hasta que regrese a dicha red.
![]() | Si crees que necesitas más información puedes usar el script rc.test-iptables.txt. Este script "de laboratorio" debería proporcionarte las reglas necesarias para comprobar cómo son atravesadas las tablas y las cadenas. |
Esta tabla debe ser principalmente usada para modificar paquetes, como ya hemos dicho antes. En otras palabras, aquí puedes usar libremente las comparaciones de modificación para cambiar el campo TOS (Type Of Service), entre otras cosas.
![]() | Es más que recomendable no usar esta tabla como filtro: ni DNAT, ni SNAT, ni Masquerading trabajan en esta tabla. |
Objetivos para los que únicamente se destina la tabla mangle:
TOS
TTL
MARK
El TOS es usado para definir y/o cambiar el campo Type Of Service del paquete. Puede ser usado para configurar políticas en la red considerando cómo deben ser enrutados los paquetes y tareas similares. Ten en cuenta que éste tema no ha sido perfeccionado y no está realmente implementado en Internet, por lo que la mayoría de los routers no hacen caso del valor de este campo y aún más, a veces actúan de manera imperfecta sobre los paquetes que reciben. En otras palabras, no lo uses para paquetes que vayan hacia Internet, a menos que quieras tomar decisiones de enrutamiento sobre ellos con iproute2.
El TTL es usado para cambiar el campo TTL (Time To Live) de un paquete y con éllo conseguir que los paquetes tengan un TTL específico. Una buena razón para hacerlo podría ser que no queramos ser descubiertos por ciertos proveedores de servicios de internet (ISP) demasiado fisgones: a algunos ISPs no les gustan los clientes que usan multiples ordenadores bajo una única conexión y saben que se trata de una única conexión porque de un mismo host llegan paquetes con diferentes valores de TTL (entre otros muchos signos delatores).
El Mark se usa para para marcar (mark) los paquetes con valores específicos. Estas marcas pueden ser reconocidas posteriormente por los programas iproute2 para realizar diferentes enrutamientos dependiendo de la marca que tengan o no tengan los paquetes. También podemos limitar en ancho de banda y realizar Class Based Queuing (colas basadas en clases, CBQ) según dichas marcas.
Esta tabla debe ser usada sólo para hacer NAT (Network Address Translation) a los diferentes paquetes. En otras palabras, debe ser empleada solamente para traducir el campo origen del paquete o el campo destino. Ten en cuenta que tal como hemos dicho antes, sólo el primer paquete de un flujo alzanzará esta cadena. Después, al resto de paquetes del mismo flujo de datos se les aplicará la misma acción que al primero. Los objetivos que hacen este tipo de cosas son:
DNAT
SNAT
MASQUERADE
El objetivo DNAT (Destination Network Address Translation) se emplea principalmente en los casos donde se tiene una IP pública y se quiere redirigir los accesos al firewall hacia algún otro host (en una "zona desmilitarizada", DMZ, por ejemplo). Dicho de otro modo, cambiamos la dirección de destino del paquete y lo re-enrutamos a otro host.
SNAT (Source Network Address Translation) es principalmente usada para cambiar la dirección de origen de los paquetes. La mayoría de las veces querrás esconder tus redes locales, DMZ, etc. Un ejemplo muy bueno podría ser cuando queremos sustituir las direcciones IP de la red local que está tras el cortafuegos, por la dirección IP del propio cortafuegos, que posee una IP pública hacia fuera. Con este objetivo el firewall automaticamente hará SNAT y de-SNAT sobre los paquetes, lo cual hace posible que las conexiones provenientes de la LAN salgan a Internet. Por ejemplo, si tu red usa 192.168.0.0/máscara_de_red, los paquetes nunca regresarán de Internet, porque IANA ha designado dicho rango de direcciones (entre otras) como privadas y sólo para ser usadas en redes locales aisladas.
El objetivo MASQUERADE se usa exactamente para lo mismo que SNAT, pero MASQUERADE requiere un poquito más de trabajo del procesador. La razón es que cada vez que llega un paquete al objetivo MASQUERADE, automáticamente chequea qué dirección IP debe asignarle, en lugar de hacer como SNAT, que simplemente utiliza la dirección IP configurada. MASQUERADE hace posible trabajar con las direcciones IP Dinámicas por DHCP que tu ISP pueda proporcionarte a través de conexiones a Internet vía PPP, PPPoE o SLIP.
La tabla filter se usa principalmente para el filtrado de paquetes: podemos comparar y filtrar paquetes de la forma que queramos. Se trata del lugar en que miramos el contenido de los paquetes y tomamos la determinación de desecharlos ((DROP)) o aceptarlos ((ACCEPT)). Por supuesto, podemos hacer filtrado antes, pero esta tabla en particular fue diseñada para realizar todas las operaciones de filtrado. Casi todos los objetivos se pueden usar en esta tabla. Trataremos ésto más ampliamente, pero desde ahora ya sabes que esta tabla es el lugar correcto para llevar a cabo el principal filtrado.
Este capítulo trata sobre la máquina de estados y la explica detalladamente, de forma que llegarás a comprender cómo trabaja. También hay una gran cantidad de ejemplos que clarifican cómo son tratados los estados por la máquina de estados: estos ejemplos nos ayudarán a entenderlo todo de la manera más práctica posible.
La máquina de estados es una parte especial de iptables que no debería llamarse así ("máquina de estados"), puesto que en realidad es una máquina de seguimiento de conexiones. Sin embargo, mucha gente la conoce por el primer nombre. A lo largo del capítulo utilizaré ambos nombres como si fueran equivalentes, aunque ésto no debería ocasionar demasiados problemas. El seguimiento de conexiones se efectúa para que la estructura de Netfilter sepa cuál es el estado de cada conexión específica. Los cortafuegos que trabajan de esta manera normalmente se denominan "stateful firewalls" (cortafuegos que consideran las peticiones y respuestas de una máquina como una única conexión de un mismo flujo de datos; podríamos traducirlo como "cortafuegos de flujos"), mientras que los que no lo hacen se denominan "non-stateful firewalls" (cada petición es independiente, aunque provenga de la misma máquina; podríamos traducirlo como "cortafuegos de conexiones"). Los cortafuegos de tipo "stateful" son mucho más seguros, pues nos permiten elaborar conjuntos de reglas mucho más precisos.
En iptables los paquetes se pueden relacionar con las conexiones mediante cuatro "estados" diferentes: NEW (nuevo), ESTABLISHED (establecido), RELATED (relacionado) e INVALID (inválido o no válido). Los trataremos en profundidad más adelante. Mediante la comparación --state podemos controlar fácilmente qué o quién tiene permiso para iniciar nuevas sesiones.
Todo el seguimiento de las conexiones la realiza una estructura especial del núcleo llamada "conntrack". El "conntrack" puede cargarse como módulo o como una parte más del núcleo, pero sea como sea la mayoría de las veces necesitaremos y pediremos un seguimiento de conexiones más específico que el que proporciona por defecto el motor del conntrack. Por éllo, hay unas partes específicas que se encargan de los protocolos TCP, UDP o ICMP, entre otros. Estos módulos captan información específica, única, de los paquetes, de forma que pueden mantener un seguimiento de cada flujo de datos. La información que recopilan los módulos se utiliza para indicarle al conntrack en qué estado se encuentra el flujo en cada momento. Por ejemplo, los flujos UDP normalmente se identifican y distinguen por su dirección IP de destino, su dirección IP de origen, su puerto de destino y su puerto de origen.
En anteriores núcleos teníamos la posibilidad de ejecutar o parar la desfragmentación de paquetes, sin embargo, desde que se introdujeron iptables y Netfilter y en particular desde que se introdujo el seguimiento de conexiones, esta opción fue eliminada. La razón es que el seguimiento de conexiones no funciona correctamente sin defragmentar los paquetes, por lo que se ha introducido en el conntrack y el proceso se realiza automáticamente. No se puede parar si no paramos también el seguimiento de conexiones. En otras palabras, desde el momento en que se emplea el seguimiento de conexiones, se emplea la desfragmentación de paquetes.
Todo el seguimiento de conexiones se efectúa en la cadena PREROUTING, excepto los paquetes generados localmente, que son controlados en la cadena OUTPUT. Ésto significa que iptables realizará todo el recálculo de estados en estas dos cadenas de la tabla Nat: si somos nosotros los que enviamos el paquete inicial del flujo, su estado se establece como NEW (nuevo) en la cadena OUTPUT, mientras que al recibir el paquete de retorno (el que "contesta" al paquete inicial) el estado del flujo se cambia a ESTABLISHED (establecido) en la cadena PREROUTING. Por el contrario, si el primer paquete no lo hemos originado nosotros, el estado NEW se establece en la cadena PREROUTING.
Entendamos como "entrada" el registro que mantiene el conntrack de cada conexión y toda la información necesaria correspondiente a esa conexión. Así pues, veamos una entrada del conntrack tal como la podríamos encontrar en /proc/net/ip_conntrack y aprendamos a interpretarla (en este fichero encontraremos un listado de todas las entradas presentes en la base de datos del conntrack). Si tienes cargado el módulo ip_conntrack, un "cat" de /proc/net/ip_conntrack podría parecerse a:
tcp 6 117 SYN_SENT src=192.168.1.6 dst=192.168.1.9 sport=32775 \
dport=22 [UNREPLIED] src=192.168.1.9 dst=192.168.1.6 sport=22 \
dport=32775 use=2
Este ejemplo contiene toda la información que el módulo conntrack mantiene para saber en qué estado se encuentra una conexión determinada. Para empezar tenemos el protocolo, que en este caso es el tcp. A continuación tenemos la misma información en su notación decimal equivalente ("6"). Después viene el tiempo de vida restante para esta entrada en particular: en el ejemplo a esta conexión le quedan 117 segundos, que irán disminuyendo hasta que se vea más tráfico perteneciente a la conexión. Cuando llega ese nuevo tráfico el contador se establece de nuevo a su valor por defecto (valor que depende del estado en que se encuentra la conexión en el momento de recibir nuevos paquetes). Sigue la información del estado actual en que se encuentra la entrada. En el ejemplo estamos viendo un paquete que se encuentra en el estado SYN_SENT. El valor interno de una conexión es ligeramente distinto a los empleados externamente por iptables. El valor SYN_SENT nos dice que tenemos delante una conexión que sólo a visto un paquete TCP SYN en una dirección. Después vemos la dirección IP de origen, la dirección IP de destino, el puerto de origen y el puerto de destino. En este punto vemos una clave específica que nos indica que no hemos visto ningún tráfico de retorno para esta conexión. Para terminar, vemos los datos que esperamos en los paquetes de retorno: las direcciones de origen y destino (que están invertidas respecto a las anteriores, ya que el paquete será devuelto hacia nosotros) y los puertos de origen y destino (también invertidos respecto a los anteriores). Así pues, éstos son los valores que podrían ser de nuestro interés.
Las entradas del seguimiento de conexiones pueden tomar diferentes valores, todos éllos especificados en las cabeceras ("headers") disponibles en los archivos linux/include/netfilter-ipv4/ip_conntrack*.h. Estos valores dependen del subprotocolo IP que estemos usando. Los protocolos TCP, UDP o ICMP toman los valores por defecto especificados en linux/include/netfilter-ipv4/ip_conntrack.h. Lo estudiaremos en detalle al revisar cada protocolo, sin embargo, no ahondaremos demasiado en este capítulo puesto que sólo se utilizan dentro del conntrack. Por otra parte, según cambia el estado también cambia el valor por defecto del tiempo que debe transcurrir hasta eliminar la conexión (el "contador de vida restante de la conexión" explicado anteriormente).
![]() | Desde hace poco hay un nuevo parche en el "patch-o-matic" de iptables llamado "tcp-window-tracking". Este parche añade, entre otras cosas, todos los tiempos límite mencionados a variables especiales del "sysctl", con lo que se pueden cambiar (los tiempos) al instante, mientras el sistema está en marcha. Gracias a ésto ya no es necesario recompilar el núcleo cada vez que se deseen cambiar los límites de los contadores de tiempo. Estos límites se pueden modificar utilizando llamadas al sistema específicas que se encuentran disponibles en el directorio /proc/sys/net/ipv4/netfilter. Concretamente debes buscar las variables /proc/sys/net/ipv4/netfilter/ip_ct_*. |
Cuando una conexión ha visto tráfico en ambas direcciones, se modificará su entrada del conntrack eliminando la clave [UNREPLIED] (que nos indica que la conexión no ha visto tráfico en ambas direcciones) y añadiendo (casi al final de la entrada) la clave [ASSURED]. Esta clave nos dice que la conexión está asegurada y por éllo no será eliminada si alcanzamos el límite máximo de conexiones simultáneas soportadas por el conntrack, todo lo contrario a aquellas que no están marcadas como [ASSURED], que sí serán borradas al alcanzar el límite de conexiones. El número de conexiones mantenidas en la tabla del seguimiento de conexiones depende de una variable, que puede establecerse a través de las funciones de ip-sysctl en los núcleos Linux más recientes. El valor por defecto de esta variable depende en gran medida de la cantidad de memoria que tengas instalada: para 128 MB de RAM tendrás un máximo de 8192 entradas, mientras que con 256 MB de RAM llegarás hasta las 16376 entradas. Puedes ver y modificar estos valores a través de la propiedad /proc/sys/net/ipv4/ip_conntrack_max.
Como has podido ver, los paquetes pueden tomar distintos estados dentro del núcleo, dependiendo del protocolo considerado. Sin embargo, fuera del núcleo sólo tenemos los cuatro estados descritos anteriormente. Principalmente estos estados se pueden emplear junto a la comparación de estados (state match), con lo cual esta comparación será capaz de diferenciar paquetes en función del estado en que se encuentren dentro del seguimiento de conexiones. Los estados válidos son: NEW, ESTABLISHED, RELATED e INVALID. La siguiente tabla explica brevemente cada posible estado:
Table 4-1. Estados de espacio de usuario
| Estado | Explicación |
|---|---|
| NEW | El estado NEW (nuevo) nos indica que el paquete es el primero que vemos. Esto significa que el primer paquete que el módulo conntrack vea en una conexión será etiquetado de esta manera. Por ejemplo, si vemos un paquete SYN que además es el primero de una conexión, coincidirá con el criterio del conntrack y será etiquetado como "nuevo". Sin embargo, el primer paquete puede que no sea un paquete SYN y aún así ser considerado como NEW. Este comportamiento puede llevar a determinados problemas en determinados casos, pero también puede ser extremadamente útil si necesitamos captar conexiones perdidas de otros cortafuegos, o si una conexión a excedido su tiempo de espera, pero en realidad no ha sido cerrada. |
| ESTABLISHED | El estado "ESTABLISHED" (establecido) ha visto tráfico en ambas direcciones y por tanto admitirá continuamente los paquetes de ese flujo. Las conexiones "establecidas" son bastante fáciles de comprender: el único requisito para alcanzar el estado "ESTABLISHED" es que un host envíe un paquete y obtenga una respuesta del otro host. El estado "NEW" (nuevo) cambiará al estado "establecido" en cuanto llegue un paquete de respuesta al cortafuegos (o cuando este paquete pase por el cortafuegos). Los mensajes de error ICMP, las redirecciones, ..., también se pueden considerar como "ESTABLISHED", si hemos enviado un paquete que a su vez genera el mensaje de error ICMP. |
| RELATED | El estado "RELATED" (relacionado) es uno de los más complejos. Una conexión se considera "relacionada" cuando está ligada a otra conexión ya "establecida". Por este motivo, para que una conexión se considere en estado "RELATED" primero debemos tener otra conexión en estado "ESTABLISHED": la conexión "establecida" generará una conexión externa a la conexión principal, y esta nueva conexión será considerada como "relacionada" siempre que el módulo conntrack pueda entender que está relacionada con la principal. Un buen ejemplo: las conexiones FTP-data son consideradas como relacionadas con el puerto de control FTP (FTP control); otro ejemplo son las conexiones DCC generadas con el IRC. Puede utilizarse para permitir las respuestas ICMP, las transferencias FTP y los DCCs (protocolos de conexión Directa de Cliente a Cliente) a través del cortafuegos. Ten en cuenta que muchos protocolos TCP (además de algunos UDP) que dependen de este mecanismo son bastante complejos y envían información de la conexión conjuntamente con la carga de datos de los segmentos TCP o UDP, por lo que requieren de módulos de ayuda especiales para ser correctamente interpretados. |
| INVALID | El estado "INVALID" (inválido) implica que el paquete no puede ser identificado o que no tiene ningún estado. Ésto puede ser debido a varias razones, como que el sistema se ha quedado sin memoria disponible, o a mensajes ICMP de error que no responden a ninguna conexión conocida. Normalmente es una buena idea eliminar (DROP) todo aquello que se encuentre en este estado. |
Los anteriores estados pueden usarse conjuntamente con la comparación --state para diferenciar paquetes en función de su estado en el seguimiento de conexiones. Ésto es lo que hace que la máquina de estados sea tan increíblemente fuerte y eficiente para nuestro cortafuegos. En el pasado, frecuentemente nos veíamos obligados a abrir todos los puertos por encima del 1024 para permitir el tráfico de retorno a nuestra red local. Con la máquina de estados funcionando ya no es necesario, pues podemos abrir el cortafuegos sólo para el tráfico de retorno, no para todo tipo de tráfico.
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
| Estado | Tiempo límite |
|---|---|
| NONE | 30 minutos |
| ESTABLISHED | 5 días |
| SYN_SENT | 2 minutos |
| SYN_RECV | 60 segundos |
| FIN_WAIT | 2 minutos |
| TIME_WAIT | 2 minutos |
| CLOSE | 10 segundos |
| CLOSE_WAIT | 12 horas |
| LAST_ACK | 30 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.
![]() | 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. |
Las conexiones UDP son en sí mismas "conexiones sin flujo". Existen varias razones para éllo, principalmente porque no implican ningún establecimiento o cierre de conexión; más que nada les falta algún tipo de secuenciamiento: recibir dos datagramas UDP en un orden específico no dice nada acerca del orden en que fueron enviados. Sin embargo es posible establecer estados en las conexiones dentro del núcleo. Veamos cómo se puede seguir una conexión y cómo podría verse en el conntrack.

Como puedes observar, desde el punto de vista del espacio de usuario la conexión se inicia casi exactamente de la misma forma que una conexión TCP. Internamente, sin embargo, la información del conntrack es bastante diferente, pero intrínsecamente los detalles son los mismos. Para empezar, veamos la entrada tras haber enviado el paquete UDP inicial.
udp 17 20 src=192.168.1.2 dst=192.168.1.5 sport=137 dport=1025 \
[UNREPLIED] src=192.168.1.5 dst=192.168.1.2 sport=1025 \
dport=137 use=1
Como puedes ver en los dos primeros valores, se trata de un paquete UDP. El primer valor es el nombre del protocolo, mientras que el segundo es el número del protocolo. Es lo mismo que se encuentra en las conexiones TCP. El tercer valor indica el "tiempo de vida" en segundos que le queda a esta entrada de estado. Tras ésto vienen los valores de origen y destino del paquete que hemos visto, la bandera [UNREPLIED], que nos indica que hasta el momento no ha habido respuesta al paquete, y por fin un listado de los valores que se esperan en los paquetes de respuesta. Estos últimos valores son los mismos que antes pero en sentido inverso. El tiempo de vida ("timeout") por defecto es de 30 segundos.
udp 17 170 src=192.168.1.2 dst=192.168.1.5 sport=137 \
dport=1025 src=192.168.1.5 dst=192.168.1.2 sport=1025 \
dport=137 use=1
En este ejemplo el servidor ya ha visto una respuesta al paquete inicial y la conexión se considera ESTABLISHED (establecida). Este detalle no se muestra en el seguimiento de conexiones, como puedes ver. La diferencia principal es la ausencia de la bandera [UNREPLIED]. Además el tiempo de vida por defecto a cambiado a 180 segundos: en el ejemplo ya han pasado 10 segundos y por eso el valor es 170; en diez segundos más el valor disminuirá a 160 y así hasta que se agote el tiempo. Sin embargo, hay un detalle que se ha visto antes y que ahora falta: la bandera [ASSURED]. Para que se establezca esta bandera en el seguimiento de conexiones, debe haber habido un mínimo de tráfico en la conexión.
udp 17 175 src=192.168.1.5 dst=195.22.79.2 sport=1025 \
dport=53 src=195.22.79.2 dst=192.168.1.5 sport=53 \
dport=1025 [ASSURED] use=1
En este momento la conexión ya está asegurada. Este ejemplo parece exactamente igual que el anterior, excepto por la bandera [ASSURED]. Si la conexión no es utilizada en 180 segundos, entonces caduca. Este tiempo (180 segundos) es un valor relativamente bajo, pero debería ser suficiente para la mayoría de los casos. Cada vez que un paquete coincide con los valores indicados en el paquete inicial (los valores que se esperan de los paquetes de respuesta ya comentados antes) y atraviesa el cortafuegos, el contador vuelve al valor por defecto, igual que ocurre con el resto de estados internos.
Los paquetes ICMP no pueden estar más lejos de ser conexiones de flujo (o "stateful connections"), ya que sólo se usan para el control de las conexiones y nunca deberían establecer ninguna conexión por sí mismos. Sin embargo, hay cuatro tipos ICMP que generarán paquetes de retorno y que presentan 2 estados diferentes: estos mensajes ICMP pueden tomar los estados NEW (nuevo) y ESTABLISHED (establecido). Los tipos ICMP de los que hablamos son: Echo request (petición de eco) y su reply (respuesta o Echo reply), Timestamp request (petición de "marca de tiempo", el valor del momento exacto en que se envía el paquete) y su reply, Information request (petición de información) y su reply, y por último Address mask request (petición de máscara de subred) y su reply. De éstos, los tipos timestamp request e information request están obsoletos y lo más probable es que simplemente se puedan desechar. Sin embargo, los mensajes de Echo se emplean en varias configuraciones, como hacer "pings" a los hosts. Las peticiones de máscara de subred (Address mask requests) no se suelen utilizar, pero pueden ser útiles en ocasiones y vale la pena permitirles que atraviesen el cortafuegos. Para tener una idea de cómo funciona una conexión de este tipo, veamos la siguiente imagen:

Como puedes observar, el host envía una petición de eco (echo request) al destinatario y el cortafuegos considera el paquete como nuevo (NEW). El destinatario responde con una respuesta de eco (echo reply) que el cortafuegos considera mediante el estado "establecido" (ESTABLISHED). Cuando ya se ha visto la primera petición de eco, se crea la siguiente entrada en el ip_conntrack.
icmp 1 25 src=192.168.1.6 dst=192.168.1.10 type=8 code=0 \
id=33029 [UNREPLIED] src=192.168.1.10 dst=192.168.1.6 \
type=0 code=0 id=33029 use=1
Como ya te habrás dado cuenta, esta entrada es un poco diferente a las entradas estándar para los paquetes TCP y UDP. Vemos el protocolo, su número, el tiempo de vida que le queda a la entrada, así como las direcciones de origen y destino. La diferencia viene después: tenemos 3 campos nuevos llamados type (tipo), code (código) e id (identidad/identificación). En realidad no son nada especiales: el valor type indica el tipo ICMP y el valor code indica el código ICMP. Todos éllos están disponibles en el apéndice Tipos ICMP. El valor de id indica el ICMP ID: cada paquete ICMP toma un valor ID (de identificación) cuando se envía y al responder el receptor también establece este mismo ID; de esta forma al llegar la respuesta al host que envió la petición se puede unir esa respuesta a la petición ICMP correcta.
En el siguiente campo volvemos a encontrar la bandera [UNREPLIED] que hemos visto anteriormente. Como entonces, la entrada del seguimiento de conexiones que estamos viendo implica que sólo se ha visto tráfico en una dirección. Por último vemos los datos que se esperan en el paquete de respuesta ICMP, que obviamente son los valores iniciales pero invertidos. Por lo que respecta al tipo y al código, se cambian por los valores adecuados al paquete de retorno, de forma que una petición de eco se transforma en una respuesta de eco, etc. El ICMP ID, lógicamente, se mantiene idéntico al paquete de petición.
El paquete de respuesta se considera como ESTABLISHED, tal como ya se ha explicado. Sin embargo, sabemos con seguridad que tras la respuesta ICMP no habrá absolutamente ningún tráfico legal en la misma conexión. Por esta razón la entrada del seguimiento de conexiones es destruida en cuanto la respuesta ha atravesado la estructura de Netfilter.
En cada uno de los casos anteriores la petición se considera como nueva (NEW), mientras que la respuesta se considera como establecida (ESTABLISHED). Más exactamente, cuando el cortafuegos ve un paquete de petición lo considera como nuevo, mientras que cuando el host envía un paquete de respuesta a la petición, se considera como establecido.
![]() | Ésto implica que el paquete de respuesta debe seguir el criterio fijado en la entrada del seguimiento de conexiones, para poder ser considerado como establecido, de la misma manera que ocurre en el resto de tipos de tráfico. |
Las peticiones ICMP tienen un tiempo de vida por defecto de 30 segundos, que puedes cambiar en la entrada /proc/sys/net/ipv4/netfilter/ip_ct_icmp_timeout. En general éste es un buen valor, ya que permitirá captar la mayoría de paquetes en tránsito.
Otra parte importantísima de ICMP es el hecho de que se emplea para indicarle a los hosts qué ha pasado con las conexiones UDP y TCP o con los intentos de conexión. Por este motivo las respuestas ICMP muy a menudo serán reconocidas como relacionadas (RELATED) con conexiones o intentos de conexión. Un ejemplo sencillo sería el de los mensajes ICMP Host unreachable (mensaje de host inalcanzable, no se puede llegar al host) e ICMP Network unreachable (mensaje de red inalcanzable, no se puede llegar hasta la red). Este tipo de mensajes se envían automáticamente de vuelta a nuestro host cuando el paquete no consigue efectuar la conexión con un host porque la red o el host de destino están fuera de servicio, de manera que el último enrutador (router) que intente conectar con el destino nos contestará con un mensaje ICMP diciéndonos lo que ocurre. En este caso la respuesta ICMP se considera como un paquete relacionado (RELATED). El siguiente gráfico debería aclarar lo que ocurre:

En el ejemplo enviamos un paquete SYN a una dirección concreta: ésto es considerado como una nueva conexión (NEW) por el cortafuegos. Sin embargo no se puede llegar a la red a la que se está intentando enviar el paquete, por lo que un router (el último) nos devuelve un error ICMP de red inalcanzable. El código de seguimiento de conexiones puede reconocer este paquete como relacionado (RELATED) gracias a la entrada ya existente, por lo que la respuesta ICMP es correctamente enviada al cliente que generó el paquete inicial y, si todo va bien, éste abortará la conexión. Mientras tanto el cortafuegos ya habrá eliminado la entrada del seguimiento de conexiones, puesto que sabe que se trata de un mensaje de error.
El mismo comportamiento se experimenta con las conexiones UDP si tienen problemas como en el caso anterior. Todo mensaje ICMP devuelto como respuesta a conexiones UDP es considerado como relacionado (RELATED). Observa el siguiente gráfico:

En esta ocasión se envía un paquete UDP al host. Esta conexión UDP se considera como nueva NEW. Sin embargo la red está prohibida administrativamente por algún cortafuegos o router antes de llegar a destino y por éllo nuestro cortafuegos recibe un mensaje ICMP Network Prohibited (red prohibida). El cortafuegos sabe que este mensaje ICMP de error está relacionado con la conexión UDP abierta y lo envía como un paquete RELATED (relacionado) al cliente. Acto seguido, elimina la entrada del seguimiento de conexiones mientras el cliente recibe el mensaje y aborta la conexión.
En algunos casos el conntrack no sabe cómo gestionar un protocolo específico, bien sea porque no conoce el protocolo o porque no sabe cómo funciona. En estos casos sigue un comportamiento por defecto, como ocurre por ejemplo con NETBLT, MUX y EGP. El procedimiento seguido es básicamente el mismo que en el seguimiento de las conexiones UDP: el primer paquete se considera como nuevo (NEW) y el tráfico de respuesta y subsiguiente es considerado como establecido (ESTABLISHED).
Cuando se emplea el comportamiento por defecto todos los paquetes afectados tendrán el mismo "tiempo de vida" (timeout) por defecto, que se puede establecer a través de la variable /proc/sys/net/ipv4/netfilter/ip_ct_generic_timeout. El valor por defecto es de 600 segundos (10 minutos). En función del tipo de tráfico que estés intentando enviar a través de un enlace que utilice este comportamiento por defecto, es posible que necesites cambiarlo, especialmente si estás conectado a través de satélite o algo similar, pues la comunicación puede llegar a tardar bastante tiempo.
Determinados protocolos son más complejos que otros, lo cual implica que a la hora de realizar el seguimiento de conexiones el trabajo será más difícil. Buenos ejemplos de éllo son los protocolos ICQ, IRC y FTP. Cada uno de éllos incluye información dentro del bloque de datos de los paquetes, por lo que para funcionar correctamente requieren de asistentes especiales para el seguimiento de conexiones.
Empecemos por ejemplo con el procolo FTP. Este protocolo comienza abriendo una única conexión llamada sesión "FTP control" (sesión de control ftp). En cuanto enviamos comandos a través de esta sesión, se abren otros puertos para transportar el resto de datos relacionados con esos comandos. Estas conexiones se pueden crear de dos maneras: activamente o pasivamente. Cuando una conexión se genera activamente, el cliente FTP envía al servidor un puerto y una dirección IP a los que conectarse. Tras ésto el cliente FTP abre el puerto indicado y el servidor conecta con él desde su propio puerto 20 (conocido como "FTP-Data"), enviando datos a través de él.
El problema radica en que el cortafuegos no tiene conocimiento de esas conexiones extras, ya que son negociadas a través del bloque de datos que transporta el paquete (lo que se conoce como "payload" o carga de información; recordemos que a grosso modo un paquete IP tiene una cabecera con información sobre la conexión y un bloque de datos que es lo que desea el host que lo recibe). Debido a ésto el cortafuegos no será capaz de saber que debería dejar conectarse al servidor con el cliente a través de estos puertos concretos.
La solución al problema pasa por añadir un asistente especial al módulo del seguimiento de conexiones, que escaneará los datos de la conexión de control para detectar sintaxis e información específicas: cuando encuentre la información adecuada, la añadirá en una nueva entrada etiquetada como relacionada (RELATED) y gracias a esta nueva entrada el servidor ya será capaz de efectuar un seguimiento de la conexión. Observa la siguiente imagen para entender los estados cuando el servidor FTP ya ha creado la conexión con el cliente.

El ftp pasivo ("Passive FTP") funciona completamente a la inversa: el cliente FTP le indica al servidor que quiere determinados datos, a lo que el servidor responde con una dirección IP y un puerto a los que conectarse. Tras recibir estos datos, el cliente se conectará a ese puerto desde su propio puerto 20 (el puerto FTP-data) y cogerá los datos en cuestión. Si tienes un servidor FTP tras tu cortafuegos, necesitarás de este módulo además de los módulos estándar de iptables para permitir que los clientes desde Internet puedan conectar correctamente con tu servidor FTP. De igual forma necesitarás el módulo si eres extremadamente restrictivo con tus usuarios y sólo quieres dejar que utilicen servidores HTTP y FTP de Internet, bloqueando cualquier otro puerto. Observa la siguiente imagen y su relación con el ftp pasivo ("Passive FTP").

Algunos asistentes del conntrack ya están disponibles en el núcleo, más concretamente, en el momento de escribir estas líneas los protocolos FTP e IRC ya disponen de estos asistentes. Si no puedes encontrar en el núcleo los asistentes que necesitas, debes echar un vistazo al patch-o-matic, en la sección de zona de usuario de iptables: podrás encontrar más asistentes del conntrack, como es el caso de los protocolos ntalk o H.323. Si no están disponibles en el patch-o-matic, todavía dispones de algunas opciones más, como el CVS ("Concurrent Versioning System") de iptables, si recientemente a entrado en él. O también puedes ponerte en contacto con la lista de correo Netfilter-devel y preguntar si está disponible. Si no lo está y no está planeado añadirlo, te quedas solo y lo más probable es que quieras leer el "CÓMO" de Rusty Russell: Rusty Russell's Unreliable Netfilter Hacking HOW-TO (en inglés), del cual tienes un enlace en el apéndice Otras fuentes y enlaces.
Los asistentes del conntrack pueden ser compilados estáticamente o como módulos en el núcleo. Si se compilan como módulos puedes cargarlos con el siguiente comando:
modprobe ip_conntrack_*
Ten en cuenta que el seguimiento de conexiones no tiene nada que ver con la traducción de direcciones (NAT), por lo que es posible que necesites más módulos si también estás "NATeando" (traduciendo) las conexiones. Por ejemplo, si quisieras traducir las direcciones y realizar un seguimiento de las conexiones FTP, necesitarías también el módulo NAT. Todos los asistentes NAT tienen el mismo prefijo en su nombre: "ip_nat_", seguido por el nombre del asistente en cuestión. Por ejemplo, el asistente para FTP con NAT (traducción de direcciones) se llamaría ip_nat_ftp y el módulo IRC se llamaría ip_nat_irc. Los asistentes del conntrack siguen la misma regla de nomenclatura, por lo que el asistente del conntrack para IRC se llamaría ip_conntrack_irc, mientras que el asistente para FTP sería ip_conntrack_ftp.
El paquete iptables tiene dos herramientas muy útiles, especialmente cuando te enfrentas a conjuntos de reglas muy grandes. Estas herramientas son iptables-save e iptables-restore, y se usan respectivamente para salvar y restaurar conjuntos de reglas en un formato de fichero específico, que parece bastante diferente del código del intérprete de comandos (shell) estándar que puedes ver en el resto del tutorial.
Una de las razones más convincentes para utilizar los comandos iptables-save e iptables-restore es que aceleran considerablemente la carga y la copia de los conjuntos de reglas más grandes. El principal problema al ejecutar un script en la línea de comandos que contenga reglas de iptables, es que cada vez que se invoca a iptables en el script se procede en primer lugar a extraer el conjunto de reglas completo del espacio del núcleo de Netfilter, para a continuación insertar, añadir o efectuar el cambio que sea necesario por el comando específico. Por último, devolverá el conjunto de reglas desde su propia memoria al espacio del núcleo. Utilizando un script, estas operaciones se ejecutan en cada una de las reglas que queramos insertar y consecuentemente cada vez cuesta más extraer e insertar el conjunto de reglas, pues cada vez hay más reglas en el conjunto.
Para resolver este problema se han creado los comandos iptables-save e iptables-restore. El primero (iptables-save) se usa para guardar el conjunto de reglas en un fichero de texto con un formato especial, mientras que iptables-restore carga ese fichero de texto de nuevo en el núcleo. Lo mejor de todo ésto es que cargan y guardan el conjunto de reglas mediante una sóla petición: iptables-save captará el conjunto de reglas completo del núcleo y lo guardará en un fichero en un sólo paso; iptables-restore cargará en el núcleo un conjunto de reglas específico en un paso por cada tabla. O sea, en vez de copiar el conjunto de reglas desde el núcleo, que en un ejemplo grande podría significar repetir el proceso 30.000 veces (si hubieran 30.000 reglas) y después volverlo a cargar en el núcleo otras tantas veces, podremos copiar desde el núcleo de una vez y volver a cargar en el núcleo en 3 pasos (suponiendo que hayan sólo 3 tablas).
Como puedes comprender, estas herramientas son exactamente lo que necesitas si estás trabajando con una gran cantidad de reglas y debes insertarlas en el conjunto de reglas. Sin embargo tienen sus inconvenientes, como discutiremos a continuación.
Es posible que te estés preguntando si iptables-restore soporta de alguna manera los scripts. Y la respuesta es que de momento no, y lo más probable es que nunca lo haga. Este es el principal problema derivado del uso de iptables-restore, puesto que no podrás hacer una gran cantidad de tareas con estos ficheros. Por ejemplo: ¿qué ocurre cuando tienes una conexión a la que se le asigna dinamicamente su dirección IP y quieres captar esta dirección cada vez que el ordenador arranca para utilizarla en tus scripts? Con iptables-restore, ésto es prácticamente imposible.
Una posibilidad de conseguirlo es crear un pequeño script que primero capte los valores que quieres usar en las reglas, después busque ciertas claves en el fichero creado por iptables-save y las sustituya por los valores captados anteriormente. Una vez llegados a este punto, puedes salvar el fichero modificado en un fichero temporal y utilizar el comando iptables-restore para cargar los nuevos valores. Sin embargo ésta manera de proceder causa un montón de problemas y no podrás utilizar correctamente iptables-save, pues probablemente eliminará los valores añadidos manualmente (por el script). En definitiva es una solución, cuando menos, poco acertada.
Otra posible solución sería cargar los ficheros con iptables-restore y a continuación ejecutar un script que inserte reglas más dinámicas en los lugares adecuados. Por descontado, esta manera de proceder es tan poco acertada como la anterior. La cuestión de fondo es que iptables-restore no está adaptado para aquellas configuraciones dónde las direcciones IP se asignan al cortafuegos dinámicamente, o dónde deseas diferentes comportamientos dependiendo de los valores de determinadas opciones de configuración.
Otro inconveniente de iptables-restore e iptables-save es que de momento no son plenamente funcionales. El problema radica en los pocos usuarios que utilizan estos comandos y por tanto son pocos los que buscan fallos, de forma que es posible que algunas comparaciones y objetivos se inserten mal, y ésto lleve a comportamientos extraños o no esperados. A pesar de todo, aunque existen estos problemas, recomendaría encarecidamente utilizar estas herramientas, ya que deberían funcionar perfectamente bien para la mayoría de conjuntos de reglas, siempre que no contengan algunos de los nuevos objetivos o comparaciones que no sabe cómo manejar convenientemente. En un par de frases: si tus conjuntos de reglas no están absolutamente "a la última", estas herramientas funcionan como caídas del cielo.
El comando iptables-save es una herramienta para guardar el conjunto de reglas existente en iptables a un fichero que puede utilizar iptables-restore. Este comando es bastante fácil de usar y sólo tiene dos argumentos. Échale un vistazo al siguiente ejemplo para entender la sintaxis apropiada del comando.
iptables-save [-c] [-t tabla]
La opción -c le indica a iptables-save que guarde también los valores existentes en los contadores de bytes y de paquetes. Ésto puede ser útil si queremos reiniciar el cortafuegos sin perder los valores de estos contadores, que servirán, por ejemplo, para continuar con nuestras rutinas estadísticas sin problemas. Por supuesto el valor por defecto es no conservar los datos de los contadores.
La opción -t indica a iptables-save qué tablas guardar. Sin este argumento el comando guardará en el fichero todas las tablas disponibles. A continuación tienes un ejemplo de la salida que puedes esperar del comando iptables-save si no tienes ningún conjunto de reglas cargado (lógicamente la salida es en inglés).
# Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:17 2002 *filter :INPUT ACCEPT [404:19766] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [530:43376] COMMIT # Completed on Wed Apr 24 10:19:17 2002 # Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:17 2002 *mangle :PREROUTING ACCEPT [451:22060] :INPUT ACCEPT [451:22060] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [594:47151] :POSTROUTING ACCEPT [594:47151] COMMIT # Completed on Wed Apr 24 10:19:17 2002 # Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:17 2002 *nat :PREROUTING ACCEPT [0:0] :POSTROUTING ACCEPT [3:450] :OUTPUT ACCEPT [3:450] COMMIT # Completed on Wed Apr 24 10:19:17 2002
Como puedes ver los comentarios comienzan con el signo #. Cada tabla se marca así: *<nombre-tabla>, como por ejemplo *mangle. A continuación, dentro de cada tabla se encuentran las especificaciones de las cadenas y las reglas. Una especificación de cadena es similar a: :<nombre-cadena> <política-cadena> [<contador-paquetes>:<contador-bytes>]. El nombre de cadena puede ser, por ej., PREROUTING, la política ya se ha descrito antes y puede ser ACCEPT. Por otra parte, los contadores de paquetes y de bytes son los mismos que obtenemos con iptables -L -v. Por último, cada declaración de tabla finaliza con la clave COMMIT. Esta clave nos indica que en ese punto debemos enviar al núcleo todas las reglas que se han ido leyendo.
El ejemplo anterior es bastante básico y por éllo considero que es apropiado mostrar un breve ejemplo que contiene un pequeño Iptables-save. Si quisiéramos ejecutar iptables-save con éste conjunto de reglas cargado en el núcleo, la salida sería algo similar a (otra vez, en inglés):
# Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002 *filter :INPUT DROP [1:229] :FORWARD DROP [0:0] :OUTPUT DROP [0:0] -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT -A FORWARD -i eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT -A FORWARD -i eth1 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -A OUTPUT -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT COMMIT # Completed on Wed Apr 24 10:19:55 2002 # Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002 *mangle :PREROUTING ACCEPT [658:32445] :INPUT ACCEPT [658:32445] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [891:68234] :POSTROUTING ACCEPT [891:68234] COMMIT # Completed on Wed Apr 24 10:19:55 2002 # Generated by iptables-save v1.2.6a on Wed Apr 24 10:19:55 2002 *nat :PREROUTING ACCEPT [1:229] :POSTROUTING ACCEPT [3:450] :OUTPUT ACCEPT [3:450] -A POSTROUTING -o eth0 -j SNAT --to-source 195.233.192.1 COMMIT # Completed on Wed Apr 24 10:19:55 2002
Como puedes ver, a cada comando se le ha añadido como prefijo los contadores de bytes y paquetes, puesto que se ha empleado la opción -c en el comando. Excepto éste detalle, la línea de comandos es casi igual al script. El único problema ahora es cómo guardar la salida del comando en un fichero, aunque la solución es bastante simple y ya deberías conocerla si has trabajado con Linux antes: sólo se trata de dirigir la salida del comando hacia un fichero con el nombre que desees. Ésto puede parecerse a:
iptables-save -c > /etc/iptables-save
El comando anterior guardará el conjunto de reglas con los valores de sus contadores en un fichero llamado /etc/iptables-save.
El comando iptables-restore se emplea para volver a cargar en el núcleo el conjunto de reglas guardado con iptables-save. Sin embargo, por ahora carga toda la información desde la entrada estándar y no desde un fichero. La sintaxis es:
iptables-restore [-c] [-n]
El argumento -c reestablece los contadores de bytes y paquetes y es la opción que debes usar cuando quieras volver a cargar los valores guardados con iptables-save de estos contadores. La opción también puede escribirse en su forma extendida: --counters.
El argumento -n le indica a iptables-restore que no sobreescriba las reglas existentes en la tabla o tablas en que esté escribiendo. El comportamiento por defecto de iptables-restore es eliminar cualquier regla preexistente. La opción en su "versión larga" sería: --noflush.
Para cargar conjuntos de reglas con el comando iptables-restore tenemos varias alternativas, aunque veremos la más simple y más utilizada.
cat /etc/iptables-save | iptables-restore -c
Simplificando: con éllo imprimiremos en la salida estándar (la pantalla) el contenido del conjunto de reglas existente en el fichero/etc/iptables-save y a continuación esa salida se dirigirá al comando iptables-restore, que captará el conjunto de reglas y lo cargará en el núcleo, incluyendo los valores de los contadores de bytes y paquetes. Ésta es la forma más sencilla de comenzar a usar el comando, aunque el ejemplo anterior se puede modificar hasta la saciedad, con diferentes posibilidades de redireccionamiento de la salida. Sin embargo, ésto se sale del objetivo de éste capítulo y prefiero dejar al lector que experimente por su cuenta.
El conjunto de reglas debería ahora estar cargado correctamente en el núcleo y todo debería funcionar. Si no es así, probablemente has cometido algún fallo al escribir los comandos, o habrás encontrado un "bug".
En este capítulo se desarrollará en profundidad cómo escribir tus propias reglas. Una regla puede definirse como las instrucciones que seguirá el cortafuegos al bloquear o permitir diferentes conexiones y paquetes en una cadena específica. Cada línea que escribas y añadas a una cadena se debe considerar una regla. También repasaremos las comparaciones básicas que tienes disponibles y cómo utilizarlas, así como los diferentes objetivos disponibles y cómo puedes crear nuevos objetivos (es decir, nuevas sub-cadenas).
Como ya se ha dicho, cada regla es una línea que lee el núcleo para saber qué hacer con un paquete. Si todos los criterios (o comparaciones) se cumplen, entonces se ejecuta la instrucción objetivo (o salto). Normalmente deberías escribir tus reglas con una estructura similar a ésta:
iptables [-t tabla] comando [comparación (match)] [objetivo/salto (target/jump)]
En ningún sitio se especifica que la instrucción objetivo (el salto) deba ser la última función de la línea. Sin embargo, deberías seguir esta estructura para conseguir que sea lo más legible posible. De todas formas, la mayoría de las reglas que veas se han escrito de esta forma. Así pues, si lees un script de otra persona, lo más seguro es que reconozcas la estructura y entiendas fácilmente la regla.
Si quieres utilizar cualquier otra tabla que no sea la estándar, puedes especificarla en la parte que dice [tabla]. Sin embargo, no es necesario especificar qué tabla usar, puesto que por defecto iptables utiliza la tabla filter (filtro) para incluir todos los comandos. Tampoco es preciso que especifiques la tabla justo en ese punto de la regla. La verdad es que puede estar casi e