Reconciliación
Cómo mantiene el bouncer la pertenencia a las address-lists de MikroTik sincronizada con las decisiones de CrowdSec.
El problema de la reconciliación
Sección titulada «El problema de la reconciliación»Cuando el bouncer arranca (o se reinicia), o durante el intervalo de reconciliación periódica, la address-list de MikroTik puede estar desincronizada con las decisiones activas de CrowdSec:
- Puede que se hayan añadido IPs a CrowdSec mientras el bouncer estaba fuera de línea
- Puede que haya IPs que expiraron en CrowdSec pero sigan en la lista de MikroTik
- Puede que haya IPs que expiraron o se eliminaron manualmente en MikroTik mientras seguían activas en CrowdSec
- Pueden seguir existiendo entradas del bouncer de una ejecución anterior
Por defecto, la reconciliación se ejecuta al arrancar y después cada 15m. Configura crowdsec.reconciliation_interval para cambiar esta cadencia. Establécelo a 0 para desactivar la reconciliación periódica; los valores por debajo de 1m se rechazan al arrancar, así que usa 0 para desactivarla o una duración de 1m o superior para activarla.
Proceso de reconciliación
Sección titulada «Proceso de reconciliación»-
Recopilar
Obtener todas las decisiones activas de CrowdSec y todas las entradas de la address-list de MikroTik.
Al arrancar, las decisiones son la primera respuesta del stream de CrowdSec, así que la primera reconciliación espera a que la LAPI haya respondido. Una LAPI inalcanzable no produce ninguna decisión, y reconciliar sobre ese conjunto vacío eliminaría todas las entradas del bouncer; en su lugar, el bouncer registra un aviso cada 30 s y deja las listas del router como están. Una pasada periódica cuya petición de snapshot falla se omite por el mismo motivo.
-
Comparar
Comparar ambos conjuntos para determinar qué IPs hay que añadir y cuáles eliminar.
-
Refrescar la caché
Sustituir la caché de direcciones en memoria de esta familia por lo que el router acaba de comunicar, antes de que salga una sola adición o eliminación, para que los manejadores de ban y unban en vivo dejen de fiarse de entradas que el router ya no tiene.
-
Aplicar
Ejecutar las adiciones (mediante scripts masivos, 100 IPs por lote, o con
bulk_add_method: apiuna llamada a la API por entrada, por el pool cuando este llegó a abrirse y una a una por la conexión principal cuando no) y las eliminaciones (llamadas paralelas a la API a través del pool de conexiones cuando este llegó a abrirse, y una a una por la conexión principal cuando no). Las adiciones actualizan la caché con las entradas que no se notificaron como fallidas, las eliminaciones con lo que han intentado. Una entrada notificada como fallida puede haber llegado al router de todos modos, así que se guarda como incierta hasta el siguiente listado: un ban en vivo la añade, y un unban en vivo la busca en el router en lugar de omitirla. -
Verificar
Registrar las métricas de reconciliación y los recuentos finales para confirmar que la pertenencia a la address-list coincide con CrowdSec.
La comparación se hace por familia de protocolo y por address-list. Entran dos conjuntos —lo que CrowdSec dice que debe estar bloqueado y lo que el router tiene ahora mismo— y salen dos listas.
El filtro por prefijo sobre el conjunto presente es lo que hace seguro apuntar la reconciliación a una lista compartida. El bouncer pide a RouterOS la lista por su nombre y después se queda solo con las entradas cuyo comentario lleva el comment_prefix configurado —el prefijo en sí, seguido de |, un espacio o nada, así que un prefijo más largo como el crowdsec-bouncer-site2 de otro bouncer no cuenta como suyo—, así que una dirección que añadiste a mano —o que mantiene otra herramienta— ni siquiera entra en la comparación, y la rama de «presente y no deseada» no puede alcanzarla. Tampoco se vuelve a añadir su dirección cuando una decisión la pide, ni en la reconciliación ni en un ban en vivo: el bouncer deja tal cual una entrada con un comentario ajeno y nunca guarda su dirección en la caché, así que ningún unban la elimina. Que la dirección quede bloqueada depende entonces de esa entrada.
La comparación es por dirección normalizada, no por decisión: varias decisiones sobre la misma IP se colapsan en una sola entrada deseada, y solo la última leída aporta el timeout y el comentario con los que se escribe.
Decisiones en vivo durante una pasada
Sección titulada «Decisiones en vivo durante una pasada»Una pasada corre al lado del bucle de decisiones, que sigue aplicando los baneos y desbaneos en vivo en cuanto llegan, también mientras la pasada espera a que el router esté tranquilo (mikrotik.cpu_limit). La pasada trabaja con la instantánea y el listado que tomó al empezar, así que espera lo que diga la última palabra de cada decisión en vivo tomada durante ella. Cuando la decisión en vivo añadió o quitó la dirección, o la encontró en manos de una entrada ajena o rechazada por el router, la pasada deja la dirección al camino en vivo: ni la añade ni la quita, y su refresco de la caché deja la entrada de esa dirección como la dejó la decisión en vivo. Cuando la decisión en vivo no hizo nada porque la caché decía que no había nada que hacer, la pasada repara la dirección como siempre: la caché puede estar desfasada, por ejemplo tras un reinicio. Cuando la pasada termina, el bouncer lleva cada una de esas direcciones a esa última palabra, porque una decisión que llegó después de que la pasada planificara aún puede cruzarse con sus adiciones y eliminaciones, y fija los gauges de decisiones activas a partir de lo que esperaba la pasada y de esas últimas palabras. Cada decisión se cuenta una sola vez.
Solo corre una pasada a la vez. Un tic periódico, un reintento o un reinicio que llegan durante una pasada lanzan una sola pasada después de ella; la que pide un reinicio no espera a que el router esté tranquilo.
Rendimiento
Sección titulada «Rendimiento»Medido en un MikroTik RB5009UG+S+ (ARM64, 4 núcleos @ 1400 MHz, 1 GB de RAM, RouterOS 7.22.1) con mikrotik.pool_size: 10:
| Métrica | CAPI completo (~28.700 IPs) |
|---|---|
| Reconciliación en frío (tiempo total) | ~58 s |
| Trabajo de adición masiva en RouterOS | ~35–36 s |
| Pico de CPU observado en el router | ~39% durante grandes ráfagas de adición/eliminación |
| Comprobación periódica sin deriva | ~3–4 s, sin escrituras de adición/eliminación |
| Eliminación masiva CAPI → solo local | ~26.800 eliminaciones en ~77 s de trabajo de eliminación en RouterOS |
Coste en régimen con el código actual
Sección titulada «Coste en régimen con el código actual»La fila de comprobación sin deriva de arriba pertenece a la versión y a la lista
con la que se midió. Dos cambios posteriores la movieron: la pasada de
reconciliación dejó de pedir dos propiedades de la address-list que no lee nadie
(−11,2%), y un desbaneo individual dejó de recorrer la lista para redescubrir un
id que ya tenía. Vuelta a medir en el mismo equipo con RouterOS 7.24.1,
guardando 22.857 entradas (22.332 IPv4 más ~525 IPv6) con intervalo de 60 s, y
tomada del propio histograma operation_duration_seconds del bouncer en lugar
de con un cronómetro:
| Métrica | 22.857 entradas, RouterOS 7.24.1 |
|---|---|
| Comprobación periódica sin deriva | 1,86 s a lo largo de 28 ciclos seguidos |
| Ciclo de trabajo con intervalo de 60 s | 3,1% del tiempo real |
| Parte gastada en las dos lecturas | 98,5% |
Qué implica esto para el ajuste: el coste lo domina la lectura de la lista, así
que escala con cuántas entradas guarda el router, no con cuánta deriva haya que
reparar. Una pasada que no encuentra nada que arreglar cuesta prácticamente lo
mismo que una que sí. Además hay un suelo que ninguna reducción de propiedades
alcanza — un listado count-only de la misma lista sigue costando 1,18 s — así
que preguntarle al router “¿ha cambiado algo?” no sale apreciablemente más
barato que pedirle todo.
La reconciliación periódica suele ser ligera: cuando no hay deriva, lista las entradas actuales de la address-list, las compara con el snapshot activo de CrowdSec, actualiza los contadores y no realiza escrituras de adición/eliminación.
Optimizaciones
Sección titulada «Optimizaciones»Adición masiva basada en scripts
Sección titulada «Adición masiva basada en scripts»En lugar de una llamada a la API por entrada, cada una con su viaje de ida y vuelta al router, el bouncer envía hasta 100 adiciones como un único script de RouterOS. En RouterOS 7.8rc1 y posteriores ejecuta el script con una sola llamada:
/execute =script=<source> =as-string=as-string hace que la llamada espere al script y devuelva lo que imprimió: las posiciones de las adiciones que no pudo hacer, que luego se reintentan con su propia llamada a la API. No se guarda nada y nada llega al log del router. En versiones anteriores, para un bloque por encima del límite de 64 kB de /execute, o cuando el router resulta no tener /execute con as-string, el bouncer sube un script temporal llamado crowdsec-bulk-import, lo ejecuta por su id interno y lo elimina:
/system/script/run =number=<script-id>Cada script añade hasta 100 IPs usando :do { ... } on-error={} para gestionar los duplicados de forma controlada.
Eliminación en paralelo
Sección titulada «Eliminación en paralelo»Las eliminaciones usan el pool de conexiones configurado con llamadas concurrentes a la API. 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.
Prefiltrado
Sección titulada «Prefiltrado»Durante la recogida inicial de decisiones, si se reciben un ban y su unban correspondiente para la misma IP, el unban prefiltra el ban y lo elimina del conjunto pendiente. Esto evita añadir y eliminar inmediatamente la misma IP.