Esta es una explicación clara y práctica sobre cómo funcionan las cadenas base vs. las cadenas regulares en nftables


Cadenas base vs. cadenas regulares en nftables

En nftables, todas las reglas pertenecen a cadenas (chains).
Pero no todas las cadenas son iguales: existen cadenas base y cadenas regulares, y es importante diferenciarlas porque cumplen funciones muy distintas dentro del procesamiento de paquetes.


1. ¿Qué es una cadena base?

Una cadena base es el punto de entrada de los paquetes a una tabla.
Es decir: el kernel automáticamente dirige los paquetes a estas cadenas según el hook asociado.

Características clave:

  • Están conectadas directamente al flujo del tráfico del kernel.
  • Deben especificar un hook y una prioridad.
  • Determinan cuándo debe procesarse el paquete en la pila de red.

Ejemplo de creación de una cadena base:

nft add chain inet filter input { type filter hook input priority 0; policy drop; }

Hooks más comunes:

Hook Función
prerouting Antes de cualquier decisión de enrutado
input Paquetes destinados al propio host
forward Paquetes que van a través del host (router)
output Tráfico generado por el host
postrouting Antes de salir por la interfaz

Importante

Una cadena base:

  • Debe tener un hook.
  • Debe tener una prioridad.
  • Puede tener una política por defecto (policy accept/drop).

Sin estas condiciones, no es considerada base, aunque se llame igual.

2. ¿Qué es una cadena regular?

Una cadena regular es simplemente una cadena normal, no conectada directamente al flujo del kernel.

Características clave:

  • No tienen hook.
  • No reciben paquetes automáticamente.
  • Solo se ejecutan si otra regla las invoca mediante jump o goto.

Ejemplo:

nft add chain inet filter allow-ssh

Esta cadena no procesará nada por sí sola. Para usarla debes llamarla desde otra cadena (normalmente base): nft add rule inet filter input tcp dport 22 jump allow-ssh

Diferencias esenciales

Aspecto Cadenas base Cadenas regulares
Conexión al kernel Sí, mediante hook No
Reciben tráfico automáticamente No
Necesitan hook/prioridad No
Política por defecto Sí (optional) No
Cómo se ejecutan Automático Jump/Goto desde otra cadena
Uso principal Entrada a la tabla Organización / modularidad

¿Por qué usar cadenas regulares?

Son útiles para: ✔ Organizar reglas en bloques lógicos
✔ Reutilizar reglas (DRY)
✔ Mantener un firewall grande fácil de mantener
✔ Ejecución condicional mediante jump

Ejemplo típico:

  • cadena base input: decide qué hacer con cada paquete
  • cadenas regulares: allow-ssh, allow-dns, log-drop, etc.

Resumen visual

Paquete → [hook:input] → (cadena base input)
                         ├── reglas directas
                         ├── jump allow-ssh → (cadena regular allow-ssh)
                         ├── jump allow-dns → (cadena regular allow-dns)
                         └── policy drop

Ejemplo

Este es un ejemplo completo, modular y profesional de un firewall con nftables, utilizando cadenas base y cadenas regulares, ideal para entornos de servidores o firewalls gestionados.


Firewall nftables modular con cadenas base y regulares

Este ejemplo incluye:

  • 1 tabla principal (inet filter)
  • 3 cadenas base: input, forward, output
  • Cadenas regulares para modularidad (allow-ssh, allow-dns, allow-local, drop-log)
  • Buenas prácticas: política por defecto DROP, logging controlado, orden lógico

Script completo

Puedes guardarlo como:
/etc/nftables.conf
o bien como un script ejecutable.

#!/usr/sbin/nft -f

