Arquitectura
cs-routeros-bouncer actúa como puente entre la inteligencia de amenazas de CrowdSec y el firewall de MikroTik.
Visión general
Sección titulada «Visión general»Las dos ramas de la derecha de ese diagrama no son adorno. /metrics es el endpoint de Prometheus que lee el dashboard incluido, y /health es el endpoint que consulta la comprobación de salud de un contenedor; ambos los sirve el mismo servidor HTTP, que arranca incluso con las métricas desactivadas.


Cómo leer los diagramas
Sección titulada «Cómo leer los diagramas»Todos los diagramas de este sitio trazan la misma distinción, porque es la que importa cuando intentas averiguar qué le hace el bouncer a tu router:
- Una línea continua es una escritura. Cambia el estado del router: aparece o desaparece una entrada de address-list, se crea, se mueve o se elimina una regla de firewall.
- Una línea discontinua es una lectura, una comprobación o un descarte. Nada cambia en el router: llegan decisiones por el stream de la LAPI, se lista una address-list, se mide la deriva, se descarta una decisión.
Tres canales llevan esa distinción, para que el diagrama siga leyéndose en escala de grises, con cualquier tipo de deficiencia en la visión del color y sobre papel: el patrón de la línea, después el color y, por último, una leyenda dibujada con esas mismas dos líneas dentro de cada diagrama de flujo. El diagrama de secuencia es la excepción: ahí una flecha continua es una petición y una discontinua la respuesta, que es la convención propia de Mermaid y no la distinción lectura/escritura, así que rotularlo con esta leyenda lo haría ilegible. La convención está escrita en docs/src/styles/diagram.css; los colores salen de docs/src/styles/theme.css en tiempo de compilación, así que los diagramas siguen la paleta en lugar de fijar la suya.
Temas en profundidad
Sección titulada «Temas en profundidad»Componentes
Sección titulada «Componentes»El bouncer está compuesto por varios paquetes internos:
| Paquete | Responsabilidad |
|---|---|
cmd/cs-routeros-bouncer | Punto de entrada de la CLI, enrutamiento de subcomandos |
internal/config | Carga de configuración, validación, vinculación de variables de entorno |
internal/crowdsec | Cliente de streaming de la LAPI de CrowdSec |
internal/routeros | Cliente de la API de RouterOS (direcciones, reglas de firewall) |
internal/manager | Orquestador central: conecta todas las piezas |
internal/metrics | Métricas de Prometheus y endpoint de salud |
Flujo de datos
Sección titulada «Flujo de datos»Apagado y limpieza
Sección titulada «Apagado y limpieza»Parar el bouncer elimina sus reglas de firewall y deja bloqueadas las direcciones. Esa asimetría es deliberada: las reglas son baratas de recrear y desconciertan si aparecen abandonadas en un router, mientras que son las entradas de la address-list las que sostienen la protección de verdad, y tirar decenas de miles en cada reinicio abriría un hueco y habría que pagarlo otra vez al volver.
La eliminación se guía por los identificadores que esta ejecución anotó al crear o adoptar cada regla, y el comentario se analiza a la inversa para saber en qué menú vive la regla. Un comentario que el analizador no reconoce —por ejemplo, tras cambiar comment_prefix a mitad de ejecución— se registra y se omite, que es justo el caso para el que existe el barrido del arranque siguiente: busca por la firma fija @cs-routeros-bouncer, así que encuentra las reglas del bouncer las escribiera el prefijo que las escribiera. Ese mismo barrido es el que limpia tras una caída, donde Shutdown no llegó a ejecutarse.
Al cerrar la conexión con RouterOS el bouncer también se marca como desconectado, de modo que /health informa del estado real durante lo que queda de la ventana de apagado.
Principios de diseño
Sección titulada «Principios de diseño»Identificación basada en comentarios
Sección titulada «Identificación basada en comentarios»Todos los recursos creados por el bouncer en MikroTik se etiquetan con un comentario estructurado:
{comment_prefix}:{type}-{chain}-{direction}-{protocol} @cs-routeros-bouncerEjemplos:
crowdsec-bouncer:filter-input-input-v4 @cs-routeros-bouncercrowdsec-bouncer:raw-prerouting-input-v6 @cs-routeros-bouncer
Esto permite al bouncer identificar y gestionar con precisión sus propios recursos sin afectar a las reglas creadas por el usuario.
Adición optimista con caché primero
Sección titulada «Adición optimista con caché primero»Al procesar una decisión de ban, el bouncer consulta primero su caché de direcciones en memoria:
- Si la dirección ya está en la caché, la llamada a la API de RouterOS se omite por completo.
- Si la dirección no está en la caché, intenta añadirla directamente (~1–3 ms).
- Si RouterOS devuelve
already have such entry, lo trata como un conflicto a nivel de dispositivo, mantiene la conexión abierta, localiza la entrada existente y actualiza su timeout/comentario.
Esto es significativamente más rápido que el enfoque de “comprobar primero” (~400 ms por IP), que exigiría listar antes todas las entradas.
Pool de conexiones
Sección titulada «Pool de conexiones»El bouncer mantiene un pool configurable de conexiones persistentes a la API de RouterOS. El pool solo se usa para las eliminaciones: durante la reconciliación, las entradas obsoletas se eliminan de forma concurrente por el pool mediante el helper genérico ParallelExec. En la prueba de CAPI con el RB5009, eliminar ~26.800 entradas exclusivas de CAPI llevó ~77 s de trabajo de eliminación en RouterOS. Si el pool no se puede abrir, las eliminaciones recurren a la conexión principal.
Adición masiva basada en scripts
Sección titulada «Adición masiva basada en scripts»Para la reconciliación inicial, el bouncer genera scripts de RouterOS que añaden entradas en bloques de 100 IPs por script. Cada entrada usa :do { ... } on-error={} para omitir los duplicados de forma controlada. Las adiciones masivas no usan el pool de conexiones: se ejecutan de forma secuencial por la conexión principal. Aun así, este enfoque es ~97× más rápido que las llamadas individuales secuenciales a la API para listas grandes — medido en un RB5009UG+S+ (RouterOS 7.22.1), donde ~28.700 entradas de CAPI supusieron ~35–36 s de trabajo de adición masiva en RouterOS; consulta Benchmarking para la metodología.
Caché de direcciones en memoria
Sección titulada «Caché de direcciones en memoria»Un mapa en memoria (map[string]struct{} con sync.RWMutex) registra todas las direcciones presentes actualmente en el router. Esto proporciona:
- Búsquedas de unban en O(1): cuando se levanta el ban de una IP, primero se consulta la caché. Si la IP no está en la caché (por ejemplo, ya expiró en el router), la llamada a la API se omite por completo.
- Ruta rápida en O(1) para bans duplicados: los eventos de ban repetidos para direcciones que ya se sabe que están en el router se resuelven de inmediato sin generar tráfico innecesario de gestión/API en RouterOS.
- Prefiltrado durante el arranque: las eliminaciones recibidas durante la recogida inicial de decisiones se prefiltran contra los bans entrantes para evitar trabajo innecesario.
Listas de direcciones con nombre único
Sección titulada «Listas de direcciones con nombre único»A diferencia de algunos bouncers que crean listas con marca de tiempo, cs-routeros-bouncer usa una única address-list con nombre por protocolo:
crowdsec-bannedpara IPv4crowdsec6-bannedpara IPv6
Las reglas de firewall referencian estas listas por nombre, lo que es más eficiente y evita el problema de la duplicación.
Reconciliación basada en diferencias
Sección titulada «Reconciliación basada en diferencias»Al arrancar, y después periódicamente según crowdsec.reconciliation_interval, el bouncer calcula la diferencia entre las decisiones activas de CrowdSec y el estado actual de la address-list de MikroTik:
- Obtener todas las decisiones activas de CrowdSec
- Obtener todas las entradas de la address-list de MikroTik
- Comparar ambos conjuntos
- Añadir las entradas que faltan (están en CrowdSec pero no en MikroTik)
- Eliminar las entradas obsoletas (están en MikroTik pero no en CrowdSec)
Esto mantiene la pertenencia sincronizada con independencia de cómo se detuvo el bouncer, de lo que ocurriera mientras estaba fuera de línea o de si las entradas del lado de RouterOS expiraron mientras CrowdSec aún las consideraba activas.