- NFQUEUE permite que Netfilter delegue decisiones de filtrado y marcado en procesos de espacio de usuario, habilitando firewalls y routers IP dinámicos.
- Suricata aporta un motor IDS/IPS multiproceso con soporte para NFQUEUE, AF_PACKET y reglas compatibles con Snort y Emerging Threats.
- Integrar NFQUEUE con Suricata, bases de datos, Memcached o Pfsense permite construir soluciones de seguridad y routing avanzadas con software libre.
- El rendimiento depende en gran medida del diseño de hilos y de la lógica en user-space, siendo clave optimizar y seleccionar bien el tráfico a inspeccionar.
Si trabajas con redes en GNU/Linux (mejores distribuciones Linux para seguridad y privacidad) y te interesa ir un paso más allá del típico firewall estático, seguramente te pique la curiosidad cómo combinar Netfilter, NFQUEUE y Suricata para montar un IDS/IPS realmente flexible sin gastarte una fortuna en hardware propietario. Ese es justo el terreno que vamos a pisar en este artículo, mezclando la parte más de bajo nivel (kernel, colas, C) con herramientas de alto nivel (Suricata, reglas, MySQL, Memcached, Pfsense).
La idea de fondo es muy potente: aprovechar que el kernel (ver cómo optimizar el kernel Linux) puede encolar paquetes a espacio de usuario y dejar que un programa nuestro decida qué hacer con ellos. Eso se puede usar tanto para filtrar tráfico (firewalling avanzado, IPS) como para hacer routing dinámico o integrar lógica de negocio (BBDD, cachés, detección de ataques a aplicaciones web, VoIP, etc.). Y si a esto le sumamos Suricata como motor IDS/IPS multiproceso, tenemos una combinación muy seria para entornos desde laboratorio hasta centros de datos con bastante tráfico.
NFQUEUE y Netfilter: elevando el firewall a espacio de usuario
En un sistema GNU/Linux típico, las reglas de Netfilter/iptables (o nftables) se suelen usar como políticas estáticas que viven íntegramente en kernel space. Frontends y appliances (incluyendo muchas soluciones basadas en Netfilter o Packet Filter de BSD) guardan la configuración en ficheros de texto, XML o SQLite y, cuando cambias algo, regeneran las reglas y las cargan de nuevo. Es flexible, pero la lógica sigue siendo una especie de «foto fija» del firewall con pequeños toques dinámicos (limitadores de conexiones por segundo, conntrack, match por país, layer7 si están disponibles, etc.).
Lo que propone NFQUEUE es cambiar el juego: en lugar de que sea siempre el kernel quien toma la decisión final, podemos delegar la decisión en un proceso de usuario. El kernel encola el paquete en una cola numerada y una aplicación que use la librería libnetfilter_queue lo recoge, lo analiza y devuelve un veredicto: aceptar, descartar o incluso marcarlo para routing de políticas. Es como tener un «juez» programable en C, Python o Perl encima del firewall.
La gracia de esto es que nuestro programa puede hacer literalmente lo que queramos: consultar /dev/urandom, una base de datos, un servicio web, una caché distribuida o un algoritmo sofisticado antes de contestar al kernel. En términos de arquitectura, el firewall deja de ser un simple conjunto de reglas estáticas para convertirse en una tubería donde Netfilter, colas y aplicaciones de usuario se encajan como piezas de un puzzle.
NFQUEUE está compuesto de dos partes: el target NFQUEUE en iptables, que envía los paquetes a una cola concreta, y la librería de usuario libnetfilter_queue, que permite leer esos paquetes y emitir un veredicto. No es un simple sniffer tipo tcpdump: aquí tenemos capacidad de decisión directa sobre el camino que toma el paquete.
Configuración básica de iptables con NFQUEUE
Desde el punto de vista de iptables, usar NFQUEUE es bastante directo: se añade una regla en la cadena que nos interese para mandar a la cola los paquetes que cumplan ciertos criterios (IP origen/destino, puertos, estados, módulos extra como GeoIP o layer7 si están disponibles, etc.).
Por ejemplo, si queremos mandar a NFQUEUE todos los pings que llegan al propio host:
iptables -I INPUT -p icmp -j NFQUEUE
Con esto enviamos los paquetes ICMP de entrada a la cola 0 (si no indicamos otra). Podríamos especificar otra cola con algo como –queue-num 3. Al listar las reglas con contadores (iptables -L -n -v -x) veremos que los contadores suben, señal de que se están encolando paquetes. Un detalle importante: si hay paquetes en la cola y ningún proceso de usuario los recoge y dicta veredicto, el comportamiento por defecto es denegarlos, de forma que un fallo de la app implica, por diseño, cortar tráfico.
Programar contra libnetfilter_queue: el “hello world” en C
Para engancharse a la cola desde espacio de usuario se utiliza la librería libnetfilter_queue (a su vez apoyada en libnfnetlink). En distribuciones como Debian basta con instalar los paquetes de desarrollo:
apt-get install libnfnetlink-dev libnetfilter-queue-dev gcc
El esqueleto de un programa mínimo que siempre acepte los paquetes se compone de unos pasos muy claros: abrir la librería, hacer unbind de posibles manejadores previos, bind al protocolo AF_INET, crear la cola con una función callback, definir el modo de copia y entrar en un bucle de recepción. La callback se ejecuta por cada paquete encolado, desde donde extraemos el id y devolvemos el veredicto.
En la práctica, el flujo es algo así: nfq_open para obtener el handle, nfq_unbind_pf para limpiar, nfq_bind_pf para asociarse a AF_INET, nfq_create_queue para registrar la callback en la cola 0, nfq_set_mode para indicar si queremos meta o paquete completo, bucle con recv() sobre el descriptor y nfq_handle_packet para procesar cada paquete. Al salir, se destruye la cola con nfq_destroy_queue y se cierra el handle con nfq_close.
Este tipo de «hello world» permite medir de forma limpia el impacto de NFQUEUE. Si compilamos el ejemplo con algo tipo:
gcc -o nftest codigo.c -lnfnetlink -lnetfilter_queue
y mandamos a la cola el tráfico de un iperf (por ejemplo, puerto TCP 5001 en INPUT y OUTPUT), veremos que un código que simplemente acepta los paquetes apenas afecta al rendimiento en una red gigabit. Eso sí, hay un detalle clave: imprimir información en la callback (printf, fflush, etc.) penaliza muchísimo el throughput, como se observa al comparar iperf con y sin debugging de pantalla.
Opciones avanzadas de NFQUEUE: bypass, balance y fail-open
NFQUEUE incorpora varias opciones interesantes a nivel de iptables que modifican el comportamiento por defecto de las colas y que conviene conocer antes de meterse en un entorno de producción o de alto rendimiento, ya que afectan a cómo se gestiona el fallo de la aplicación de usuario o el llenado de la cola.
Con –queue-bypass se puede hacer que, si no hay ningún proceso escuchando la cola, los paquetes no se descarten, sino que continúen al siguiente salto de la cadena de iptables. Esto puede ser útil si queremos que el sistema «falle abierto» cuando el servicio de usuario no esté operativo, aunque desde el punto de vista de seguridad es un arma de doble filo.
La opción –queue-balance permite distribuir los paquetes en un rango de colas (por ejemplo, de la 0 a la 3) y luego tener varios procesos o hilos independientes consumiendo de cada cola. El código de Netfilter garantiza que los paquetes de un mismo flujo terminan siempre en la misma cola, lo que facilita mucho mantener la coherencia en la lógica de decisión.
También existe el modo –fail-open, que controla qué hacer cuando la cola se llena porque el proceso de usuario va demasiado lento. Activarlo hace que, en lugar de tirar paquetes, el kernel los acepte directamente, evitando interrupciones masivas del tráfico. De nuevo, desde seguridad puede ser delicado, porque si queremos decidir caso a caso, perder paquetes de decisión supone incumplir ese objetivo.
Para monitorizar lo que está pasando con las colas, Netfilter expone información en el pseudo-fs /proc/net/netfilter/nfnetlink_queue, que se puede consultar fácilmente desde scripts o herramientas de monitorización.
Integrar lógica de negocio: pruebas con Memcached y MySQL
Una vez controlado el «hello world», el siguiente paso natural es enriquecer la callback con llamadas a sistemas externos. Un experimento típico consiste en decidir si aceptar o rechazar un paquete en función de si la IP origen aparece en algún backend, por ejemplo una base de datos MySQL o una caché Memcached.
En el caso de Memcached, se instala el demonio (apt-get install memcached) y se carga una clave, por ejemplo autorizada, con la IP que nos interesa. Podemos hacerlo con un simple echo y netcat, y comprobar luego con un get que el valor está bien almacenado. A partir de ahí, el programa NFQUEUE, además de obtener el id de paquete, recibe el paquete completo usando NFQNL_COPY_PACKET, saca la cabecera IP (struct iphdr) y convierte la dirección de origen a string con inet_ntop.
Para no perder tiempo en cada paquete abriendo conexiones, se inicializa la conexión a Memcached una sola vez en el main (memcached_create, memcached_server_list_append, memcached_server_push) y se guarda el handler en variables globales. Ya en la callback se llama a memcached_get con la clave que queramos, se compara la IP origen con el valor recuperado y, si coinciden, se devuelve NF_ACCEPT; en caso contrario, NF_DROP. Si la clave no existe o hay un error, se opta por tirar el paquete como política conservadora.
Midiendo con iperf, esta estrategia reduce el rendimiento a unos ~140 Mbit/s en una red gigabit, y se observa que la cola empieza a sufrir pérdidas (indicadas, por ejemplo, con símbolos en el propio código). Es decir, la simple llamada a un servicio de caché por paquete ya tiene un coste importante, aunque sigue siendo viable para volúmenes de tráfico medios si se optimiza.
Con MySQL, el planteamiento es similar pero más pesado: se instala el servidor y la librería de cliente, se crea una base de datos (por ejemplo nfqueue) con una tabla sencilla autorizada(ip varchar(50)), y se inserta la IP permitida. En el programa, se hace un mysql_init y mysql_real_connect al arrancar y, en la callback, se compone una consulta tipo «select * from autorizada where ip like ‘x.x.x.x'». Si la query se ejecuta correctamente y hay una fila, se acepta el paquete; si no, se descarta.
Con la cache de consultas de MySQL activada, los tests dan en torno a ~188 Mbit/s, que baja a ~103 Mbit/s si se desactiva la query cache. Son cifras que, aunque lejos del gigabit, demuestran que incluso haciendo las cosas de la forma menos elegante (single-thread, sin optimizar), se pueden manejar volúmenes de tráfico respetables con decisiones basadas en BBDD o cachés.
Rendimiento, multihilo y uso de CPU
Las pruebas con iperf, Memcached y MySQL dejan claro que el techo de rendimiento no lo impone tanto NFQUEUE en sí como la lógica que añadimos en espacio de usuario y cómo la implementamos. Un ejecutable que únicamente devuelve NF_ACCEPT alcanza prácticamente el gigabit sin despeinarse; en cuanto metemos I/O o llamadas de red, el throughput cae, y la CPU de la máquina de NFQUEUE, del demonio Memcached o de MySQL se ponen a tope.
A nivel de arquitectura, esto tiene dos lecturas. Por un lado, confirma que es perfectamente viable delegar decisiones de firewall en aplicaciones de usuario para volúmenes importantes, siempre que se tengan en cuenta los costes reales de cada llamada. Por otro, demuestra que para acercarse a los máximos de la plataforma hay que pensar en multihilo o multiproceso. NFQUEUE permite repartir tráfico en varias colas; podríamos lanzar varias copias de nuestra app, cada una escuchando una cola diferente, y aprovechar varios núcleos sin complicarnos con pthreads o fork masivos.
Otra optimización evidente sería limitar qué tráfico pasa por NFQUEUE. En las pruebas se estaba encolando todo el flujo de iperf, pero en un escenario real podríamos encolar sólo paquetes con estado NEW, dejar pasar ESTABLISHED/RELATED y reservar la lógica costosa para inicios de sesión o patrones sospechosos.
En definitiva, el uso de CPU y el diseño de los hilos mandan: si el proceso de usuario se queda corto, la cola se llena y nos toca tirar de cosas como fail-open o asumir drops, perdiendo parte del control fino que persigue este enfoque.
Routing dinámico con marcas de Netfilter
NFQUEUE no se limita a decir «acepto» o «tiro». También se puede usar para aplicar marcas de Netfilter (fwmark) a los paquetes y combinarlas con ip rule e iproute2 para montar esquemas de routing político muy flexibles, casi al estilo de VRF ligeros.
El procedimiento, a grandes rasgos, sería: definir varias tablas de routing en /etc/iproute2/rt_tables, por ejemplo lento y rapido; asignar a cada tabla una ruta por defecto distinta (una por la fibra y otra por un enlace más limitado); utilizar ip rule para decir que los paquetes con fwmark 1 vayan a la tabla rapido, los de fwmark 2 a lento, etc.; y, por último, usar NFQUEUE para marcar los paquetes adecuadamente antes de devolver el veredicto.
Para marcar desde la callback se usa nfq_set_verdict2, que es como nfq_set_verdict pero permitiendo fijar un valor de marca que luego ip rule verá. Combinando todo esto, se puede construir un router IP que decide por dónde enrutar en base a criterios arbitrarios: desde cosas absurdas como el tamaño par/impar del paquete hasta inputs externos como algoritmos de predicción de tráfico, eventos de redes sociales o señales de sistemas de monitorización.
El resultado es un sistema en el que el kernel sigue reenviando paquetes al ritmo habitual, pero el camino exacto que toma cada flujo se delega a un software externo que puede cambiar de opinión en tiempo real sin tocar reglas estáticas.
NFQUEUE y Suricata: IPS de alto nivel en GNU/Linux
Todo lo anterior se puede programar a mano en C, pero cuando hablamos de detección de intrusos e inspección profunda de paquetes la opción sensata suele ser apoyarse en un motor IDS/IPS maduro. Ahí entra Suricata, que nació precisamente como alternativa multiproceso a Snort, con capacidades IPS desde el principio y un enfoque muy orientado a aprovechar los muchos núcleos de CPU actuales.
Suricata está escrito desde cero y se distribuye bajo licencia GPLv2; la Open Information Security Foundation (OISF) mantiene tanto el motor como un ecosistema de reglas y documentación bastante completo. A diferencia de Snort 2.x, que heredó un núcleo de un solo hilo y fue parcheando sobre él, Suricata se diseñó pensando en dividir el trabajo en múltiples hilos: captura, decodificación, detección y salida, con distintas estrategias de distribución de carga.
A nivel funcional, Suricata aporta soporte nativo para IPv6, inspección de capa 7 (HTTP muy avanzado mediante librería HTP), reconocimiento de protocolos independientemente de puertos, reconstrucción de flujos y un sistema de variables de sesión (flowbits) muy potente para correlacionar diferentes etapas de un ataque repartidas en varias conexiones TCP.
Un punto fuerte adicional es que es compatible con reglas de Snort y puede utilizar tanto conjuntos de firmas de Sourcefire VRT como de Emerging Threats (la versión gratuita ET Open y la comercial ET Pro). Además, exporta los eventos en formatos muy útiles (fast.log, JSON en eve.json) para su integración con SIEMs, ELK, Splunk, etc.
Suricata como IPS en Linux: modos de captura y NFQUEUE
En GNU/Linux, Suricata puede trabajar en distintos modos según cómo se intercepte el tráfico: NFQUEUE, AF_PACKET, PF_RING, libpcap, NFLOG, IPFW, DAG, Napatech… Cada uno tiene sus ventajas y requisitos. A nivel de IPS puro, los dos más importantes son NFQUEUE y AF_PACKET.
En modo NFQ (NFQUEUE), el flujo es similar al que hemos descrito antes: un conjunto de reglas de iptables envía paquetes a una cola; Suricata, ejecutándose en espacio de usuario, lee de esa cola, inspecciona el contenido según sus reglas y devuelve un veredicto al kernel: NF_ACCEPT, NF_DROP o NF_REPEAT. El tercero se puede usar para reinyectar el paquete en la misma tabla iptables tras haberle aplicado marcas o modificaciones adicionales.
Este modo es muy flexible y fácil de introducir en infraestructuras ya existentes, porque basta con tocar las reglas en puntos concretos (por ejemplo, FORWARD, INPUT, OUTPUT) y dejar todo lo demás como estaba. El coste es que añadimos la sobrecarga de pasar paquetes arriba y abajo por NFQUEUE, con el impacto ya mencionado si el volumen es muy elevado o las reglas son pesadas.
En modo AF_PACKET, Suricata trabaja más cerca de la interfaz de red, copiando paquetes a través de sockets de tipo AF_PACKET. Es un enfoque de copia cero mucho más rápido, pero que exige que el sistema funcione como puerta de enlace con dos interfaces y que el bloqueo de tráfico se haga a nivel de reenvío entre NICs: el paquete que se decide bloquear simplemente no se pasa de la interfaz de entrada a la de salida.
En ambos modos se puede combinar Suricata con Netfilter, pero NFQUEUE encaja especialmente bien en escenarios donde queremos reusar toda la lógica de iptables (políticas, rangos, reglas previas) y sólo mandar a Suricata el tráfico que nos interesa inspeccionar de forma profunda.
Instalación básica de Suricata desde código fuente
Para quienes prefieren compilar Suricata en lugar de tirar de paquetes, el proceso en distribuciones tipo Debian/Ubuntu pasa por instalar primero las dependencias de compilación (build-essential, libpcre, libpcap-dev, libnet-dev, libyaml-dev, zlib, libcap-ng-dev, libjansson-dev, etc.), descargar el tarball desde la web oficial y ejecutar el clásico ./configure, make, make install.
Durante la fase de configure, el script indicará qué soportes se han activado: AF_PACKET yes/no, PF_RING, NFQUEUE yes/no, NFLOG, IPFW, soporte para libnss, libjansson, Prelude, PCRE JIT, Lua, GeoIP, etc. Es importante revisar que NFQUEUE está habilitado si queremos trabajar en ese modo, y que se ha localizado la librería de captura que nos interesa.
Después de instalar el binario, se puede ejecutar make install-conf para desplegar una configuración por defecto en /etc/suricata y make install-rules para descargar y colocar un conjunto de reglas de Emerging Threats en /etc/suricata/rules. Estos conjuntos se pueden ir actualizando posteriormente con herramientas como suricata-update.
En sistemas Red Hat / CentOS la lógica es similar, usando yum o dnf para las dependencias (libpcap-devel, pcre-devel, libnet-devel, libyaml-devel, jansson-devel, etc.) y luego compilando con los mismos pasos. De cara a rendimiento conviene, además, desactivar LRO/GRO en la interfaz de captura mediante ethtool, ya que estas funciones de offload pueden alterar la visibilidad de los paquetes a nivel IDS.
Configuración de Suricata: YAML, variables y threading
La configuración principal de Suricata reside en /etc/suricata/suricata.yaml. Es un fichero YAML bastante legible y fuertemente comentado, donde se definen desde rutas de logs y conjuntos de reglas hasta políticas de sistema operativo objetivo y parámetros de threading.
Uno de los campos básicos es default-log-dir, que marca dónde irán los ficheros de log (por defecto /var/log/suricata). Bajo la sección vars se encuentran variables como HOME_NET, EXTERNAL_NET, HTTP_PORTS, SHELLCODE_PORTS, SSH_PORTS… que sirven de abreviatura en las reglas. HOME_NET suele configurarse con el rango de red local que queremos proteger, mientras que EXTERNAL_NET se define típicamente como !HOME_NET.
Otra parte relevante es la host-os-policy, donde se indica a Suricata qué sistema operativo se supone que ejecutan determinados rangos IP. Eso le permite ajustar la forma en que reensambla TCP o interpreta ciertos comportamientos de la pila de red, dificultando intentos de evasión basados en diferencias entre pilas (Windows vs Linux, etc.). Se pueden asignar rangos concretos a categorías como windows, linux, bsd, vista, windows2k3, etc.
En cuanto al threading, la sección threading permite afinar la afinidad de CPU y el número de hilos de detección. Por defecto, set-cpu-affinity suele venir desactivado, lo que deja que el scheduler del sistema reparta los hilos por los núcleos. El parámetro detect-thread-ratio indica cuántos hilos de detección se crean por cada núcleo disponible; con detect-thread-ratio: 1.5 en una máquina de 8 cores, Suricata generará 12 hilos de detección, más los de captura y gestión.
Todo este modelo se refleja luego en la salida al arrancar el demonio: se ve un hilo de captura (por ejemplo, pcap) y múltiples hilos detectores, además de gestores de flujo y de estadísticas. Esta arquitectura multiproceso es la que permite a Suricata escalar mucho mejor que motores monohilo cuando se enfrentan a enlaces de 10/40 Gbit/s.
Reglas y actualización de firmas en Suricata
Suricata se apoya en conjuntos de reglas para detectar patrones de ataque, comportamientos anómalos o usos indebidos de protocolos. Además de aceptar reglas en formato Snort, el ecosistema más habitual es el de Emerging Threats: ET Open (gratuito) y ET Pro (comercial), con reglas orientadas a amenazas actuales.
En muchas distribuciones modernas existe la herramienta suricata-update, que simplifica la gestión de reglas: actualiza fuentes, habilita o deshabilita proveedores concretos y descarga las últimas versiones de los juegos de firmas. Un flujo típico sería instalar suricata-update (por ejemplo vía pip), ejecutar un primer suricata-update para descargar ET Open, listar fuentes con suricata-update list-sources, habilitar fuentes adicionales como ptresearch/attackdetection, oisf/trafficid o sslbl/ssl-fp-blacklist, y volver a lanzar suricata-update para regenerar el fichero rules.
El archivo suricata.yaml se ajusta para apuntar a la ruta correcta de rules, y a partir de ahí Suricata comenzará a levantar eventos de alerta que se volcarán en fast.log (texto legible rápido) y eve.json (JSON estructurado con información muy completa). Este último formato es especialmente útil para alimentar dashboards, sistemas de correlación o scripts personalizados.
Además de las firmas, Suricata incorpora decoders y parsers de múltiples protocolos, lo que le permite no depender tanto de puertos: puede identificar tráfico HTTP aunque vaya por puertos no estándar, detectar SSH, TLS, DNS, etc. sobre diferentes puertos y niveles de encapsulación (incluyendo túneles IPv4/IPv6 mixtos).
Uso práctico: de la detección de exploits web al bloqueo automático
Uno de los casos de uso más deseados en entornos de hosting o centros de datos es detectar en tiempo real intentos de explotación de vulnerabilidades en aplicaciones web (por ejemplo, WordPress y sus plugins) y reaccionar de forma automática, normalmente bloqueando o poniendo en lista negra la IP origen en el firewall.
Suricata, alimentado con reglas actualizadas, es capaz de reconocer patrones de ataques específicos contra URLs, parámetros, payloads HTTP e incluso secuencias de peticiones que encajan con exploits conocidos. El IDS puede operar en modo pasivo, recibiendo tráfico por mirroring de un puerto de switch (SPAN), pero para que actúe como IPS y bloquee hace falta integrarlo con el plano de reenvío.
Hay dos enfoques habituales: montar el IDS como bridge en línea, de forma que el tráfico pase físicamente a través de la máquina (con iptables, AF_PACKET o PF, según plataforma), o dejar la topología como está pero combinar mirroring con acciones en el firewall central vía API, scripts o NFQUEUE. En el primer caso se minimiza la latencia entre detección y bloqueo, a costa de poner otro elemento «en medio» de la red; en el segundo se gana en flexibilidad y resiliencia, pero se introduce algo más de complejidad en el orquestado.
Que un IDS sea capaz de detectar un intento de explotar un plugin vulnerable de WordPress y que, acto seguido, el mismo sistema o un componente asociado añada la IP atacante a una lista negra de iptables, es perfectamente factible. Se puede hacer vía salida a JSON de Suricata más scripts que llamen a iptables/nftables, o delegar parte de la lógica en NFQUEUE, donde el propio motor o un proceso asociado decide el veredicto en caliente sin esperar a que se actualice una lista externa.
Esto permite concentrarse en amenazas que realmente importan (exploits, intentos de escalada, escaneos muy agresivos), ignorando o simplemente registrando el ruido de fondo como escaneos de puertos básicos que, en muchos contextos, no son preocupantes por sí mismos.
Suricata sobre Pfsense: firewall opensource con IDS/IPS integrado
No todo el mundo puede o quiere permitirse un firewall de gama alta propietario tipo Palo Alto. En muchos entornos resulta más atractivo montar una solución opensource con Pfsense y Suricata, que cubra tanto las necesidades de cortafuegos avanzado (multi-WAN, VLAN, VPN, NAT, etc.) como de IDS/IPS.
Pfsense, basado en FreeBSD y Packet Filter, se lleva especialmente bien con entornos virtualizados (Proxmox, KVM, etc.), con la salvedad de que es recomendable usar tarjetas E1000 en lugar de Virtio en máquinas KVM si se quiere evitar problemas de rendimiento y cortes bajo carga, a menos que se apliquen las recomendaciones de Netgate (desactivar hardware checksum offload en System > Advanced > Networking y reiniciar, sabiendo que puede no ser suficiente con cargas muy altas).
Los requisitos mínimos de hardware para un laboratorio con Suricata en Pfsense pueden ser modestos (1 CPU 500 MHz, 1 GB RAM, 4 GB de disco), pero para un uso serio se aconsejan al menos 2 CPUs, 4 GB de RAM y 16 GB de almacenamiento, sin olvidar disponer de varias interfaces de red (una para WAN, otra para LAN, más si se quieren múltiples WAN o VLANs complejas).
La instalación de Pfsense en sí es muy rápida: se arranca desde la ISO, se acepta la licencia, se elige instalar, se selecciona idioma y teclado, se deja el particionado automático (Auto UFS si vamos a usar todo el disco), y en unos minutos el sistema está listo para el primer arranque. Desde la consola se ofrece un menú para asignar interfaces, reiniciar, lanzar shell, etc.
En laboratorio es habitual, por ejemplo en VirtualBox, desactivar momentáneamente el firewall de Pfsense desde consola con pfctl -d para poder entrar por la WAN al interfaz web (usuario admin, contraseña pfsense) y completar el asistente inicial: datos generales, servidores NTP, configuración WAN (DHCP en lab suele valer), LAN, cambio de password de admin y aplicación de la configuración.
Una vez estabilizado el acceso, se puede crear una regla en el firewall de la WAN que permita HTTPS desde cualquier origen hacia la propia IP de Pfsense, añadiendo separadores descriptivos para organizar visualmente las reglas (por ejemplo «Acceso Firewall»). Conviene también desactivar la opción de bloquear redes privadas en la WAN si estamos en un entorno de pruebas con direcciones RFC1918 detrás, para evitar tener que recurrir continuamente a pfctl -d.
Instalación de Suricata en Pfsense y visión general
Con la base de Pfsense funcionando, instalar Suricata es cuestión de entrar en System > Package Manager > Available Packages, buscar suricata e instalar el paquete. El proceso descarga bastantes ficheros y puede tardar un poco según el hardware, pero es totalmente asistido desde la interfaz web.
Una vez instalado, aparece una entrada de Suricata en la pestaña Services, desde donde se puede configurar instancias por interfaz (WAN, LAN, VLANs, etc.), elegir qué conjuntos de reglas usar, activar modo IDS o IPS y ajustar parámetros de rendimiento y logging. La profundidad de opciones es grande (incluso da para artículos enteros sólo sobre la configuración), pero la ventaja es que muchas tareas que en Linux requieren editar YAML a mano aquí se resuelven con formularios y checkboxes.
Algo importante: aunque en laboratorio sea tentador abrir la administración de Pfsense directamente a Internet, en producción es fundamental restringir el acceso a direcciones fijas, usar VPN para la gestión remota y evitar a toda costa dejar la consola web expuesta. Pfsense es muy flexible, pero también hay que tratarlo como el elemento crítico que es.
Con Suricata activo en Pfsense, se obtiene un entorno donde el tráfico pasa por PF para firewalling y NAT, y Suricata inspecciona según sus reglas y puede bloquear en modo IPS. Esta combinación, gestionada desde una única interfaz web, simplifica bastante la vida a la hora de desplegar protección DPI en pequeñas y medianas redes.
En muchos despliegues se suma a esto una conexión de Pfsense/Suricata a un SIEM o plataforma de logs centralizada, aprovechando los formatos de salida estructurados para correlacionar eventos y detectar campañas más amplias.
Supervisión de eventos y ejemplo de logs en Suricata
Una vez Suricata está en marcha, los eventos se van registrando en la ruta definida por default-log-dir, habitualmente /var/log/suricata. El fichero fast.log utiliza un formato compacto de texto con marcas temporales, IDs de reglas, clasificaciones y prioridad, adecuado para una inspección rápida desde terminal (tail -f).
Por ejemplo, ante tráfico con sumas de comprobación TCP incorrectas, podemos ver líneas del estilo: marcas de tiempo con fecha y hora, seguido de identificador de regla (por ejemplo 1:2200074:1), mensaje «SURICATA TCPv4 checksum inválida», clasificación, prioridad y el par IP/puerto origen-destino. Este tipo de alertas permiten identificar rápidamente problemas de integridad de paquetes o intentos de evasión.
El fichero eve.json contiene los mismos eventos en JSON, con campos como timestamp, event_type, src_ip, dest_ip, src_port, dest_port, proto, y un subdocumento alert con action, gid, signature_id, rev, signature, category y severity. Este formato se puede ingerir fácilmente con Logstash, Fluentd, Filebeat o cualquier otro agente de logs, permitiendo analíticas mucho más ricas que el simple tail de texto plano.
Al desplegar Suricata sobre un servidor multi-core (por ejemplo 8 núcleos), la prensa de hilos se aprecia bien en herramientas como htop en modo threads, viendo uno o varios hilos de captura (pcap, AF_PACKET o NFQ) y un conjunto amplio de hilos de detección repartidos entre los cores. Ajustar el detect-thread-ratio y la afinidad de CPU puede marcar grandes diferencias en throughput y latencias cuando el volumen de tráfico roza el límite de la plataforma.
Antes de ponerlo en producción conviene dedicar un rato a afinar qué conjuntos de reglas se activan, para evitar avalanchas de falsos positivos que terminen bloqueando tráfico legítimo o saturando de ruido los logs. Suricata-update permite deshabilitar categorías enteras o reglas individuales para ir encontrando un equilibrio razonable entre sensibilidad y operatividad.
Aplicaciones especiales: VoIP, análisis de audio y NFQUEUE creativo
Más allá de los usos clásicos (protección de servicios web, detección de malware, análisis de DDoS), el dúo Netfilter+NFQUEUE permite plantear soluciones bastante creativas en ámbitos como la VoIP. Por ejemplo, es factible montar un filtro anti SPIT (spam over IP telephony) o un sistema de censura de palabras malsonantes en flujos RTP.
La idea sería: identificar tráfico RTP por puertos o por reconocimiento de protocolo y mandarlo a NFQUEUE; desde la aplicación de usuario, reconstruir el flujo RTP usando una librería tipo librtp, sacar de ahí el audio en formato WAV y pasarlo a un motor de reconocimiento de palabras clave (wordspotting), como podría ser una librería de síntesis o reconocimiento ofertada por un tercero.
En función de las palabras detectadas, el proceso NFQUEUE podría decidir dejar pasar, bloquear o incluso alterar la reproducción insertando un beep en el flujo, aunque esto último ya requiere un control muy fino de RTCP, secuencias de paquetes y tiempos, casi estilo man-in-the-middle. No es algo trivial, pero a nivel teórico está perfectamente al alcance aprovechando el mismo esquema de colas y veredictos.
Es cierto que una parte de esto se podría hacer con un simple sniffer que alimente a un procesador externo, y luego actuar en la señalización SIP o a través de un SBC (Asterisk, Kamailio, etc.). La diferencia de usar NFQUEUE es que la acción sobre el flujo RTP puede ser inmediata y directa, sin necesidad de coordinar múltiples componentes ni esperar a que la capa de señalización termine la llamada.
Estos escenarios ilustran bien el potencial de la combinación GNU/Linux + Netfilter + Suricata + librerías de terceros: no se trata sólo de bloquear puertos y direcciones IP, sino de orquestar decisiones complejas sobre el tráfico en tiempo real usando un ecosistema 100 % software libre.
Mirando todo el recorrido, desde el pequeño programa C que siempre acepta paquetes hasta un despliegue de Suricata multiproceso integrado con NFQUEUE, Pfsense, BBDD y cachés, se aprecia la flexibilidad que ofrece esta pila tecnológica para construir desde firewalls dinámicos sencillos hasta arquitecturas de IDS/IPS a escala de centro de datos, con capacidad real de inspección profunda y de reacción automatizada frente a ataques cada vez más complejos.
Tabla de Contenidos
- NFQUEUE y Netfilter: elevando el firewall a espacio de usuario
- Configuración básica de iptables con NFQUEUE
- Programar contra libnetfilter_queue: el “hello world” en C
- Opciones avanzadas de NFQUEUE: bypass, balance y fail-open
- Integrar lógica de negocio: pruebas con Memcached y MySQL
- Rendimiento, multihilo y uso de CPU
- Routing dinámico con marcas de Netfilter
- NFQUEUE y Suricata: IPS de alto nivel en GNU/Linux
- Suricata como IPS en Linux: modos de captura y NFQUEUE
- Instalación básica de Suricata desde código fuente
- Configuración de Suricata: YAML, variables y threading
- Reglas y actualización de firmas en Suricata
- Uso práctico: de la detección de exploits web al bloqueo automático
- Suricata sobre Pfsense: firewall opensource con IDS/IPS integrado
- Instalación de Suricata en Pfsense y visión general
- Supervisión de eventos y ejemplo de logs en Suricata
- Aplicaciones especiales: VoIP, análisis de audio y NFQUEUE creativo