flush ruleset
table inet filter {
    #
    # ──────── CADENAS BASE ────────
    #
    chain input {
        type filter hook input priority 0;
        policy drop;
        
        # Aceptar tráfico del loopback
        iif lo accept

        # Aceptar tráfico de conexiones establecidas
        ct state established,related accept

        # Llamada a cadenas regulares
        tcp dport 22 jump allow-ssh
        udp dport { 53 } jump allow-dns
        tcp dport { 53 } jump allow-dns

        # Llamada a cadena de gestión local
        ip saddr 192.168.1.0/24 jump allow-local

        # Si nada coincide → registrar y enviar a drop final
        jump drop-log
        drop
    }

    chain forward {
        type filter hook forward priority 0;
        policy drop;
    }
  
    chain output {
        type filter hook output priority 0;
        policy accept;
    }

    #
    # ──────── CADENAS REGULARES ────────
    #
    # Permisos SSH

    chain allow-ssh {
        ct state new tcp dport 22 accept
    }

    # Permisos DNS
    chain allow-dns {
        ip protocol udp udp dport 53 accept
        ip protocol tcp tcp dport 53 accept
    }

    # Tráfico permitido de red interna
    chain allow-local {
        ip saddr 192.168.1.0/24 accept
    }

    # Logging de paquetes denegados
    chain drop-log {
        limit rate 5/second log prefix "NFT DROP: " level warn
    }
}

Explicación del diseño

Cadenas base (con hook)

Estas cadenas están conectadas al flujo del kernel, por lo que reciben tráfico automáticamente:

  • input → paquetes destinados al host
  • forward → paquetes que pasan a través del host
  • output → paquetes generados por el host

Ejemplo: type filter hook input priority 0; policy drop;

Cadenas regulares (reutilizables)

No reciben tráfico por sí mismas; se ejecutan solo cuando les haces jump: tcp dport 22 jump allow-ssh

Esto permite:

  • Modularidad
  • Mantenimiento sencillo
  • Agrupar reglas por servicios

Resumen visual

input (base)
 ├── iif lo accept
 ├── ct established,related accept
 ├── jump allow-ssh  → allow-ssh (regular)
 ├── jump allow-dns  → allow-dns (regular)
 ├── jump allow-local → allow-local (regular)
 ├── jump drop-log → drop-log (regular)
 └── drop

Tip

chain drop-log

Explicación detallada de la cadena:

chain drop-log {
    limit rate 5/second log prefix "NFT DROP: " level warn
}

Esta cadena es regular (no base) y se usa para registrar (log) paquetes que van a ser descartados, pero evitando inundar el sistema de logs.

Vamos línea por línea 👇


1. limit rate 5/second

Esta regla aplica un limitador de velocidad (rate‑limiting).

✔ ¿Qué significa? Solo permite generar hasta 5 logs por segundo.

✔ ¿Por qué es necesario? Imagina un ataque de:

  • port scanning masivo
  • DDoS
  • flood de paquetes inválidos

Sin limitación, el log del sistema podría:

  • llenarse demasiado rápido
  • consumir CPU
  • saturar journald o syslog
  • llenar discos si no hay rotación

Con este límite, solo se registran algunos paquetes representativos.
Todos los paquetes iguales seguirán siendo bloqueados, pero no todos se registrarán.


2. log prefix "NFT DROP: "

Esta parte genera un mensaje de log en el sistema con un prefijo específico.

¿Para qué sirve el prefijo? Para que en journalctl o /var/log/messages puedas identificar fácilmente estos eventos: Ejemplo real del log:

Mar 17 10:22:15 server kernel: NFT DROP: IN=eth0 OUT= MAC=... SRC=1.2.3.4 DST=...

Esto permite:

  • Filtrar logs por "NFT DROP:"
  • Detectar intentos sospechosos
  • Crear dashboards (Grafana/Graylog/ELK/Splunk)
  • Reaccionar a patrones de ataque

3. level warn

Define el nivel de severidad del mensaje de log. warn → nivel de advertencia.

¿Por qué warn? Porque los drops inesperados suelen indicar:

  • Conexiones sospechosas
  • Escaneos
  • Malware
  • Tráfico inapropiado

Si usas info, podría mezclarse con mucho ruido. Si usas err o crit, sería exagerado.

¿Y qué pasa después?

La cadena drop-log solo registra el paquete, pero no lo descarta. Por eso, en la cadena base input el diseño era:

jump drop-log
drop

Secuencia real:

  1. Se registra el paquete, limitado a 5 por segundo.
  2. Luego se descarta con un drop explícito en la cadena base.

Esto separa claramente registrar de actuar, lo cual es una buena práctica.

Resumen claro

Parte Función
limit rate 5/second Evita saturación de logs (rate limiting)
log prefix "NFT DROP: " Registra solo algunos paquetes marcándolos con un prefijo útil
level warn Nivel de severidad adecuado para eventos anómalos