[{"body":"","link":"https://blog.bsdguy.org/tags/automation/","section":"tags","tags":null,"title":"Automation"},{"body":"","link":"https://blog.bsdguy.org/tags/backup/","section":"tags","tags":null,"title":"Backup"},{"body":"","link":"https://blog.bsdguy.org/tags/balanceo/","section":"tags","tags":null,"title":"Balanceo"},{"body":"","link":"https://blog.bsdguy.org/tags/failover/","section":"tags","tags":null,"title":"Failover"},{"body":"","link":"https://blog.bsdguy.org/tags/firewall/","section":"tags","tags":null,"title":"Firewall"},{"body":"","link":"https://blog.bsdguy.org/tags/linux/","section":"tags","tags":null,"title":"Linux"},{"body":"","link":"https://blog.bsdguy.org/tags/mariadb/","section":"tags","tags":null,"title":"Mariadb"},{"body":"","link":"https://blog.bsdguy.org/tags/mysql/","section":"tags","tags":null,"title":"Mysql"},{"body":"","link":"https://blog.bsdguy.org/tags/nat/","section":"tags","tags":null,"title":"Nat"},{"body":"","link":"https://blog.bsdguy.org/","section":"","tags":null,"title":"netlog"},{"body":"","link":"https://blog.bsdguy.org/posts/","section":"posts","tags":null,"title":"Posts"},{"body":"","link":"https://blog.bsdguy.org/tags/python/","section":"tags","tags":null,"title":"Python"},{"body":"El respaldo más común de MariaDB es una línea en el crontab:\n1mysqldump --all-databases | gzip \u0026gt; /backup/db-$(date +%F).sql.gz Funciona hasta el día en que no. Si mysqldump falla a la mitad — el servidor se reinicia, se llena el disco, una tabla está corrupta — gzip recibe un flujo cortado, lo comprime sin quejarse y el pipeline termina con código 0, porque el shell solo mira el código del último comando. Tienes un archivo con fecha de hoy, tamaño razonable y la mitad de tus datos. Te enteras cuando intentas restaurar.\nEste post construye un script en Python que resuelve eso y otros detalles que la línea del crontab ignora: revisa el código de salida y el final de cada volcado, separa cada base en su propio archivo, respalda usuarios y permisos, genera checksums, rota los respaldos viejos solo cuando el actual salió bien y corre desde un timer de systemd. Usa solo la librería estándar: nada de pip install.\nTodo lo que aparece aquí se probó contra MariaDB 12.3, incluyendo la restauración. Funciona igual desde MariaDB 10.5, donde los binarios pasaron a llamarse mariadb y mariadb-dump (los nombres mysql y mysqldump siguen existiendo como enlaces, y el script los usa como respaldo).\nLógico o físico Hay dos formas de respaldar MariaDB, y conviene elegir a propósito:\nLógico (mariadb-dump) Físico (mariadb-backup) Qué genera SQL: CREATE TABLE, INSERT\u0026hellip; Copia de los archivos de datos de InnoDB Portabilidad Entre versiones y arquitecturas Misma versión mayor de MariaDB Restaurar una sola tabla Fácil (es texto) Posible, pero laborioso Velocidad de restauración Lenta: re-ejecuta todo y reconstruye índices Rápida: copiar archivos y arrancar Tamaño práctico Hasta decenas de GB Cientos de GB o más Para la mayoría de servidores — aplicaciones web, un ERP, un Zabbix pequeño, el FreeRADIUS de un ISP — el respaldo lógico es la opción correcta: es legible, portable y fácil de restaurar parcialmente. Cuando la restauración de un volcado empieza a tardar horas, es momento de pasar a mariadb-backup. Hay una nota sobre eso al final.\n1. Un usuario solo para respaldos No uses root. Crea un usuario con los privilegios mínimos para leer todo sin poder modificar nada:\n1CREATE USER \u0026#39;backup\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;una-contraseña-larga-y-aleatoria\u0026#39;; 2 3GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT, PROCESS 4 ON *.* TO \u0026#39;backup\u0026#39;@\u0026#39;localhost\u0026#39;; Privilegio Para qué lo necesita mariadb-dump SELECT Leer los datos, y mysql.proc para los procedimientos SHOW VIEW Volcar la definición de las vistas TRIGGER Volcar los triggers LOCK TABLES Bloquear tablas que no son InnoDB durante el volcado EVENT Volcar los eventos programados PROCESS Leer información de tablespaces 'backup'@'localhost' solo acepta conexiones locales: el script corre en el mismo servidor.\n2. Credenciales fuera de la línea de comandos Pasar la contraseña con -p la deja visible en ps para cualquier usuario del sistema. En su lugar, guárdala en un archivo de opciones que solo root pueda leer:\n1sudo mkdir -p /etc/mariadb-backup 2sudo tee /etc/mariadb-backup/backup.cnf \u0026gt; /dev/null \u0026lt;\u0026lt;\u0026#39;CNF\u0026#39; 3[client] 4user = backup 5password = una-contraseña-larga-y-aleatoria 6# socket = /run/mysqld/mysqld.sock 7CNF 8sudo chmod 600 /etc/mariadb-backup/backup.cnf Si el cliente no encuentra el socket, descomenta la línea socket con la ruta de tu distribución (Debian usa /run/mysqld/mysqld.sock; openSUSE, /run/mysql/mysql.sock).\nEl script le pasa este archivo a cada comando con --defaults-extra-file. Pruébalo a mano:\n1sudo mariadb --defaults-extra-file=/etc/mariadb-backup/backup.cnf -e \u0026#39;SHOW DATABASES\u0026#39; --defaults-extra-file debe ser la primera opción de la línea de comandos; si va después de otra, el cliente lo ignora o falla.\n3. Las opciones de mariadb-dump Antes del script, vale la pena entender qué le vamos a pedir a mariadb-dump, porque los valores por defecto no bastan para un respaldo completo:\nOpción Qué hace --single-transaction Abre una transacción REPEATABLE READ y vuelca todo desde esa foto. Consistente para InnoDB sin bloquear escrituras. --quick Lee fila por fila en lugar de cargar cada tabla completa en memoria. --routines Incluye procedimientos y funciones almacenadas (no van por defecto). --triggers Incluye triggers (van por defecto, pero mejor explícito). --events Incluye eventos del event scheduler (no van por defecto). --hex-blob Escribe columnas binarias en hexadecimal, inmune a problemas de codificación. --default-character-set=utf8mb4 Evita que los acentos y emojis se corrompan en el viaje. Lo que no usamos: --all-databases. Un solo archivo con todo es cómodo hasta que necesitas restaurar una base de 200 MB que está enterrada en un volcado de 40 GB. El script vuelca cada base por separado.\nTampoco volcamos la base mysql directamente. Sus tablas internas cambian entre versiones, y restaurarlas sobre un servidor más nuevo puede romperlo. En su lugar, el script genera un _grants.sql con sentencias CREATE USER y GRANT, que son portables.\n4. El script Guárdalo como /usr/local/sbin/mariadb-backup.py:\n1#!/usr/bin/env python3 2\u0026#34;\u0026#34;\u0026#34;Respaldo lógico de MariaDB: un archivo .sql.gz por base de datos.\u0026#34;\u0026#34;\u0026#34; 3 4import argparse 5import fcntl 6import gzip 7import hashlib 8import logging 9import os 10import shutil 11import subprocess 12import sys 13import tempfile 14from datetime import datetime, timedelta 15from pathlib import Path 16 17EXCLUDE = {\u0026#34;information_schema\u0026#34;, \u0026#34;performance_schema\u0026#34;, \u0026#34;sys\u0026#34;, \u0026#34;mysql\u0026#34;} 18DUMP_OPTS = [ 19 \u0026#34;--single-transaction\u0026#34;, 20 \u0026#34;--quick\u0026#34;, 21 \u0026#34;--routines\u0026#34;, 22 \u0026#34;--triggers\u0026#34;, 23 \u0026#34;--events\u0026#34;, 24 \u0026#34;--hex-blob\u0026#34;, 25 \u0026#34;--default-character-set=utf8mb4\u0026#34;, 26] 27STAMP = \u0026#34;%Y%m%d-%H%M%S\u0026#34; 28CHUNK = 1024 * 1024 29 30log = logging.getLogger(\u0026#34;mariadb-backup\u0026#34;) 31 32 33class BackupError(Exception): 34 pass 35 36 37def find_binary(*names): 38 for name in names: 39 path = shutil.which(name) 40 if path: 41 return path 42 raise BackupError(f\u0026#34;no se encontró ninguno de: {\u0026#39;, \u0026#39;.join(names)}\u0026#34;) 43 44 45def run_query(client, defaults_file, sql): 46 \u0026#34;\u0026#34;\u0026#34;Ejecuta SQL con el cliente mariadb y devuelve las filas como listas.\u0026#34;\u0026#34;\u0026#34; 47 result = subprocess.run( 48 [client, f\u0026#34;--defaults-extra-file={defaults_file}\u0026#34;, \u0026#34;-N\u0026#34;, \u0026#34;-B\u0026#34;, \u0026#34;-r\u0026#34;, \u0026#34;-e\u0026#34;, sql], 49 capture_output=True, text=True, 50 ) 51 if result.returncode != 0: 52 raise BackupError(result.stderr.strip()) 53 return [line.split(\u0026#34;\\t\u0026#34;) for line in result.stdout.splitlines()] 54 55 56def list_databases(client, defaults_file): 57 rows = run_query(client, defaults_file, \u0026#34;SHOW DATABASES\u0026#34;) 58 return [row[0] for row in rows if row[0] not in EXCLUDE] 59 60 61def dump_grants(client, defaults_file, dest): 62 \u0026#34;\u0026#34;\u0026#34;Genera CREATE USER + GRANT para cada usuario (sin roles ni cuentas internas).\u0026#34;\u0026#34;\u0026#34; 63 users = run_query( 64 client, defaults_file, 65 \u0026#34;SELECT user, host FROM mysql.user \u0026#34; 66 \u0026#34;WHERE is_role = \u0026#39;N\u0026#39; AND user NOT IN (\u0026#39;\u0026#39;, \u0026#39;mariadb.sys\u0026#39;)\u0026#34;, 67 ) 68 if not users: 69 return 70 sql = \u0026#34;\u0026#34; 71 for user, host in users: 72 account = \u0026#34;\u0026#39;{}\u0026#39;@\u0026#39;{}\u0026#39;\u0026#34;.format(user.replace(\u0026#34;\u0026#39;\u0026#34;, \u0026#34;\u0026#39;\u0026#39;\u0026#34;), host.replace(\u0026#34;\u0026#39;\u0026#34;, \u0026#34;\u0026#39;\u0026#39;\u0026#34;)) 73 sql += f\u0026#34;SHOW CREATE USER {account}; SHOW GRANTS FOR {account};\u0026#34; 74 rows = run_query(client, defaults_file, sql) 75 dest.write_text(\u0026#34;\u0026#34;.join(f\u0026#34;{row[0]};\\n\u0026#34; for row in rows)) 76 77 78def dump_database(dump_bin, defaults_file, db, dest): 79 \u0026#34;\u0026#34;\u0026#34;Vuelca una base a dest (.sql.gz). Escribe primero a .partial y renombra al final.\u0026#34;\u0026#34;\u0026#34; 80 partial = dest.with_name(dest.name + \u0026#34;.partial\u0026#34;) 81 cmd = [dump_bin, f\u0026#34;--defaults-extra-file={defaults_file}\u0026#34;, *DUMP_OPTS, db] 82 tail = b\u0026#34;\u0026#34; 83 84 with tempfile.TemporaryFile() as err: 85 with open(partial, \u0026#34;wb\u0026#34;) as raw, gzip.GzipFile(fileobj=raw, mode=\u0026#34;wb\u0026#34;, compresslevel=6) as gz: 86 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=err) 87 for chunk in iter(lambda: proc.stdout.read(CHUNK), b\u0026#34;\u0026#34;): 88 gz.write(chunk) 89 tail = (tail + chunk)[-512:] 90 proc.stdout.close() 91 rc = proc.wait() 92 err.seek(0) 93 stderr = err.read().decode(errors=\u0026#34;replace\u0026#34;).strip() 94 95 if rc != 0: 96 partial.unlink(missing_ok=True) 97 raise BackupError(f\u0026#34;mariadb-dump salió con código {rc}: {stderr}\u0026#34;) 98 if b\u0026#34;-- Dump completed\u0026#34; not in tail: 99 partial.unlink(missing_ok=True) 100 raise BackupError(\u0026#34;el volcado no terminó con \u0026#39;-- Dump completed\u0026#39;\u0026#34;) 101 if stderr: 102 log.warning(\u0026#34;%s: %s\u0026#34;, db, stderr) 103 104 os.replace(partial, dest) 105 106 107def sha256sum(path): 108 digest = hashlib.sha256() 109 with open(path, \u0026#34;rb\u0026#34;) as fh: 110 for chunk in iter(lambda: fh.read(CHUNK), b\u0026#34;\u0026#34;): 111 digest.update(chunk) 112 return digest.hexdigest() 113 114 115def prune(root, keep_days, current): 116 cutoff = datetime.now() - timedelta(days=keep_days) 117 for entry in sorted(root.iterdir()): 118 if not entry.is_dir() or entry == current: 119 continue 120 try: 121 taken = datetime.strptime(entry.name, STAMP) 122 except ValueError: 123 continue # no es un directorio creado por este script 124 if taken \u0026lt; cutoff: 125 log.info(\u0026#34;eliminando respaldo antiguo %s\u0026#34;, entry.name) 126 shutil.rmtree(entry) 127 128 129def main(): 130 parser = argparse.ArgumentParser(description=__doc__) 131 parser.add_argument(\u0026#34;--defaults-file\u0026#34;, default=\u0026#34;/etc/mariadb-backup/backup.cnf\u0026#34;) 132 parser.add_argument(\u0026#34;--dest\u0026#34;, type=Path, default=Path(\u0026#34;/var/backups/mariadb\u0026#34;)) 133 parser.add_argument(\u0026#34;--keep-days\u0026#34;, type=int, default=14) 134 args = parser.parse_args() 135 136 logging.basicConfig(level=logging.INFO, format=\u0026#34;%(levelname)s %(message)s\u0026#34;) 137 os.umask(0o077) 138 args.dest.mkdir(parents=True, exist_ok=True) 139 140 lock = open(args.dest / \u0026#34;.lock\u0026#34;, \u0026#34;w\u0026#34;) 141 try: 142 fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB) 143 except BlockingIOError: 144 log.error(\u0026#34;otro respaldo está en curso\u0026#34;) 145 return 1 146 147 try: 148 client = find_binary(\u0026#34;mariadb\u0026#34;, \u0026#34;mysql\u0026#34;) 149 dump_bin = find_binary(\u0026#34;mariadb-dump\u0026#34;, \u0026#34;mysqldump\u0026#34;) 150 databases = list_databases(client, args.defaults_file) 151 except BackupError as exc: 152 log.error(\u0026#34;%s\u0026#34;, exc) 153 return 1 154 155 run_dir = args.dest / datetime.now().strftime(STAMP) 156 run_dir.mkdir() 157 log.info(\u0026#34;respaldando %d bases en %s\u0026#34;, len(databases), run_dir) 158 159 failed = [] 160 try: 161 dump_grants(client, args.defaults_file, run_dir / \u0026#34;_grants.sql\u0026#34;) 162 except BackupError as exc: 163 log.error(\u0026#34;usuarios y permisos: %s\u0026#34;, exc) 164 failed.append(\u0026#34;_grants\u0026#34;) 165 166 for db in databases: 167 dest = run_dir / f\u0026#34;{db}.sql.gz\u0026#34; 168 try: 169 dump_database(dump_bin, args.defaults_file, db, dest) 170 except BackupError as exc: 171 log.error(\u0026#34;%s: %s\u0026#34;, db, exc) 172 failed.append(db) 173 continue 174 log.info(\u0026#34;%s: %.1f MiB\u0026#34;, db, dest.stat().st_size / 2**20) 175 176 files = sorted(p for p in run_dir.iterdir() if p.is_file()) 177 (run_dir / \u0026#34;SHA256SUMS\u0026#34;).write_text( 178 \u0026#34;\u0026#34;.join(f\u0026#34;{sha256sum(p)} {p.name}\\n\u0026#34; for p in files) 179 ) 180 181 if failed: 182 log.error(\u0026#34;fallaron: %s — no se borran respaldos antiguos\u0026#34;, \u0026#34;, \u0026#34;.join(failed)) 183 return 1 184 185 prune(args.dest, args.keep_days, run_dir) 186 log.info(\u0026#34;respaldo completo\u0026#34;) 187 return 0 188 189 190if __name__ == \u0026#34;__main__\u0026#34;: 191 sys.exit(main()) 1sudo chmod 700 /usr/local/sbin/mariadb-backup.py Qué hace y por qué Lista las bases en cada corrida. SHOW DATABASES en lugar de una lista fija: si mañana alguien crea una base nueva, entra al respaldo sin tocar nada. EXCLUDE deja fuera las bases virtuales (information_schema, performance_schema, sys) y mysql, que se cubre con _grants.sql.\nTransmite en bloques. dump_database() lee la salida de mariadb-dump de a 1 MiB y la escribe directo al GzipFile. El volcado nunca vive completo en memoria, así que una base de 30 GB consume lo mismo que una de 30 MB.\nDetecta volcados truncados, de dos formas. Primero, revisa el código de salida de mariadb-dump — justo lo que el pipeline de shell pierde. Segundo, guarda los últimos 512 bytes del flujo y busca la marca -- Dump completed que mariadb-dump escribe solo cuando termina bien. Si cualquiera de las dos falla, el archivo se descarta.\nEscribe a .partial y renombra. El volcado se escribe a tienda.sql.gz.partial y solo al terminar se renombra con os.replace(), que es atómico. Nunca existe un tienda.sql.gz a medias: o está completo, o no está.\nstderr a un archivo temporal. Si stderr fuera un PIPE que nadie lee mientras leemos stdout, un proceso que escribe muchas advertencias llenaría el buffer del pipe y se bloquearía para siempre. Mandarlo a un TemporaryFile elimina ese riesgo. Si el volcado sale bien pero hubo advertencias, se registran.\nUna base que falla no detiene las demás. Se registra el error, se sigue con la siguiente y el script termina con código 1. Así systemd marca el servicio como fallido.\nRotación solo si todo salió bien. prune() borra los directorios con más de --keep-days días, pero únicamente cuando ninguna base falló. Si los respaldos llevan una semana fallando sin que nadie lo note, lo último que quieres es que el script borre los últimos buenos. Además, solo borra directorios cuyo nombre coincide con el formato de fecha del script; cualquier otra cosa en /var/backups/mariadb queda intacta.\nUn solo respaldo a la vez. fcntl.flock() sobre .lock evita que dos ejecuciones se pisen si una se alarga más que el intervalo del timer. El kernel libera el lock automáticamente al terminar el proceso, incluso si muere de forma abrupta.\nPermisos restrictivos. os.umask(0o077) hace que todo lo creado sea legible solo por el dueño. Un respaldo contiene todos tus datos y los hashes de contraseña de todos los usuarios: trátalo como tal.\nChecksums compatibles con sha256sum. SHA256SUMS usa el mismo formato que la herramienta estándar, así que se verifica con sha256sum -c en cualquier máquina, sin el script.\n5. Primera ejecución 1sudo /usr/local/sbin/mariadb-backup.py 1INFO respaldando 3 bases en /var/backups/mariadb/20260928-185715 2INFO radius: 412.3 MiB 3INFO tienda: 18.7 MiB 4INFO wiki: 2.1 MiB 5INFO respaldo completo El resultado:\n1/var/backups/mariadb/ 2├── .lock 3└── 20260928-185715/ 4 ├── _grants.sql 5 ├── SHA256SUMS 6 ├── radius.sql.gz 7 ├── tienda.sql.gz 8 └── wiki.sql.gz Verifica la integridad:\n1cd /var/backups/mariadb/20260928-185715 \u0026amp;\u0026amp; sha256sum -c SHA256SUMS Las opciones se pueden cambiar sin editar el script:\n1sudo /usr/local/sbin/mariadb-backup.py --dest /srv/backups/db --keep-days 30 6. Programarlo con systemd Un timer de systemd tiene ventajas claras sobre cron: los logs quedan en el journal, Persistent=true ejecuta el respaldo perdido si el servidor estaba apagado a la hora programada, y el servicio puede aislarse del resto del sistema.\n/etc/systemd/system/mariadb-backup.service:\n1[Unit] 2Description=Respaldo lógico de MariaDB 3After=mariadb.service 4Requires=mariadb.service 5 6[Service] 7Type=oneshot 8ExecStart=/usr/local/sbin/mariadb-backup.py 9Nice=10 10IOSchedulingClass=idle 11ProtectSystem=strict 12ReadWritePaths=/var/backups/mariadb 13ProtectHome=true 14PrivateTmp=true 15NoNewPrivileges=true /etc/systemd/system/mariadb-backup.timer:\n1[Unit] 2Description=Respaldo diario de MariaDB 3 4[Timer] 5OnCalendar=*-*-* 02:30:00 6RandomizedDelaySec=15m 7Persistent=true 8 9[Install] 10WantedBy=timers.target Nice e IOSchedulingClass=idle hacen que el respaldo ceda CPU y disco a la base de datos si hay carga. ProtectSystem=strict monta todo el sistema de archivos como solo lectura para este servicio, excepto ReadWritePaths. Si un error en el script intentara escribir en otro lado, fallaría. RandomizedDelaySec evita que varios servidores con la misma configuración golpeen el almacenamiento compartido al mismo segundo. Actívalo:\n1sudo mkdir -p /var/backups/mariadb 2sudo systemctl daemon-reload 3sudo systemctl enable --now mariadb-backup.timer Comprueba la próxima ejecución, lanza una manual y revisa el log:\n1systemctl list-timers mariadb-backup.timer 2sudo systemctl start mariadb-backup.service 3journalctl -u mariadb-backup.service -n 20 Enterarte cuando falla Un respaldo que falla en silencio es casi igual de malo que no tener respaldo. Como el script sale con código 1 ante cualquier error, systemd marca el servicio como failed, y puedes engancharle una notificación con OnFailure=:\n1[Unit] 2OnFailure=notify-failure@%n.service Donde notify-failure@.service es un servicio tuyo que manda un correo, un mensaje de Telegram o un webhook. Si ya tienes Zabbix o Prometheus, otra opción es monitorear la antigüedad del directorio más reciente en /var/backups/mariadb: alerta si supera 26 horas.\n7. Restaurar Un respaldo vale lo que su restauración. Estos son los casos habituales.\nUna base en el mismo servidor Los volcados no incluyen CREATE DATABASE (no usamos --databases), así que primero hay que crearla. Eso es a propósito: te deja restaurar con el nombre que quieras.\n1cd /var/backups/mariadb/20260928-185715 2sha256sum -c SHA256SUMS 3 4sudo mariadb -e \u0026#39;CREATE DATABASE tienda\u0026#39; 5gunzip -c tienda.sql.gz | sudo mariadb tienda Una base con otro nombre, para inspeccionarla El caso más común en la vida real: alguien borró registros ayer y hay que recuperarlos sin pisar la base de producción.\n1sudo mariadb -e \u0026#39;CREATE DATABASE tienda_ayer\u0026#39; 2gunzip -c tienda.sql.gz | sudo mariadb tienda_ayer Ahora puedes comparar y copiar solo lo necesario con un INSERT ... SELECT entre tienda_ayer y tienda.\nUn gotcha que encontré probando esto: si algún trigger, vista o procedimiento se creó con el nombre de la base escrito explícitamente (CREATE TRIGGER tienda.t ...), el volcado conserva ese texto tal cual, y al restaurar en tienda_ayer el objeto intenta crearse en tienda. La restauración falla con Trigger 'tienda.t' already exists. Los objetos definidos sin prefijo (lo normal cuando se crean con USE tienda) se restauran sin problema en cualquier nombre.\nSolo una tabla Como el volcado es texto, puedes extraer una sola tabla. El bloque de cada tabla empieza con un comentario -- Table structure for table y termina donde empieza el siguiente:\n1gunzip -c tienda.sql.gz \\ 2 | sed -n \u0026#39;/^-- Table structure for table `productos`/,/^-- Table structure for table/p\u0026#39; \\ 3 \u0026gt; productos.sql Revisa el archivo antes de aplicarlo: contiene un DROP TABLE IF EXISTS. Aplícalo sobre la base de pruebas, no sobre producción.\nUsuarios y permisos en un servidor nuevo _grants.sql contiene CREATE USER y GRANT de todas las cuentas, incluyendo root@localhost y el propio usuario backup, que en un servidor nuevo ya existen. Edita el archivo, deja solo las cuentas de aplicación y aplícalo:\n1sudo mariadb \u0026lt; _grants.sql Las contraseñas van como hash (IDENTIFIED BY PASSWORD '*...'), así que los usuarios conservan su contraseña sin que el archivo la contenga en claro. Aun así, esos hashes se pueden atacar por fuerza bruta: el archivo es sensible.\nPrueba la restauración de verdad Programa una prueba periódica — mensual está bien — en una máquina o contenedor aparte: toma el último respaldo, verifica checksums, restaura todo y corre un par de consultas que confirmen que los datos tienen sentido (conteo de filas de tablas grandes, fecha del registro más reciente). Es la única forma de saber que el procedimiento funciona antes de necesitarlo.\n8. Sacar los respaldos del servidor Un respaldo en el mismo disco que la base de datos protege contra un DELETE sin WHERE, pero no contra un disco muerto, un ransomware o un servidor comprometido. Necesitas al menos una copia en otro lugar.\nLo más simple es un segundo timer que corra después del respaldo y copie el directorio con rsync a otro servidor o con rclone a almacenamiento de objetos (S3, Backblaze B2, MinIO):\n1rclone sync /var/backups/mariadb remoto:respaldos-mariadb --exclude .lock Dos recomendaciones:\nCifra antes de subir si el destino no es tuyo. age o gpg con una clave pública: el servidor puede cifrar, pero solo quien tiene la clave privada (que no vive en el servidor) puede descifrar. Que el servidor no pueda borrar la copia remota. Si un atacante toma el servidor, lo primero que hará es borrar los respaldos que alcance. Usa credenciales de solo escritura, o activa versionado / object lock en el bucket. Consideraciones de producción --single-transaction solo es consistente para InnoDB. Las tablas MyISAM o Aria no son transaccionales: se vuelcan tal como estén en ese instante, y si cambian durante el respaldo, el resultado puede ser inconsistente con el resto. Revisa qué motores usas:\n1SELECT table_schema, engine, COUNT(*) 2 FROM information_schema.tables 3 WHERE table_schema NOT IN (\u0026#39;mysql\u0026#39;, \u0026#39;information_schema\u0026#39;, \u0026#39;performance_schema\u0026#39;, \u0026#39;sys\u0026#39;) 4 GROUP BY table_schema, engine; Si aparece algo distinto de InnoDB en una base importante, conviértelo con ALTER TABLE ... ENGINE=InnoDB.\nEvita cambios de esquema durante el respaldo. Un ALTER TABLE, RENAME TABLE o TRUNCATE TABLE que ocurra mientras corre --single-transaction puede hacer que el volcado de esa tabla salga vacío o falle. Programa las migraciones lejos de la ventana de respaldo.\nPunto en el tiempo. Un respaldo diario significa que puedes perder hasta 24 horas de datos. Si eso no es aceptable, activa el binary log (log_bin) y archívalo: con el último volcado más los binlogs posteriores, mariadb-binlog reproduce los cambios hasta el minuto anterior al desastre. Añade --master-data=2 a DUMP_OPTS para que el volcado registre la posición del binlog en la que fue tomado (requiere el privilegio RELOAD).\nCuándo pasar a mariadb-backup. Si una base pasa de unas decenas de GB, o restaurarla toma más tiempo del que el negocio tolera, cambia a respaldo físico con mariadb-backup. La estructura del script sirve igual: cambia la función de volcado, conserva el lock, los checksums, la rotación y el timer.\nEspacio en disco. Con retención de 14 días, necesitas unas 15 veces el tamaño de un respaldo comprimido (14 más el que se está creando). Un SQL comprimido con gzip suele ocupar entre el 10% y el 25% del tamaño de los datos en disco, según qué tan repetitivos sean. Monitorea el espacio libre del volumen de respaldos igual que el de la base.\n","link":"https://blog.bsdguy.org/posts/mariadb-respaldos-python/","section":"posts","tags":["mariadb","mysql","python","backup","linux","systemd","automation"],"title":"Respaldos de MariaDB con Python: mariadb-dump, Rotación y systemd"},{"body":"","link":"https://blog.bsdguy.org/tags/router/","section":"tags","tags":null,"title":"Router"},{"body":"","link":"https://blog.bsdguy.org/tags/systemd/","section":"tags","tags":null,"title":"Systemd"},{"body":"","link":"https://blog.bsdguy.org/tags/","section":"tags","tags":null,"title":"Tags"},{"body":"","link":"https://blog.bsdguy.org/tags/vyos/","section":"tags","tags":null,"title":"Vyos"},{"body":"Tener dos proveedores de internet solo sirve si el router sabe usarlos. VyOS resuelve esto con el módulo WAN Load Balancing (load-balancing wan), que reparte las conexiones nuevas entre varios enlaces, vigila la salud de cada uno y los saca de rotación cuando fallan — todo desde el mismo árbol de configuración, sin scripts externos ni marcas de mangle a mano como en otras plataformas.\nEste post construye la configuración completa sobre VyOS 1.5 (Circinus), paso a paso, explicando qué hace cada bloque y por qué está ahí.\nEscenario de referencia 1ISP1 ─── eth0 (100 Mbps) ┐ 2 ├─── VyOS ─── eth2 ─── LAN (192.168.10.0/24) 3ISP2 ─── eth1 (100 Mbps) ┘ Variable ISP1 ISP2 LAN Interfaz eth0 eth1 eth2 IP del router 203.0.113.2/30 198.51.100.2/30 192.168.10.1/24 Gateway 203.0.113.1 198.51.100.1 — Host de monitoreo 1.1.1.1 9.9.9.9 — Ajusta las direcciones a tu entorno. Al final hay una sección con los cambios necesarios si uno o ambos ISPs te entregan la IP por DHCP.\nCómo funciona el balanceo en VyOS Antes de configurar conviene entender el mecanismo, porque explica varias decisiones posteriores:\nCada conexión nueva que entra por la LAN se evalúa contra las reglas de load-balancing wan, en orden numérico. La regla que coincide elige una WAN según los pesos de las interfaces que estén sanas en ese momento. La conexión queda marcada en conntrack; todos sus paquetes siguientes salen por la misma WAN. El balanceo es por conexión, no por paquete, así que una descarga individual nunca suma el ancho de banda de ambos enlaces — lo que se reparte es el conjunto de conexiones. Un proceso de health check prueba cada WAN periódicamente. Si una falla, deja de recibir conexiones nuevas hasta que se recupere. Por defecto, el módulo aplica source NAT (masquerade) automáticamente con la IP de la WAN elegida. 1. Interfaces Entra al modo de configuración:\n1configure Asigna direcciones y descripciones:\n1set interfaces ethernet eth0 description \u0026#39;WAN1-ISP1\u0026#39; 2set interfaces ethernet eth0 address \u0026#39;203.0.113.2/30\u0026#39; 3 4set interfaces ethernet eth1 description \u0026#39;WAN2-ISP2\u0026#39; 5set interfaces ethernet eth1 address \u0026#39;198.51.100.2/30\u0026#39; 6 7set interfaces ethernet eth2 description \u0026#39;LAN\u0026#39; 8set interfaces ethernet eth2 address \u0026#39;192.168.10.1/24\u0026#39; 1commit Antes de seguir, comprueba que cada gateway responde desde su propia interfaz:\n1run ping 203.0.113.1 interface eth0 count 3 2run ping 198.51.100.1 interface eth1 count 3 Si alguno no responde, no tiene sentido continuar: el balanceador necesita que ambos enlaces funcionen a nivel de capa 3.\n2. Rutas estáticas Necesitamos dos tipos de rutas.\nRuta default en la tabla principal El balanceador crea sus propias tablas de enrutamiento para el tráfico que viene de la LAN, pero el tráfico originado por el propio router (DNS del router, NTP, actualizaciones, ping desde la CLI) usa la tabla principal. Le damos una default por cada ISP con distancias distintas:\n1set protocols static route 0.0.0.0/0 next-hop 203.0.113.1 distance \u0026#39;1\u0026#39; 2set protocols static route 0.0.0.0/0 next-hop 198.51.100.1 distance \u0026#39;10\u0026#39; Con esto el router sale por ISP1 y usa ISP2 si la interfaz eth0 pierde enlace. Más adelante (paso 8) veremos cómo hacer que también reaccione a caídas upstream.\nRutas de host para los monitores Cada WAN se vigilará haciendo ping a un host externo distinto. Fijamos una ruta /32 para que cada host de monitoreo siempre se alcance por su enlace correspondiente:\n1set protocols static route 1.1.1.1/32 next-hop 203.0.113.1 2set protocols static route 9.9.9.9/32 next-hop 198.51.100.1 Por qué esto importa: sin estas rutas, el ping de monitoreo de WAN2 podría resolverse por la tabla principal y salir por WAN1. Resultado: si ISP2 cae aguas arriba, el health check seguiría respondiendo y el enlace nunca se marcaría como caído. La ruta de host garantiza que la prueba mide el enlace correcto.\nEl costo es que 1.1.1.1 y 9.9.9.9 quedan atados a un solo ISP. Por eso conviene usar como monitores IPs que no uses para otra cosa (no las pongas como DNS de la LAN), o bien una IP estable dentro de la red de cada ISP.\n1commit 3. Health checks de cada WAN Aquí se define cómo decide VyOS si un enlace está vivo:\n1# WAN1 2set load-balancing wan interface-health eth0 nexthop \u0026#39;203.0.113.1\u0026#39; 3set load-balancing wan interface-health eth0 failure-count \u0026#39;3\u0026#39; 4set load-balancing wan interface-health eth0 success-count \u0026#39;2\u0026#39; 5set load-balancing wan interface-health eth0 test 10 type \u0026#39;ping\u0026#39; 6set load-balancing wan interface-health eth0 test 10 target \u0026#39;1.1.1.1\u0026#39; 7set load-balancing wan interface-health eth0 test 10 resp-time \u0026#39;3\u0026#39; 8 9# WAN2 10set load-balancing wan interface-health eth1 nexthop \u0026#39;198.51.100.1\u0026#39; 11set load-balancing wan interface-health eth1 failure-count \u0026#39;3\u0026#39; 12set load-balancing wan interface-health eth1 success-count \u0026#39;2\u0026#39; 13set load-balancing wan interface-health eth1 test 10 type \u0026#39;ping\u0026#39; 14set load-balancing wan interface-health eth1 test 10 target \u0026#39;9.9.9.9\u0026#39; 15set load-balancing wan interface-health eth1 test 10 resp-time \u0026#39;3\u0026#39; Parámetro Significado nexthop Gateway del ISP. El balanceador lo usa para construir la tabla de ruteo propia de esa WAN. Es obligatorio. failure-count Pruebas fallidas consecutivas para marcar el enlace como caído. success-count Pruebas exitosas consecutivas para devolverlo a rotación. test N type ping, ttl (prueba estilo traceroute con TTL limitado) o user-defined (script propio). target Host al que se prueba. resp-time Segundos máximos de espera por respuesta. Por qué no probar solo el gateway: el gateway del ISP suele ser el equipo que tienes enfrente (su CPE o su BRAS). Puede responder perfectamente mientras el ISP no tiene salida a internet. Probar un host externo detecta caídas aguas arriba.\nAjuste de sensibilidad: failure-count 3 con success-count 2 es un balance razonable. Valores más bajos reaccionan más rápido pero provocan flapping ante pérdida de paquetes puntual; valores más altos toleran enlaces ruidosos pero tardan más en hacer failover.\n4. Reglas de exclusión Antes de balancear, sacamos del juego el tráfico que no debe ir a internet. Si no lo haces, el tráfico entre tu LAN y otras redes privadas (VLANs, VPN, oficinas remotas) puede terminar marcado y enviado a una WAN.\n1set load-balancing wan rule 5 description \u0026#39;No balancear trafico privado\u0026#39; 2set load-balancing wan rule 5 inbound-interface \u0026#39;eth2\u0026#39; 3set load-balancing wan rule 5 exclude 4set load-balancing wan rule 5 destination address \u0026#39;192.168.0.0/16\u0026#39; 5 6set load-balancing wan rule 6 description \u0026#39;No balancear trafico privado\u0026#39; 7set load-balancing wan rule 6 inbound-interface \u0026#39;eth2\u0026#39; 8set load-balancing wan rule 6 exclude 9set load-balancing wan rule 6 destination address \u0026#39;10.0.0.0/8\u0026#39; 10 11set load-balancing wan rule 7 description \u0026#39;No balancear trafico privado\u0026#39; 12set load-balancing wan rule 7 inbound-interface \u0026#39;eth2\u0026#39; 13set load-balancing wan rule 7 exclude 14set load-balancing wan rule 7 destination address \u0026#39;172.16.0.0/12\u0026#39; El tráfico excluido se enruta normalmente con la tabla principal. Las reglas se evalúan en orden numérico y la primera que coincide gana, así que las exclusiones deben llevar números menores que la regla de balanceo.\n5. Regla de balanceo La regla principal: todo lo que entra por la LAN se reparte entre ambas WAN.\n1set load-balancing wan rule 100 description \u0026#39;Balanceo LAN\u0026#39; 2set load-balancing wan rule 100 inbound-interface \u0026#39;eth2\u0026#39; 3set load-balancing wan rule 100 protocol \u0026#39;all\u0026#39; 4set load-balancing wan rule 100 interface eth0 weight \u0026#39;1\u0026#39; 5set load-balancing wan rule 100 interface eth1 weight \u0026#39;1\u0026#39; Con pesos iguales, cada WAN recibe aproximadamente el 50% de las conexiones nuevas. Si una WAN cae, la regla sigue funcionando con la interfaz que queda: el failover ya está incluido en el balanceo, no hace falta configurarlo aparte.\nEnlaces de capacidad distinta Si ISP1 es de 300 Mbps e ISP2 de 100 Mbps, reparte en proporción 3:1:\n1set load-balancing wan rule 100 interface eth0 weight \u0026#39;3\u0026#39; 2set load-balancing wan rule 100 interface eth1 weight \u0026#39;1\u0026#39; Los pesos son relativos: ISP1 recibe 3 de cada 4 conexiones nuevas.\n6. Opciones globales 1set load-balancing wan sticky-connections inbound 2set load-balancing wan flush-connections sticky-connections inbound: una conexión que entra por una WAN (por ejemplo, un port forward hacia un servidor de la LAN) responde por esa misma WAN. Sin esto, la respuesta podría salir por la otra, con una IP de origen distinta, y el cliente remoto la descartaría. flush-connections: cuando una WAN cambia de estado, VyOS limpia la tabla de conntrack. Las conexiones atadas al enlace caído se reestablecen de inmediato por el enlace sano en lugar de quedar colgadas hasta su timeout. El costo es que las conexiones del enlace sano también se reinician en ese momento; si eso te molesta más que las sesiones colgadas, omite esta opción. Source NAT El balanceador aplica masquerade automáticamente por la interfaz de salida elegida, así que no necesitas reglas NAT adicionales para este escenario. Si prefieres controlar NAT tú mismo (por ejemplo, para usar un pool de IPs públicas en lugar de la IP de la interfaz), desactívalo y define las reglas explícitamente:\n1set load-balancing wan disable-source-nat 2 3set nat source rule 100 outbound-interface name \u0026#39;eth0\u0026#39; 4set nat source rule 100 source address \u0026#39;192.168.10.0/24\u0026#39; 5set nat source rule 100 translation address \u0026#39;masquerade\u0026#39; 6 7set nat source rule 110 outbound-interface name \u0026#39;eth1\u0026#39; 8set nat source rule 110 source address \u0026#39;192.168.10.0/24\u0026#39; 9set nat source rule 110 translation address \u0026#39;masquerade\u0026#39; Aplica y guarda:\n1commit 2save 7. Tráfico fijo a un enlace (con respaldo) Algunos servicios se rompen si la IP pública de origen cambia entre conexiones: VPNs site-to-site, sesiones de banca en línea, paneles que validan IP de origen, VoIP con registro SIP. Para ellos, en lugar de balancear, usamos una regla failover: siempre sale por el enlace de mayor peso y solo usa el otro si el primero cae.\n1# WireGuard saliente: siempre por ISP1, respaldo ISP2 2set load-balancing wan rule 20 description \u0026#39;WireGuard fijo a WAN1\u0026#39; 3set load-balancing wan rule 20 inbound-interface \u0026#39;eth2\u0026#39; 4set load-balancing wan rule 20 protocol \u0026#39;udp\u0026#39; 5set load-balancing wan rule 20 destination port \u0026#39;51820\u0026#39; 6set load-balancing wan rule 20 failover 7set load-balancing wan rule 20 interface eth0 weight \u0026#39;10\u0026#39; 8set load-balancing wan rule 20 interface eth1 weight \u0026#39;1\u0026#39; 9 10# Un host de la LAN (servidor VoIP) siempre por ISP2, respaldo ISP1 11set load-balancing wan rule 30 description \u0026#39;PBX fijo a WAN2\u0026#39; 12set load-balancing wan rule 30 inbound-interface \u0026#39;eth2\u0026#39; 13set load-balancing wan rule 30 source address \u0026#39;192.168.10.50\u0026#39; 14set load-balancing wan rule 30 failover 15set load-balancing wan rule 30 interface eth0 weight \u0026#39;1\u0026#39; 16set load-balancing wan rule 30 interface eth1 weight \u0026#39;10\u0026#39; En modo failover, el peso no reparte tráfico: define prioridad. La interfaz sana con mayor peso es la que se usa.\nObserva la numeración final de las reglas:\nRegla Tipo Qué hace 5–7 exclude Tráfico privado → tabla principal 20 failover WireGuard → WAN1 (respaldo WAN2) 30 failover PBX → WAN2 (respaldo WAN1) 100 balanceo Todo lo demás → 50/50 Las reglas específicas siempre antes que la general.\n1commit 2save 8. Hook: reaccionar a cambios de estado El balanceador puede ejecutar un script cada vez que una WAN cambia de estado. Lo usaremos para dos cosas: dejar registro en el log y mover la ruta default de la tabla principal (la que usa el propio router, paso 2), que por sí sola solo reacciona a caídas de enlace físico.\nCrea el script:\n1sudo tee /config/scripts/wlb-hook.sh \u0026gt; /dev/null \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; 2#!/bin/vbash 3# Variables que entrega VyOS: 4# WLB_INTERFACE_NAME -\u0026gt; eth0, eth1... 5# WLB_INTERFACE_STATE -\u0026gt; ACTIVE | FAILED 6 7source /opt/vyatta/etc/functions/script-template 8 9logger -t wlb-hook \u0026#34;WAN ${WLB_INTERFACE_NAME} ahora esta ${WLB_INTERFACE_STATE}\u0026#34; 10 11if [ \u0026#34;${WLB_INTERFACE_NAME}\u0026#34; = \u0026#34;eth0\u0026#34; ]; then 12 configure 13 if [ \u0026#34;${WLB_INTERFACE_STATE}\u0026#34; = \u0026#34;FAILED\u0026#34; ]; then 14 set protocols static route 0.0.0.0/0 next-hop 203.0.113.1 distance \u0026#39;250\u0026#39; 15 else 16 set protocols static route 0.0.0.0/0 next-hop 203.0.113.1 distance \u0026#39;1\u0026#39; 17 fi 18 commit 19 exit 20fi 21EOF 22sudo chmod +x /config/scripts/wlb-hook.sh Regístralo en la configuración:\n1set load-balancing wan hook \u0026#39;/config/scripts/wlb-hook.sh\u0026#39; 2commit 3save Cuando ISP1 falla, su ruta default sube a distancia 250 y la de ISP2 (distancia 10) toma el control para el tráfico del propio router. Cuando ISP1 regresa, se restaura.\n/config/scripts/ sobrevive a las actualizaciones de imagen de VyOS. No guardes scripts en otro lugar.\n9. Firewall El balanceo no reemplaza al firewall. Una base mínima con la sintaxis de VyOS 1.5 que protege al router en ambas WAN:\n1# Estado de conexiones, global 2set firewall global-options state-policy established action \u0026#39;accept\u0026#39; 3set firewall global-options state-policy related action \u0026#39;accept\u0026#39; 4set firewall global-options state-policy invalid action \u0026#39;drop\u0026#39; 5 6# Tráfico hacia el router 7set firewall ipv4 input filter default-action \u0026#39;drop\u0026#39; 8set firewall ipv4 input filter rule 10 description \u0026#39;LAN al router\u0026#39; 9set firewall ipv4 input filter rule 10 action \u0026#39;accept\u0026#39; 10set firewall ipv4 input filter rule 10 inbound-interface name \u0026#39;eth2\u0026#39; 11set firewall ipv4 input filter rule 20 description \u0026#39;ICMP desde WAN\u0026#39; 12set firewall ipv4 input filter rule 20 action \u0026#39;accept\u0026#39; 13set firewall ipv4 input filter rule 20 protocol \u0026#39;icmp\u0026#39; 14set firewall ipv4 input filter rule 30 action \u0026#39;accept\u0026#39; 15set firewall ipv4 input filter rule 30 inbound-interface name \u0026#39;lo\u0026#39; 16 17# Tráfico a través del router 18set firewall ipv4 forward filter default-action \u0026#39;drop\u0026#39; 19set firewall ipv4 forward filter rule 10 description \u0026#39;LAN a internet\u0026#39; 20set firewall ipv4 forward filter rule 10 action \u0026#39;accept\u0026#39; 21set firewall ipv4 forward filter rule 10 inbound-interface name \u0026#39;eth2\u0026#39; Los pings de monitoreo son tráfico saliente del router (cadena output, aceptada por defecto) y sus respuestas entran como established, así que no necesitan regla adicional.\n1commit 2save 10. Verificación Sal del modo configuración (exit) para los comandos operacionales.\nEstado de cada WAN:\n1show wan-load-balance Salida esperada con ambos enlaces sanos:\n1Interface: eth0 2 Status: active 3 Last Status Change: Mon Sep 28 10:12:44 2026 4 +Test: ping Target: 1.1.1.1 5 Last Interface Success: 0s 6 Last Interface Failure: n/a 7 # Interface Failure(s): 0 8 9Interface: eth1 10 Status: active 11 ... Conexiones y la WAN asignada a cada una:\n1show wan-load-balance connection Tráfico por interfaz en tiempo real:\n1monitor bandwidth interface eth0 2monitor bandwidth interface eth1 Distribución desde un cliente de la LAN: abre varias conexiones a un servicio que devuelva tu IP pública. Con balanceo 50/50 debes ver alternarse ambas IPs:\n1for i in $(seq 1 10); do curl -s https://ifconfig.me; echo; done Algunos clientes reutilizan la conexión HTTP (keep-alive) y siempre verán la misma IP. curl en un bucle abre conexiones nuevas cada vez.\nSimular la caída de una WAN La prueba más fiel es desconectar el cable de ISP1, pero para simular una caída aguas arriba (gateway vivo, internet muerta) bloquea el host de monitoreo:\n1configure 2set firewall ipv4 output filter rule 999 action \u0026#39;drop\u0026#39; 3set firewall ipv4 output filter rule 999 destination address \u0026#39;1.1.1.1\u0026#39; 4commit En unos segundos (failure-count × intervalo de prueba) show wan-load-balance debe mostrar eth0 como failed, el log registrará el evento del hook y todo el tráfico nuevo saldrá por eth1:\n1show log | match wlb-hook Restaura:\n1delete firewall ipv4 output filter rule 999 2commit 3exit Si tus ISPs entregan IP por DHCP Solo cambian tres cosas:\n1set interfaces ethernet eth0 address dhcp 2set interfaces ethernet eth0 dhcp-options default-route-distance \u0026#39;1\u0026#39; 3 4set interfaces ethernet eth1 address dhcp 5set interfaces ethernet eth1 dhcp-options default-route-distance \u0026#39;10\u0026#39; 6 7set load-balancing wan interface-health eth0 nexthop \u0026#39;dhcp\u0026#39; 8set load-balancing wan interface-health eth1 nexthop \u0026#39;dhcp\u0026#39; Elimina las rutas default estáticas del paso 2: DHCP las instala con las distancias indicadas. nexthop dhcp hace que el balanceador tome el gateway que entregue el ISP en cada renovación. Las rutas /32 de los monitores necesitan un gateway fijo. Con DHCP, usa set protocols static route 1.1.1.1/32 dhcp-interface 'eth0' (y lo mismo con 9.9.9.9 por eth1). En el hook, sustituye el cambio de distancia de ruta estática por set interfaces ethernet eth0 dhcp-options default-route-distance '250' y su reverso. Configuración completa 1set interfaces ethernet eth0 address \u0026#39;203.0.113.2/30\u0026#39; 2set interfaces ethernet eth0 description \u0026#39;WAN1-ISP1\u0026#39; 3set interfaces ethernet eth1 address \u0026#39;198.51.100.2/30\u0026#39; 4set interfaces ethernet eth1 description \u0026#39;WAN2-ISP2\u0026#39; 5set interfaces ethernet eth2 address \u0026#39;192.168.10.1/24\u0026#39; 6set interfaces ethernet eth2 description \u0026#39;LAN\u0026#39; 7 8set protocols static route 0.0.0.0/0 next-hop 203.0.113.1 distance \u0026#39;1\u0026#39; 9set protocols static route 0.0.0.0/0 next-hop 198.51.100.1 distance \u0026#39;10\u0026#39; 10set protocols static route 1.1.1.1/32 next-hop 203.0.113.1 11set protocols static route 9.9.9.9/32 next-hop 198.51.100.1 12 13set load-balancing wan flush-connections 14set load-balancing wan hook \u0026#39;/config/scripts/wlb-hook.sh\u0026#39; 15set load-balancing wan sticky-connections inbound 16 17set load-balancing wan interface-health eth0 failure-count \u0026#39;3\u0026#39; 18set load-balancing wan interface-health eth0 nexthop \u0026#39;203.0.113.1\u0026#39; 19set load-balancing wan interface-health eth0 success-count \u0026#39;2\u0026#39; 20set load-balancing wan interface-health eth0 test 10 resp-time \u0026#39;3\u0026#39; 21set load-balancing wan interface-health eth0 test 10 target \u0026#39;1.1.1.1\u0026#39; 22set load-balancing wan interface-health eth0 test 10 type \u0026#39;ping\u0026#39; 23set load-balancing wan interface-health eth1 failure-count \u0026#39;3\u0026#39; 24set load-balancing wan interface-health eth1 nexthop \u0026#39;198.51.100.1\u0026#39; 25set load-balancing wan interface-health eth1 success-count \u0026#39;2\u0026#39; 26set load-balancing wan interface-health eth1 test 10 resp-time \u0026#39;3\u0026#39; 27set load-balancing wan interface-health eth1 test 10 target \u0026#39;9.9.9.9\u0026#39; 28set load-balancing wan interface-health eth1 test 10 type \u0026#39;ping\u0026#39; 29 30set load-balancing wan rule 5 destination address \u0026#39;192.168.0.0/16\u0026#39; 31set load-balancing wan rule 5 exclude 32set load-balancing wan rule 5 inbound-interface \u0026#39;eth2\u0026#39; 33set load-balancing wan rule 6 destination address \u0026#39;10.0.0.0/8\u0026#39; 34set load-balancing wan rule 6 exclude 35set load-balancing wan rule 6 inbound-interface \u0026#39;eth2\u0026#39; 36set load-balancing wan rule 7 destination address \u0026#39;172.16.0.0/12\u0026#39; 37set load-balancing wan rule 7 exclude 38set load-balancing wan rule 7 inbound-interface \u0026#39;eth2\u0026#39; 39 40set load-balancing wan rule 20 destination port \u0026#39;51820\u0026#39; 41set load-balancing wan rule 20 failover 42set load-balancing wan rule 20 inbound-interface \u0026#39;eth2\u0026#39; 43set load-balancing wan rule 20 interface eth0 weight \u0026#39;10\u0026#39; 44set load-balancing wan rule 20 interface eth1 weight \u0026#39;1\u0026#39; 45set load-balancing wan rule 20 protocol \u0026#39;udp\u0026#39; 46 47set load-balancing wan rule 30 failover 48set load-balancing wan rule 30 inbound-interface \u0026#39;eth2\u0026#39; 49set load-balancing wan rule 30 interface eth0 weight \u0026#39;1\u0026#39; 50set load-balancing wan rule 30 interface eth1 weight \u0026#39;10\u0026#39; 51set load-balancing wan rule 30 source address \u0026#39;192.168.10.50\u0026#39; 52 53set load-balancing wan rule 100 inbound-interface \u0026#39;eth2\u0026#39; 54set load-balancing wan rule 100 interface eth0 weight \u0026#39;1\u0026#39; 55set load-balancing wan rule 100 interface eth1 weight \u0026#39;1\u0026#39; 56set load-balancing wan rule 100 protocol \u0026#39;all\u0026#39; Consideraciones de producción El balanceo es por conexión. Un speedtest de un solo flujo nunca mostrará la suma de ambos enlaces. Los speedtests multi-hilo sí pueden acercarse, porque abren varias conexiones que caen en WANs distintas.\nSitios que rompen con IPs cambiantes. Algunos servicios web asocian la sesión a la IP de origen; si la página abre conexiones nuevas que salen por otra WAN, la sesión se invalida. Si detectas uno, fíjalo con una regla failover por destination address como en el paso 7.\nServicios publicados hacia internet. El balanceo gobierna el tráfico saliente. Para servicios entrantes, sticky-connections inbound asegura que las respuestas salgan por la WAN correcta, pero el failover de cara al exterior depende de DNS: publica el servicio con TTL bajo y usa un proveedor con health checks que cambie el registro si una IP deja de responder.\nIPv6. El módulo load-balancing wan trabaja sobre IPv4. Con IPv6 lo habitual es no balancear: cada ISP delega su prefijo, y la LAN anuncia ambos o solo el del enlace activo según tu estrategia de failover.\nMonitores robustos. Un solo host de prueba por WAN es un punto único de falla: si 1.1.1.1 tiene un mal día, sacarás de rotación un enlace sano. En entornos críticos, usa como target una IP dentro de la red del propio ISP (su DNS, por ejemplo), que valida el enlace sin depender de terceros.\n","link":"https://blog.bsdguy.org/posts/vyos-multiwan-balanceo-failover/","section":"posts","tags":["vyos","router","wan","balanceo","failover","nat","firewall","linux"],"title":"VyOS 1.5: MultiWAN con Balanceo de Carga y Failover"},{"body":"","link":"https://blog.bsdguy.org/tags/wan/","section":"tags","tags":null,"title":"Wan"},{"body":"","link":"https://blog.bsdguy.org/tags/bgp/","section":"tags","tags":null,"title":"Bgp"},{"body":"","link":"https://blog.bsdguy.org/tags/infraestructura/","section":"tags","tags":null,"title":"Infraestructura"},{"body":"","link":"https://blog.bsdguy.org/tags/isp/","section":"tags","tags":null,"title":"Isp"},{"body":"","link":"https://blog.bsdguy.org/tags/opnsense/","section":"tags","tags":null,"title":"Opnsense"},{"body":"Un ISP tiene requisitos distintos a una red corporativa. No hay usuarios internos navegando sitios de entretenimiento — hay clientes con IPs asignadas, tránsito hacia upstream providers, equipos de red que deben protegerse del tráfico de clientes, y un plano de gestión que no puede quedar expuesto. OPNsense cubre este caso de uso con solidez si entiendes qué configurar y en qué orden.\nTopología de referencia 1Upstream (tránsito) 2 │ 3 [ em0 ] — WAN / Transit 4 OPNsense 5 [ em1 ] — Red de clientes (PPPoE o IPs estáticas) 6 [ em2 ] — Gestión (OOB / equipos de red) Interfaz Nombre Red Propósito em0 WAN IP del upstream Tránsito hacia internet em1 CLIENTES 100.64.0.0/22 Segmento de clientes (CG-NAT o pública) em2 MGMT 10.255.0.0/24 Gestión de infraestructura 100.64.0.0/10 es el espacio CGNAT (RFC 6598). Úsalo si no tienes IPs públicas para todos tus clientes.\n1. Interfaces Interfaces → Assignments\nAsigna las tres interfaces y nómbralas. Luego configura cada una:\nWAN (em0) IPv4 Type: Static (o DHCP si tu upstream lo requiere) IPv4: IP asignada por tu upstream / BGP peer Block private networks: ✓ Block bogon networks: ✓ CLIENTES (em1) Enable: ✓ IPv4 Type: Static IPv4: 100.64.0.1 / 22 Block private networks: ✗ MGMT (em2) Enable: ✓ IPv4 Type: Static IPv4: 10.255.0.1 / 24 Block private networks: ✗ 2. Routing Si tu upstream te da una ruta default vía DHCP o un gateway estático:\nSystem → Gateways → Add\nCampo Valor Name GW_UPSTREAM Interface WAN IP Address IP de tu upstream Monitor IP 8.8.8.8 (o tu upstream) System → Routes → Add\nCampo Valor Network 0.0.0.0/0 Gateway GW_UPSTREAM Si usas BGP, instala el plugin os-frr desde System → Firmware → Plugins y configura FRR — la ruta default llegará por el proceso de routing sin necesidad de entrada manual.\n3. NAT Opción A — Sin NAT (IPs públicas para clientes) Si asignas IPs públicas directamente a tus clientes, desactiva el NAT de salida automático:\nFirewall → NAT → Outbound → Selecciona Disable Outbound NAT\nEl tráfico de clientes sale con su IP de origen real. Asegúrate de que tu upstream acepta esos prefijos (filtra por AS origin o prefix-list en BGP).\nOpción B — CG-NAT (100.64.0.0/22 → IP pública) Firewall → NAT → Outbound → Selecciona Manual Outbound NAT\nAgrega una regla:\nCampo Valor Interface WAN Source 100.64.0.0/22 Translation WAN address (o pool) Description CG-NAT clientes CG-NAT introduce limitaciones en aplicaciones que dependen de IP de origen único (gaming, VoIP, algunos VPNs). Considera port block allocation si tu volumen de clientes lo requiere.\n4. Aliases Firewall → Aliases → Add\nDefine estos antes de escribir reglas:\nRFC1918 — espacio privado\nCampo Valor Name RFC1918 Type Network Networks 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 CGNAT_SPACE\nCampo Valor Name CGNAT_SPACE Type Network Networks 100.64.0.0/10 INFRA_MGMT — equipos de red bajo gestión\nCampo Valor Name INFRA_MGMT Type Network Networks 10.255.0.0/24, 10.255.1.0/24, \u0026hellip; PUERTOS_GESTION — acceso SSH/HTTPS a routers\nCampo Valor Name PUERTOS_GESTION Type Port Ports 22, 443, 8291 (Winbox) 5. Reglas de firewall WAN — perímetro externo Firewall → Rules → WAN\nOPNsense bloquea todo por defecto en WAN. Solo agrega lo que necesitas:\nRegla 1 — Bloquear spoofing de origen privado (logging útil)\nCampo Valor Action Block Source RFC1918 Destination any Log ✓ Description Bloquear spoof RFC1918 desde WAN Regla 2 — Bloquear spoofing de espacio CGNAT\nCampo Valor Action Block Source CGNAT_SPACE Destination any Log ✓ Description Bloquear spoof 100.64/10 desde WAN Regla 3 — Permitir BGP (si aplica)\nCampo Valor Action Pass Protocol TCP Source IP del peer BGP Destination WAN address Dest. Port 179 Description BGP upstream El deny implícito final bloquea cualquier otro tráfico entrante no solicitado.\nCLIENTES — el segmento más riesgoso Firewall → Rules → CLIENTES\nLos clientes no deben alcanzar tu infraestructura de gestión. Solo deben tener internet.\nRegla 1 — Bloquear acceso a infraestructura de gestión\nCampo Valor Action Block Source CLIENTES net Destination INFRA_MGMT Log ✓ Description Bloquear clientes → infraestructura Regla 2 — Bloquear acceso a la GUI de OPNsense\nCampo Valor Action Block Protocol TCP Source CLIENTES net Destination This Firewall Dest. Port 443, 80 Description Bloquear acceso GUI Regla 3 — Bloquear tráfico lateral entre clientes (si están en la misma subred)\nCampo Valor Action Block Source CLIENTES net Destination CLIENTES net Description Bloquear tráfico cliente-cliente Esta regla es importante en CGNAT: evita que un cliente escanee o ataque a otro dentro del mismo bloque 100.64/22.\nRegla 4 — Permitir DNS hacia el resolver local (si sirves DNS a clientes)\nCampo Valor Action Pass Protocol TCP/UDP Source CLIENTES net Destination This Firewall Dest. Port 53 Description DNS local Regla 5 — Permitir internet\nCampo Valor Action Pass Protocol any Source CLIENTES net Destination any Description Internet MGMT — plano de gestión Firewall → Rules → MGMT\nLa red de gestión administra routers, switches y el propio OPNsense. Tiene acceso amplio pero solo desde hosts autorizados.\nRegla 1 — Permitir todo desde MGMT\nCampo Valor Action Pass Source MGMT net Destination any Description Acceso total desde gestión Si quieres ser más estricto, limita el destino a INFRA_MGMT y agrega una segunda regla para internet con destino any.\n6. DNS para clientes Si sirves DNS recursivo a tus clientes desde OPNsense:\nServices → Unbound DNS → General\nEnable: ✓ Listen Port: 53 Network Interfaces: CLIENTES, MGMT DNSSEC: ✓ DNS64: opcional si mezclas IPv4/IPv6 Agrega ACLs para controlar quién puede consultar:\nServices → Unbound DNS → Access Lists\nRed Acción 100.64.0.0/22 Allow 10.255.0.0/24 Allow 0.0.0.0/0 Refuse 7. Proteger la GUI de OPNsense System → Settings → Administration\nTCP Port: cambia de 443 a un puerto no estándar (ej. 4443) Listen Interfaces: selecciona solo MGMT — la GUI nunca debe escuchar en WAN ni CLIENTES HTTPs only: ✓ Login Protection: ✓ (rate limiting contra fuerza bruta) Disable web GUI redirect rule: ✓ Aplica y reconecta desde la interfaz MGMT.\n8. Logging y visibilidad Firewall → Log Files → Live View\nFiltra por interfaz CLIENTES y acción block para ver intentos de acceso a la infraestructura. En un ISP es normal ver:\nEscaneos de puerto hacia This Firewall Intentos de SSH o Telnet hacia el gateway Tráfico CGNAT saliente bloqueado por reglas de aislamiento System → Settings → Logging → Remote Syslog\nApunta hacia tu servidor de logs centralizado (Graylog, Loki, syslog-ng). En producción no quieres confiar solo en el storage local de OPNsense.\n9. Verificación Desde un cliente en CLIENTES net:\n1# Debe funcionar 2curl https://example.com 3dig @100.64.0.1 google.com # DNS local 4 5# Debe fallar 6ping 10.255.0.1 # Gateway MGMT — bloqueado 7ssh 10.255.0.5 # Router de infraestructura — bloqueado 8curl https://100.64.0.1:4443 # GUI OPNsense — bloqueado 9ping 100.64.0.100 # Otro cliente — bloqueado Desde MGMT:\n1ping 100.64.0.1 # Gateway clientes — visible 2ssh 10.255.0.5 # Equipo de red — debe funcionar 3curl -k https://10.255.0.1:4443 # GUI OPNsense — debe abrir Verificar rutas activas:\nSystem → Routes → Status — confirma la ruta default hacia GW_UPSTREAM y las rutas conectadas de cada interfaz.\nResumen del modelo de acceso Desde \\ Hacia Internet Infra/MGMT Otros clientes GUI OPNsense WAN — ✗ ✗ ✗ CLIENTES ✓ ✗ ✗ ✗ MGMT ✓ ✓ ✓ ✓ El principio es el mismo que en cualquier infraestructura de red: los clientes son tráfico no confiable. La red de gestión es el único plano desde donde se opera la infraestructura, y ningún cliente debe poder alcanzarla — ni por error ni por intento deliberado.\n","link":"https://blog.bsdguy.org/posts/opnsense-firewall-isp-basico/","section":"posts","tags":["opnsense","firewall","isp","bgp","nat","pppoe","infraestructura"],"title":"OPNsense como Firewall Básico para ISP"},{"body":"","link":"https://blog.bsdguy.org/tags/pppoe/","section":"tags","tags":null,"title":"Pppoe"},{"body":"","link":"https://blog.bsdguy.org/tags/autenticacion/","section":"tags","tags":null,"title":"Autenticacion"},{"body":"RADIUS (Remote Authentication Dial-In User Service) es el estándar de facto para centralizar la autenticación, autorización y contabilidad (AAA) de usuarios en redes de acceso. En ISPs pequeños y medianos, la combinación FreeRADIUS + MariaDB + daloRADIUS ofrece un stack robusto y gratuito que puede manejar miles de sesiones PPPoE. Este post cubre la instalación completa desde cero en Debian/Ubuntu, con un servidor PPPoE MikroTik como NAS.\nArquitectura 1Suscriptor DSL/FTTH 2 │ 3 ▼ 4 CPE (modem/bridge) 5 │ PPPoE 6 ▼ 7┌─────────────────┐ RADIUS (UDP 1812/1813) ┌────────────────────┐ 8│ MikroTik L2TP │ ─────────────────────────────────► │ FreeRADIUS │ 9│ / PPPoE Server │ │ + MariaDB │ 10│ (NAS) │ ◄───────────────────────────────── │ (192.168.1.10) │ 11└─────────────────┘ Access-Accept / Reject └────────────────────┘ 12 │ 13 ┌────────────────────┐ 14 │ daloRADIUS │ 15 │ (web UI PHP) │ 16 └────────────────────┘ Componente Rol FreeRADIUS Servidor AAA, procesa solicitudes del NAS MariaDB Backend con usuarios, grupos y atributos daloRADIUS Interfaz web para gestión de usuarios y reportes MikroTik NAS que autentica sesiones PPPoE contra RADIUS Paso 1: Instalar MariaDB y crear la base de datos 1apt update \u0026amp;\u0026amp; apt install -y mariadb-server 2mysql_secure_installation Crear la base de datos y el usuario:\n1mysql -u root -p 2 3CREATE DATABASE radius CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 4CREATE USER \u0026#39;radius\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;RadiusP@ss2026\u0026#39;; 5GRANT ALL PRIVILEGES ON radius.* TO \u0026#39;radius\u0026#39;@\u0026#39;localhost\u0026#39;; 6FLUSH PRIVILEGES; 7EXIT; Paso 2: Instalar FreeRADIUS con módulo SQL 1apt install -y freeradius freeradius-mysql freeradius-utils Importar el esquema SQL FreeRADIUS incluye los scripts de esquema en /etc/freeradius/3.0/mods-config/sql/main/mysql/:\n1mysql -u radius -p radius \u0026lt; /etc/freeradius/3.0/mods-config/sql/main/mysql/schema.sql Esto crea las tablas fundamentales:\nTabla Propósito radcheck Atributos de autenticación por usuario (contraseña) radreply Atributos que RADIUS devuelve al NAS tras el Accept radgroupcheck Atributos de verificación por grupo radgroupreply Atributos de respuesta por grupo (pool IP, etc.) radusergroup Asignación de usuarios a grupos radacct Contabilidad de sesiones (Accounting) nas Dispositivos NAS autorizados Configurar el módulo SQL Habilitar el módulo:\n1ln -s /etc/freeradius/3.0/mods-available/sql /etc/freeradius/3.0/mods-enabled/sql Editar /etc/freeradius/3.0/mods-available/sql:\n1sql { 2 dialect = \u0026#34;mysql\u0026#34; 3 4 driver = \u0026#34;rlm_sql_mysql\u0026#34; 5 6 server = \u0026#34;localhost\u0026#34; 7 port = 3306 8 login = \u0026#34;radius\u0026#34; 9 password = \u0026#34;RadiusP@ss2026\u0026#34; 10 radius_db = \u0026#34;radius\u0026#34; 11 12 # Leer usuarios de la tabla radcheck 13 read_clients = yes 14 client_table = \u0026#34;nas\u0026#34; 15 16 pool { 17 start = 5 18 min = 3 19 max = 32 20 spare = 10 21 uses = 0 22 lifetime = 0 23 idle_timeout = 60 24 } 25} Activar SQL en los sitios virtuales En /etc/freeradius/3.0/sites-enabled/default, dentro de las secciones authorize, accounting y session, descomentar o agregar sql:\n1authorize { 2 filter_username 3 preprocess 4 chap 5 mschap 6 digest 7 suffix 8 eap { 9 ok = return 10 } 11 sql # ← agregar/descomentar 12 pap 13} 14 15accounting { 16 detail 17 sql # ← agregar/descomentar 18 exec 19 attr_filter.accounting_response 20} 21 22session { 23 sql # ← agregar/descomentar 24} Lo mismo aplica para /etc/freeradius/3.0/sites-enabled/inner-tunnel si se usa EAP.\nPaso 3: Registrar el NAS MikroTik Insertar el NAS en la tabla nas para que FreeRADIUS acepte sus solicitudes:\n1mysql -u radius -p radius 2 3INSERT INTO nas (nasname, shortname, type, secret, description) 4VALUES (\u0026#39;192.168.1.1\u0026#39;, \u0026#39;MikroTik-PPPoE\u0026#39;, \u0026#39;other\u0026#39;, \u0026#39;SecretShared123\u0026#39;, \u0026#39;Router PPPoE principal\u0026#39;); El campo secret debe coincidir exactamente con el configurado en el cliente RADIUS del MikroTik.\nPaso 4: Agregar un usuario de prueba 1-- Contraseña en texto claro (Cleartext-Password) 2INSERT INTO radcheck (username, attribute, op, value) 3VALUES (\u0026#39;testuser\u0026#39;, \u0026#39;Cleartext-Password\u0026#39;, \u0026#39;:=\u0026#39;, \u0026#39;TestPass2026\u0026#39;); 4 5-- Asignar al grupo \u0026#34;pppoe-users\u0026#34; 6INSERT INTO radusergroup (username, groupname, priority) 7VALUES (\u0026#39;testuser\u0026#39;, \u0026#39;pppoe-users\u0026#39;, 1); Definir atributos de respuesta del grupo (pool de IPs):\n1INSERT INTO radgroupreply (groupname, attribute, op, value) 2VALUES 3 (\u0026#39;pppoe-users\u0026#39;, \u0026#39;Framed-Pool\u0026#39;, \u0026#39;:=\u0026#39;, \u0026#39;pool-clientes\u0026#39;), 4 (\u0026#39;pppoe-users\u0026#39;, \u0026#39;Session-Timeout\u0026#39;, \u0026#39;:=\u0026#39;, \u0026#39;86400\u0026#39;), 5 (\u0026#39;pppoe-users\u0026#39;, \u0026#39;Idle-Timeout\u0026#39;, \u0026#39;:=\u0026#39;, \u0026#39;3600\u0026#39;); Paso 5: Verificar FreeRADIUS en modo debug Detener el servicio y correr en debug para ver el flujo completo:\n1systemctl stop freeradius 2freeradius -X En otra terminal, probar con radtest:\n1radtest testuser TestPass2026 127.0.0.1 0 testing123 Respuesta esperada:\n1Sending Access-Request of id 123 to 127.0.0.1 port 1812 2 User-Name = \u0026#34;testuser\u0026#34; 3 User-Password = \u0026#34;TestPass2026\u0026#34; 4 ... 5Received Access-Accept of id 123 from 127.0.0.1 port 1812 6 Framed-Pool = \u0026#34;pool-clientes\u0026#34; 7 Session-Timeout = 86400 Si obtienes Access-Accept, FreeRADIUS está operando correctamente. Iniciar el servicio:\n1systemctl enable --now freeradius Paso 6: Instalar daloRADIUS daloRADIUS es una interfaz web PHP para gestionar usuarios, NAS y revisar reportes de contabilidad.\nDependencias 1apt install -y apache2 php php-mysql php-gd php-curl php-mail php-mail-mime \\ 2 php-pear php-db libapache2-mod-php unzip curl Descargar e instalar 1cd /var/www/html 2curl -LO https://github.com/lirantal/daloradius/archive/refs/heads/master.zip 3unzip master.zip 4mv daloradius-master daloradius 5chown -R www-data:www-data /var/www/html/daloradius 6chmod -R 755 /var/www/html/daloradius Importar esquema adicional de daloRADIUS 1mysql -u radius -p radius \u0026lt; /var/www/html/daloradius/contrib/db/mysql-daloradius.sql Configurar la conexión a la base de datos Editar /var/www/html/daloradius/library/daloradius.conf.php:\n1$configValues[\u0026#39;FREERADIUS_VERSION\u0026#39;] = \u0026#39;3\u0026#39;; 2$configValues[\u0026#39;DB_ENGINE\u0026#39;] = \u0026#39;mysqli\u0026#39;; 3$configValues[\u0026#39;DB_HOST\u0026#39;] = \u0026#39;localhost\u0026#39;; 4$configValues[\u0026#39;DB_PORT\u0026#39;] = \u0026#39;3306\u0026#39;; 5$configValues[\u0026#39;DB_USER\u0026#39;] = \u0026#39;radius\u0026#39;; 6$configValues[\u0026#39;DB_PASS\u0026#39;] = \u0026#39;RadiusP@ss2026\u0026#39;; 7$configValues[\u0026#39;DB_NAME\u0026#39;] = \u0026#39;radius\u0026#39;; VirtualHost de Apache (opcional, dominio dedicado) Crear /etc/apache2/sites-available/daloradius.conf:\n1\u0026lt;VirtualHost *:80\u0026gt; 2 ServerName radius.midominio.com 3 DocumentRoot /var/www/html/daloradius 4 5 \u0026lt;Directory /var/www/html/daloradius\u0026gt; 6 Options -Indexes 7 AllowOverride All 8 Require all granted 9 \u0026lt;/Directory\u0026gt; 10 11 ErrorLog ${APACHE_LOG_DIR}/daloradius_error.log 12 CustomLog ${APACHE_LOG_DIR}/daloradius_access.log combined 13\u0026lt;/VirtualHost\u0026gt; 1a2ensite daloradius 2a2enmod rewrite 3systemctl reload apache2 Acceder a http://192.168.1.10/daloradius con las credenciales por defecto:\nCampo Valor Usuario administrator Password radius Cambiar la contraseña inmediatamente en Config → Operators → Manage Operators.\nPaso 7: Configurar MikroTik como cliente RADIUS En el RouterOS del MikroTik (PPPoE Server):\n1# Agregar el servidor RADIUS 2/radius add \\ 3 address=192.168.1.10 \\ 4 secret=SecretShared123 \\ 5 service=ppp \\ 6 authentication-port=1812 \\ 7 accounting-port=1813 8 9# Activar uso de RADIUS en el perfil PPPoE 10/ppp aaa set use-radius=yes accounting=yes 11 12# Verificar 13/radius print Confirmar que el perfil de PPPoE apunta a RADIUS:\n1/ppp profile set default use-radius=yes Pool de IPs administrado por RADIUS Si RADIUS devuelve el atributo Framed-Pool, el MikroTik asignará una IP del pool local con ese nombre:\n1/ip pool add name=pool-clientes ranges=10.20.0.1-10.20.0.254 Paso 8: Probar una sesión PPPoE completa Conectar un cliente PPPoE con usuario testuser / TestPass2026. En FreeRADIUS logs (/var/log/freeradius/radius.log) se verá:\n1Login OK: [testuser] (from client MikroTik-PPPoE port 0 via TLS tunnel) En MikroTik verificar la sesión activa:\n1/ppp active print En daloRADIUS ir a Accounting → Active Sessions para ver la sesión en tiempo real.\nAtributos RADIUS útiles para PPPoE Atributo RADIUS Efecto en MikroTik Framed-Pool Pool de IPs del que se asigna la dirección Framed-IP-Address IP fija para el usuario Session-Timeout Tiempo máximo de sesión en segundos Idle-Timeout Desconexión por inactividad Mikrotik-Rate-Limit Límite de velocidad (ej. 10M/10M) Mikrotik-Address-List Agrega la IP del usuario a una lista Ejemplo para limitar velocidad a 10 Mbps simétrico via radgroupreply:\n1INSERT INTO radgroupreply (groupname, attribute, op, value) 2VALUES (\u0026#39;plan-10mbps\u0026#39;, \u0026#39;Mikrotik-Rate-Limit\u0026#39;, \u0026#39;:=\u0026#39;, \u0026#39;10M/10M\u0026#39;); Solución de problemas comunes Access-Reject sin mensaje claro\nCorrer freeradius -X y buscar la línea ERROR: o WARNING:. Causas frecuentes:\nEl secret del NAS no coincide entre MikroTik y la tabla nas. El módulo SQL no está habilitado en authorize. La contraseña en radcheck usa el operador incorrecto (debe ser :=, no ==). daloRADIUS muestra error de conexión a DB\n1# Probar conexión directa 2mysql -u radius -p radius -e \u0026#34;SHOW TABLES;\u0026#34; Si falla, revisar permisos del usuario radius en MariaDB.\nMikroTik no envía Accounting\nVerificar en /ppp aaa:\n1/ppp aaa print 2 use-radius: yes 3 accounting: yes Si accounting: no, los registros de sesión no se guardarán en radacct.\nConsideraciones de seguridad Usar un secret compartido largo y aleatorio (mínimo 20 caracteres). Limitar el acceso al puerto 1812/1813 UDP únicamente desde las IPs de los NAS (ufw allow from 192.168.1.1 to any port 1812,1813 proto udp). Proteger daloRADIUS con HTTPS y autenticación básica de Apache si está expuesto fuera de la red de gestión. Considerar migrar las contraseñas de Cleartext-Password a MD5-Password o usar CHAP/MS-CHAPv2 para no transmitir contraseñas en texto claro entre FreeRADIUS y MariaDB. Con esto tienes un stack RADIUS completamente funcional: FreeRADIUS procesa la autenticación, MariaDB persiste usuarios y sesiones, daloRADIUS da visibilidad operativa, y el MikroTik delega toda la AAA al servidor central. Desde aquí puedes escalar agregando más NAS a la tabla nas, crear grupos por plan de velocidad, y automatizar altas de usuarios desde daloRADIUS o directamente vía SQL.\n","link":"https://blog.bsdguy.org/posts/freeradius-daloradius-pppoe/","section":"posts","tags":["radius","freeradius","daloradius","pppoe","isp","autenticacion","mikrotik","linux"],"title":"Configurando RADIUS con FreeRADIUS y daloRADIUS para autenticación PPPoE"},{"body":"","link":"https://blog.bsdguy.org/tags/daloradius/","section":"tags","tags":null,"title":"Daloradius"},{"body":"","link":"https://blog.bsdguy.org/tags/freeradius/","section":"tags","tags":null,"title":"Freeradius"},{"body":"","link":"https://blog.bsdguy.org/tags/mikrotik/","section":"tags","tags":null,"title":"Mikrotik"},{"body":"","link":"https://blog.bsdguy.org/tags/radius/","section":"tags","tags":null,"title":"Radius"},{"body":"","link":"https://blog.bsdguy.org/tags/dhcp/","section":"tags","tags":null,"title":"Dhcp"},{"body":"","link":"https://blog.bsdguy.org/tags/networking/","section":"tags","tags":null,"title":"Networking"},{"body":"","link":"https://blog.bsdguy.org/tags/vlan/","section":"tags","tags":null,"title":"Vlan"},{"body":"VyOS incluye un servidor DHCP completo basado en ISC DHCP. La configuración vive bajo set service dhcp-server y sigue el mismo modelo jerárquico que el resto del sistema: se edita en modo configuración, se valida con commit y se persiste con save.\nEste post parte del escenario definido en VyOS: Primera Configuración y agrega DHCP para la LAN principal y dos VLANs.\nEscenario de referencia 1Internet 2 │ 3 eth0 (WAN) 4 │ 5 [VyOS] 6 │ 7 eth1 LAN principal — 192.168.1.1/24 8 eth1.10 VLAN 10 Servidores — 10.10.10.1/24 9 eth1.20 VLAN 20 Usuarios — 10.10.20.1/24 Cada segmento necesita su propio servidor DHCP, porque VyOS actúa como gateway de cada red.\n1. DHCP para la LAN principal El bloque mínimo de configuración requiere:\nUn nombre de shared-network (agrupa subredes bajo un mismo servidor) La subred con su máscara El rango de IPs a asignar El router (gateway que se entrega al cliente) El tiempo de lease 1configure 2 3set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 range 0 start \u0026#39;192.168.1.100\u0026#39; 4set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 range 0 stop \u0026#39;192.168.1.200\u0026#39; 5set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 default-router \u0026#39;192.168.1.1\u0026#39; 6set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 lease \u0026#39;86400\u0026#39; 7 8commit 9save El lease de 86400 segundos equivale a 24 horas. Para redes de oficina o hogar es un valor razonable; para redes de invitados con alta rotación, algo entre 3600 y 7200 es más adecuado.\nAgregar servidores DNS 1set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 name-server \u0026#39;1.1.1.1\u0026#39; 2set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 name-server \u0026#39;8.8.8.8\u0026#39; 3 4commit 5save Se pueden declarar múltiples name-server. Los clientes los reciben en el orden en que se configuran.\nAgregar sufijo de dominio 1set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 domain-name \u0026#39;corp.local\u0026#39; 2 3commit 4save Con esto los clientes resuelven nombres cortos (server1) como server1.corp.local.\n2. DHCP para las VLANs Cada VLAN es una subred independiente. Se pueden agrupar bajo el mismo shared-network o en redes separadas. Usar redes separadas por VLAN es más claro y facilita el troubleshooting.\nVLAN 10 — Servidores 1set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 range 0 start \u0026#39;10.10.10.10\u0026#39; 2set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 range 0 stop \u0026#39;10.10.10.50\u0026#39; 3set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 default-router \u0026#39;10.10.10.1\u0026#39; 4set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 lease \u0026#39;86400\u0026#39; 5set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 name-server \u0026#39;10.10.10.1\u0026#39; 6set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 domain-name \u0026#39;srv.corp.local\u0026#39; 7 8commit 9save El rango 10.10.10.10 – 10.10.10.50 deja el espacio 10.10.10.51 – 10.10.10.254 libre para asignaciones estáticas o equipos configurados a mano.\nVLAN 20 — Usuarios 1set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 range 0 start \u0026#39;10.10.20.50\u0026#39; 2set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 range 0 stop \u0026#39;10.10.20.200\u0026#39; 3set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 default-router \u0026#39;10.10.20.1\u0026#39; 4set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 lease \u0026#39;28800\u0026#39; 5set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 name-server \u0026#39;1.1.1.1\u0026#39; 6set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 name-server \u0026#39;8.8.8.8\u0026#39; 7set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 domain-name \u0026#39;corp.local\u0026#39; 8 9commit 10save Lease de 28800 (8 horas): apropiado para estaciones de trabajo en jornada laboral.\n3. Reservas estáticas (static mappings) Una reserva vincula una MAC address a una IP fija. El cliente sigue usando DHCP pero siempre recibe la misma dirección. Esto es preferible a configurar la IP directamente en el equipo porque la gestión queda centralizada en el router.\nReserva para un servidor en VLAN 10 1set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 static-mapping srv-nfs mac \u0026#39;aa:bb:cc:dd:ee:01\u0026#39; 2set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 static-mapping srv-nfs ip-address \u0026#39;10.10.10.100\u0026#39; 3 4commit 5save El nombre srv-nfs es una etiqueta local; no tiene que coincidir con el hostname del equipo, pero ayuda a identificar la reserva.\nReserva con opciones adicionales Es posible sobreescribir DNS y gateway por reserva individual. Útil para equipos que deben usar un DNS interno diferente:\n1set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 static-mapping srv-dc mac \u0026#39;aa:bb:cc:dd:ee:02\u0026#39; 2set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 static-mapping srv-dc ip-address \u0026#39;10.10.10.101\u0026#39; 3set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 static-mapping srv-dc name-server \u0026#39;10.10.10.101\u0026#39; 4 5commit 6save Nota: La IP de una reserva puede estar dentro o fuera del rango dinámico. Fuera del rango es lo más seguro para evitar conflictos si el pool se agota.\n4. Opciones DHCP avanzadas Opciones personalizadas (option 43, 66, 67, etc.) VyOS permite inyectar opciones DHCP arbitrarias por su número. Por ejemplo, para entregar la IP de un servidor TFTP (opción 66) a teléfonos IP:\n1set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 bootfile-server \u0026#39;10.10.10.5\u0026#39; 2set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 bootfile-name \u0026#39;firmware.bin\u0026#39; Múltiples rangos en la misma subred Si se quiere reservar un bloque intermedio (por ejemplo, para impresoras con IP fija entre .50 y .99):\n1set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 range 0 start \u0026#39;192.168.1.100\u0026#39; 2set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 range 0 stop \u0026#39;192.168.1.149\u0026#39; 3set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 range 1 start \u0026#39;192.168.1.200\u0026#39; 4set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 range 1 stop \u0026#39;192.168.1.250\u0026#39; 5 6commit 7save El bloque 192.168.1.150 – 192.168.1.199 queda libre para asignación manual.\n5. Verificación y troubleshooting Ver leases activos 1show dhcp server leases Muestra IP asignada, MAC, hostname del cliente y tiempo de expiración.\n1show dhcp server leases pool VLAN20 Filtra por pool específico.\nVer estadísticas del servidor 1show dhcp server statistics Útil para detectar pools exhaustos (pool full) antes de que los usuarios reporten problemas.\nVer la configuración actual 1show service dhcp-server Liberar un lease manualmente 1clear dhcp server lease ip 192.168.1.105 Útil cuando un equipo cambió de MAC y el lease anterior todavía está activo.\nVerificar desde el cliente (Linux) 1# Renovar lease 2sudo dhclient -r eth0 \u0026amp;\u0026amp; sudo dhclient eth0 3 4# Ver qué IP y opciones se recibieron 5ip addr show eth0 6cat /etc/resolv.conf Logs del servidor DHCP 1show log dhcp server O en tiempo real:\n1monitor log | match DHCP 6. Ejemplo completo — configuración final Resumen de todo lo anterior en un bloque limpio para copiar y adaptar:\n1configure 2 3# --- LAN principal --- 4set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 range 0 start \u0026#39;192.168.1.100\u0026#39; 5set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 range 0 stop \u0026#39;192.168.1.200\u0026#39; 6set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 default-router \u0026#39;192.168.1.1\u0026#39; 7set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 name-server \u0026#39;1.1.1.1\u0026#39; 8set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 name-server \u0026#39;8.8.8.8\u0026#39; 9set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 domain-name \u0026#39;corp.local\u0026#39; 10set service dhcp-server shared-network-name LAN subnet 192.168.1.0/24 lease \u0026#39;86400\u0026#39; 11 12# --- VLAN 10 Servidores --- 13set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 range 0 start \u0026#39;10.10.10.10\u0026#39; 14set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 range 0 stop \u0026#39;10.10.10.50\u0026#39; 15set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 default-router \u0026#39;10.10.10.1\u0026#39; 16set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 name-server \u0026#39;10.10.10.1\u0026#39; 17set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 domain-name \u0026#39;srv.corp.local\u0026#39; 18set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 lease \u0026#39;86400\u0026#39; 19 20# Reservas VLAN 10 21set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 static-mapping srv-nfs mac \u0026#39;aa:bb:cc:dd:ee:01\u0026#39; 22set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 static-mapping srv-nfs ip-address \u0026#39;10.10.10.100\u0026#39; 23set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 static-mapping srv-dc mac \u0026#39;aa:bb:cc:dd:ee:02\u0026#39; 24set service dhcp-server shared-network-name VLAN10 subnet 10.10.10.0/24 static-mapping srv-dc ip-address \u0026#39;10.10.10.101\u0026#39; 25 26# --- VLAN 20 Usuarios --- 27set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 range 0 start \u0026#39;10.10.20.50\u0026#39; 28set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 range 0 stop \u0026#39;10.10.20.200\u0026#39; 29set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 default-router \u0026#39;10.10.20.1\u0026#39; 30set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 name-server \u0026#39;1.1.1.1\u0026#39; 31set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 name-server \u0026#39;8.8.8.8\u0026#39; 32set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 domain-name \u0026#39;corp.local\u0026#39; 33set service dhcp-server shared-network-name VLAN20 subnet 10.10.20.0/24 lease \u0026#39;28800\u0026#39; 34 35commit 36save Resumen de comandos útiles Tarea Comando Ver todos los leases show dhcp server leases Ver leases de un pool show dhcp server leases pool VLAN10 Ver estadísticas show dhcp server statistics Ver configuración show service dhcp-server Liberar lease por IP clear dhcp server lease ip \u0026lt;IP\u0026gt; Ver logs en tiempo real monitor log | match DHCP Próximos pasos Con DHCP en funcionamiento, los siguientes pasos naturales son:\nDNS Forwarder local (set service dns forwarding) para que VyOS resuelva nombres internos y reenvíe el resto DHCP Relay si tienes switches de capa 3 y los servidores DHCP están en otra subred IPv6 con DHCPv6 (set service dhcpv6-server) o SLAAC para dual-stack ","link":"https://blog.bsdguy.org/posts/vyos-dhcp-server/","section":"posts","tags":["vyos","dhcp","router","vlan","networking","linux"],"title":"VyOS: Configurar un Servidor DHCP con Ejemplos Prácticos"},{"body":"","link":"https://blog.bsdguy.org/tags/static-route/","section":"tags","tags":null,"title":"Static-Route"},{"body":"VyOS es un router/firewall de código abierto basado en Debian. A diferencia de equipos como MikroTik o Cisco, toda la configuración vive en un árbol jerárquico que se edita en modo de configuración y se confirma con commit. Este modelo — inspirado en Junos — hace que los cambios sean atómicos y reversibles, lo cual es una ventaja real en producción.\nEste post cubre los cuatro bloques fundamentales para dejar un router VyOS operativo: NAT, Firewall, Rutas Estáticas y VLANs.\nEscenario de referencia 1Internet 2 │ 3 eth0 (WAN — IP pública o DHCP del ISP) 4 │ 5 [VyOS] 6 │ 7 eth1 (LAN — 192.168.1.1/24) 8 │ 9 eth1.10 VLAN 10 — Servidores (10.10.10.1/24) 10 eth1.20 VLAN 20 — Usuarios (10.10.20.1/24) 1. Configuración inicial de interfaces Entrar al modo de configuración es siempre el primer paso:\n1configure Asignar IPs a las interfaces:\n1set interfaces ethernet eth0 description \u0026#39;WAN\u0026#39; 2set interfaces ethernet eth0 address dhcp 3 4set interfaces ethernet eth1 description \u0026#39;LAN\u0026#39; 5set interfaces ethernet eth1 address \u0026#39;192.168.1.1/24\u0026#39; Confirmar y guardar:\n1commit 2save Tip: compare antes de commit muestra exactamente qué cambiará. Útil para auditar en sesiones largas.\n2. NAT — Masquerade hacia WAN VyOS implementa NAT como reglas numeradas. Para salida a internet desde cualquier subred interna:\n1set nat source rule 10 description \u0026#39;LAN a Internet\u0026#39; 2set nat source rule 10 outbound-interface name \u0026#39;eth0\u0026#39; 3set nat source rule 10 source address \u0026#39;192.168.0.0/16\u0026#39; 4set nat source rule 10 translation address masquerade 5 6commit 7save El bloque 192.168.0.0/16 cubre la LAN y las VLANs que se agregarán más adelante. Si preferís ser más estricto, podés crear una regla por subred.\nVerificar:\n1show nat source rules 2show nat source translations 3. Firewall por zonas VyOS permite un modelo de firewall basado en zonas, donde cada interfaz pertenece a una zona y se define la política entre pares de zonas. Es más ordenado que aplicar reglas sueltas por interfaz.\nDefinir zonas 1set firewall zone WAN interface eth0 2set firewall zone LAN interface eth1 3set firewall zone LOCAL local-zone local-zone es el propio router (tráfico que termina o sale de VyOS mismo).\nPolítica WAN → LOCAL (acceso al router desde internet) Por defecto denegar todo; permitir solo ICMP y respuestas de conexiones establecidas:\n1set firewall ipv4 name WAN-TO-LOCAL default-action drop 2set firewall ipv4 name WAN-TO-LOCAL rule 10 action accept 3set firewall ipv4 name WAN-TO-LOCAL rule 10 state established 4set firewall ipv4 name WAN-TO-LOCAL rule 10 state related 5set firewall ipv4 name WAN-TO-LOCAL rule 20 action accept 6set firewall ipv4 name WAN-TO-LOCAL rule 20 protocol icmp 7 8set firewall zone LOCAL from WAN firewall name WAN-TO-LOCAL Política WAN → LAN (tráfico entrante hacia la red) 1set firewall ipv4 name WAN-TO-LAN default-action drop 2set firewall ipv4 name WAN-TO-LAN rule 10 action accept 3set firewall ipv4 name WAN-TO-LAN rule 10 state established 4set firewall ipv4 name WAN-TO-LAN rule 10 state related 5 6set firewall zone LAN from WAN firewall name WAN-TO-LAN Política LAN → WAN (tráfico saliente de la LAN) 1set firewall ipv4 name LAN-TO-WAN default-action accept 2 3set firewall zone WAN from LAN firewall name LAN-TO-WAN 1commit 2save Verificar:\n1show firewall zones 2show firewall ipv4 name WAN-TO-LOCAL statistics 4. Rutas estáticas Para redes que no son directamente conectadas ni aprendidas por un protocolo de ruteo dinámico.\nRuta por defecto manual (si eth0 no usa DHCP) 1set protocols static route 0.0.0.0/0 next-hop 203.0.113.1 Ruta hacia una red remota detrás de otro router Supongamos que 192.168.100.0/24 está detrás de un router en 192.168.1.254:\n1set protocols static route 192.168.100.0/24 next-hop 192.168.1.254 Ruta de descarte (blackhole) Útil para agregar supernets y evitar loops de ruteo:\n1set protocols static route 10.10.0.0/16 blackhole distance 254 1commit 2save Verificar:\n1show ip route 2show ip route static 5. VLANs — Subinterfaces 802.1Q VyOS crea VLANs como subinterfaces ethn.vlan-id sobre la interfaz troncal.\nCrear subinterfaces 1set interfaces ethernet eth1 vif 10 description \u0026#39;Servidores\u0026#39; 2set interfaces ethernet eth1 vif 10 address \u0026#39;10.10.10.1/24\u0026#39; 3 4set interfaces ethernet eth1 vif 20 description \u0026#39;Usuarios\u0026#39; 5set interfaces ethernet eth1 vif 20 address \u0026#39;10.10.20.1/24\u0026#39; Agregar las VLANs a las zonas de firewall 1set firewall zone LAN interface eth1.10 2set firewall zone LAN interface eth1.20 Extender el NAT para las nuevas subredes La regla de NAT ya usa 192.168.0.0/16, así que las subredes 10.10.x.x quedan fuera. Agregar una regla adicional:\n1set nat source rule 20 description \u0026#39;VLANs a Internet\u0026#39; 2set nat source rule 20 outbound-interface name \u0026#39;eth0\u0026#39; 3set nat source rule 20 source address \u0026#39;10.10.0.0/16\u0026#39; 4set nat source rule 20 translation address masquerade 1commit 2save Verificar:\n1show interfaces 2show interfaces ethernet eth1 vif 3ping 10.10.10.1 from-address 10.10.20.1 Resumen de comandos útiles Tarea Comando Ver configuración activa show configuration Ver diff sin hacer commit compare Revertir cambios sin commit discard Rollback al commit anterior rollback 1 (en modo config) Ver tabla de ruteo show ip route Ver traducciones NAT activas show nat source translations Ver logs del firewall show log firewall Próximos pasos Con esta base podés avanzar hacia:\nDHCP Server por VLAN (set service dhcp-server) DNS Forwarder local (set service dns forwarding) BGP / OSPF con el stack set protocols bgp o set protocols ospf WireGuard o IPsec para acceso remoto VyOS tiene la ventaja de que la curva de aprendizaje inicial es confusa, pero una vez que entiendes el modelo de configure → set → commit → save, la configuración escala de forma muy limpia.\n","link":"https://blog.bsdguy.org/posts/vyos-primera-configuracion/","section":"posts","tags":["vyos","router","nat","firewall","vlan","static-route","linux"],"title":"VyOS: Primera Configuración — NAT, Firewall, Rutas Estáticas y VLANs"},{"body":"El balanceo de dos enlaces WAN en MikroTik es uno de esos temas donde la documentación oficial muestra el esqueleto pero omite los detalles que hacen que la configuración sea estable en producción. Este post cubre la implementación completa con Per-Connection Classifier (PCC), failover automático con Netwatch, y las reglas de mangle necesarias para que el tráfico saliente y las conexiones establecidas se comporten correctamente.\nEscenario 1ISP1 ─── ether1 (100 Mbps) ┐ 2 ├─── MikroTik ─── LAN (192.168.1.0/24) 3ISP2 ─── ether2 (100 Mbps) ┘ Variable ISP1 ISP2 Interfaz ether1 ether2 IP del router 203.0.113.2/30 198.51.100.2/30 Gateway 203.0.113.1 198.51.100.1 Ajusta las IPs a tu entorno. El procedimiento es idéntico con direcciones DHCP — solo cambia cómo obtienes el gateway.\n1. Tablas de enrutamiento RouterOS v7 tiene soporte nativo para múltiples routing tables. Creamos una tabla por WAN:\n1/routing table 2add name=wan1 fib 3add name=wan2 fib 2. Rutas por defecto Una ruta default en la tabla principal y una en cada tabla WAN:\n1/ip route 2add dst-address=0.0.0.0/0 gateway=203.0.113.1 routing-table=main distance=1 check-gateway=ping comment=\u0026#34;WAN1 default\u0026#34; 3add dst-address=0.0.0.0/0 gateway=198.51.100.1 routing-table=main distance=2 check-gateway=ping comment=\u0026#34;WAN2 default (failover)\u0026#34; 4 5add dst-address=0.0.0.0/0 gateway=203.0.113.1 routing-table=wan1 comment=\u0026#34;WAN1 tabla propia\u0026#34; 6add dst-address=0.0.0.0/0 gateway=198.51.100.1 routing-table=wan2 comment=\u0026#34;WAN2 tabla propia\u0026#34; El distance en la tabla main da failover puro si no activas el balanceo. Con PCC reemplazamos este comportamiento para repartir tráfico entre ambos.\ncheck-gateway=ping es crítico: RouterOS elimina la ruta de la tabla cuando el gateway no responde, lo que activa el failover automático sin necesidad de scripts adicionales.\n3. Mangle: marcado de conexiones con PCC PCC divide las conexiones en grupos según un hash de la tupla src/dst. Con per-connection-classifier=both-addresses:2/0 y /2/1 repartimos al 50%.\n1/ip firewall mangle 2 3# --- Tráfico que ya tiene marca de conexión: seguir por el mismo enlace --- 4add chain=prerouting connection-state=established,related \\ 5 connection-mark=via-wan1 action=mark-routing new-routing-mark=wan1 \\ 6 passthrough=no comment=\u0026#34;Established: seguir por WAN1\u0026#34; 7 8add chain=prerouting connection-state=established,related \\ 9 connection-mark=via-wan2 action=mark-routing new-routing-mark=wan2 \\ 10 passthrough=no comment=\u0026#34;Established: seguir por WAN2\u0026#34; 11 12# --- Nuevas conexiones: balanceo PCC --- 13add chain=prerouting connection-state=new in-interface=!ether1 in-interface=!ether2 \\ 14 per-connection-classifier=both-addresses:2/0 \\ 15 action=mark-connection new-connection-mark=via-wan1 passthrough=yes \\ 16 comment=\u0026#34;PCC nuevo → WAN1\u0026#34; 17 18add chain=prerouting connection-state=new in-interface=!ether1 in-interface=!ether2 \\ 19 per-connection-classifier=both-addresses:2/1 \\ 20 action=mark-connection new-connection-mark=via-wan2 passthrough=yes \\ 21 comment=\u0026#34;PCC nuevo → WAN2\u0026#34; 22 23add chain=prerouting connection-mark=via-wan1 \\ 24 action=mark-routing new-routing-mark=wan1 passthrough=no \\ 25 comment=\u0026#34;Rutear conexiones WAN1\u0026#34; 26 27add chain=prerouting connection-mark=via-wan2 \\ 28 action=mark-routing new-routing-mark=wan2 passthrough=no \\ 29 comment=\u0026#34;Rutear conexiones WAN2\u0026#34; 30 31# --- Output (tráfico originado en el propio router) --- 32add chain=output connection-state=established,related \\ 33 connection-mark=via-wan1 action=mark-routing new-routing-mark=wan1 passthrough=no 34 35add chain=output connection-state=established,related \\ 36 connection-mark=via-wan2 action=mark-routing new-routing-mark=wan2 passthrough=no 37 38add chain=output connection-state=new \\ 39 per-connection-classifier=both-addresses:2/0 \\ 40 action=mark-connection new-connection-mark=via-wan1 passthrough=yes 41 42add chain=output connection-state=new \\ 43 per-connection-classifier=both-addresses:2/1 \\ 44 action=mark-connection new-connection-mark=via-wan2 passthrough=yes 45 46add chain=output connection-mark=via-wan1 \\ 47 action=mark-routing new-routing-mark=wan1 passthrough=no 48 49add chain=output connection-mark=via-wan2 \\ 50 action=mark-routing new-routing-mark=wan2 passthrough=no Por qué in-interface=!ether1 y !ether2: el PCC solo debe aplicarse a tráfico que viene de la LAN. El tráfico que entra por las WAN ya tiene una conexión establecida y no debe ser reclasificado.\n4. NAT Masquerade diferenciado por interfaz de salida:\n1/ip firewall nat 2add chain=srcnat out-interface=ether1 action=masquerade comment=\u0026#34;SNAT WAN1\u0026#34; 3add chain=srcnat out-interface=ether2 action=masquerade comment=\u0026#34;SNAT WAN2\u0026#34; No uses action=masquerade global sin out-interface — aplicaría a tráfico interno también.\n5. Failover automático con Netwatch check-gateway=ping en las rutas maneja el failover a nivel de routing table. Sin embargo, si el gateway responde pero no hay conectividad real hacia internet (corte del ISP aguas arriba), la ruta permanece activa. Netwatch resuelve esto verificando un host externo por cada enlace.\n1/tool netwatch 2 3add host=8.8.8.8 interval=10s timeout=2s \\ 4 up-script=\u0026#34;/ip route set [find comment=\\\u0026#34;WAN1 default\\\u0026#34;] distance=1\u0026#34; \\ 5 down-script=\u0026#34;/ip route set [find comment=\\\u0026#34;WAN1 default\\\u0026#34;] distance=10\u0026#34; \\ 6 comment=\u0026#34;Monitor WAN1 via 8.8.8.8\u0026#34; 7 8add host=1.1.1.1 interval=10s timeout=2s \\ 9 up-script=\u0026#34;/ip route set [find comment=\\\u0026#34;WAN2 default\\\u0026#34;] distance=2\u0026#34; \\ 10 down-script=\u0026#34;/ip route set [find comment=\\\u0026#34;WAN2 default\\\u0026#34;] distance=10\u0026#34; \\ 11 comment=\u0026#34;Monitor WAN2 via 1.1.1.1\u0026#34; Cuando WAN1 cae, su distancia sube a 10 y todo el tráfico sale por WAN2 (distancia 2). Cuando vuelve, la distancia regresa a 1 y el balanceo se reanuda.\nUsa hosts de monitoreo distintos por WAN para evitar falsos positivos. Si usas el mismo host (8.8.8.8) para ambas, un bloqueo puntual de ese IP derribaría los dos enlaces simultáneamente en tu tabla de rutas.\n6. Tráfico que NO debe balancearse Algunos servicios rompen si la conexión migra de IP pública. Fíjalos a un enlace específico antes de las reglas PCC:\n1/ip firewall mangle 2 3# VPN saliente siempre por WAN1 4add chain=prerouting dst-port=1194 protocol=udp \\ 5 action=mark-connection new-connection-mark=via-wan1 passthrough=yes \\ 6 comment=\u0026#34;OpenVPN fijo a WAN1\u0026#34; 7 8# Tráfico hacia una red corporativa específica siempre por WAN2 9add chain=prerouting dst-address=10.0.0.0/8 \\ 10 action=mark-connection new-connection-mark=via-wan2 passthrough=yes \\ 11 comment=\u0026#34;Red corporativa fija a WAN2\u0026#34; Coloca estas reglas antes de las reglas PCC en la cadena prerouting. El orden en mangle es secuencial.\n7. Verificación Confirmar que ambas rutas están activas:\n1/ip route print where dst-address=0.0.0.0/0 Debes ver ambas rutas con la flag A (active). Si una cae por check-gateway, aparecerá sin A.\nVer marcas de conexión activas:\n1/ip firewall connection print where connection-mark~\u0026#34;via-wan\u0026#34; Verificar distribución de tráfico:\n1/interface monitor-traffic ether1,ether2 interval=1 Simular caída de WAN1:\n1/ip route set [find comment=\u0026#34;WAN1 default\u0026#34;] distance=10 Verifica que el tráfico migre a ether2, luego restaura:\n1/ip route set [find comment=\u0026#34;WAN1 default\u0026#34;] distance=1 Consideraciones de producción Sesiones TCP largas: PCC garantiza que una misma conexión siempre use el mismo enlace, pero si el enlace cae mientras la conexión está activa, esa sesión TCP muere. No hay solución a esto sin ECMP a nivel de sesión o un proxy intermedio. El cliente simplemente reconectará.\nLinks asimétricos: Si WAN1 tiene 100 Mbps y WAN2 tiene 50 Mbps, ajusta el PCC para repartir en proporción 2:1:\n1# Divide en 3 grupos: 0 y 1 van a WAN1, 2 va a WAN2 2per-connection-classifier=both-addresses:3/0 → WAN1 3per-connection-classifier=both-addresses:3/1 → WAN1 4per-connection-classifier=both-addresses:3/2 → WAN2 IPv6: Si tus ISPs entregan prefijos IPv6, replica la lógica en /ipv6 firewall mangle y /ipv6 route. PCC funciona igual sobre IPv6.\nConexiones entrantes (servidores publicados): el balanceo aplica solo a tráfico saliente. Si publicas servicios, usa DNS con TTL bajo apuntando a una sola IP pública, o un proveedor de DNS con failover (Cloudflare, Route 53) que cambie el registro cuando detecte que la IP no responde.\n","link":"https://blog.bsdguy.org/posts/mikrotik-dual-wan-balanceo/","section":"posts","tags":["mikrotik","routeros","wan","balanceo","firewall","mangle"],"title":"Dual WAN en MikroTik RouterOS v7: Balanceo y Failover con PCC"},{"body":"","link":"https://blog.bsdguy.org/tags/ip/","section":"tags","tags":null,"title":"Ip"},{"body":"","link":"https://blog.bsdguy.org/tags/mangle/","section":"tags","tags":null,"title":"Mangle"},{"body":"OPNsense es un firewall serio. Pero la GUI puede generar confusión sobre el orden correcto de operaciones — especialmente cuando combinas VLANs, interfaces, y reglas de firewall que dependen unas de otras. Este post sigue el orden exacto en que debes hacer cada paso para llegar a una configuración funcional y segura.\nEscenario Un único puerto físico (em1) actúa como trunk hacia un switch administrado. Cinco VLANs para segmentos distintos:\nVLAN ID Red Propósito MGT 10 10.0.10.0/24 Gestión de red SRV 20 10.0.20.0/24 Servidores internos USR 30 10.0.30.0/24 Usuarios / endpoints IOT 40 10.0.40.0/24 Dispositivos IoT DMZ 50 10.0.50.0/24 Servicios publicados El router tiene IP .1 en cada subred. WAN en em0.\n1. Crear las VLANs Interfaces → Other Types → VLAN → Add\nRepite para cada VLAN:\nParent VLAN tag Description em1 10 VLAN_MGT em1 20 VLAN_SRV em1 30 VLAN_USR em1 40 VLAN_IOT em1 50 VLAN_DMZ Guarda cada una. Aún no son interfaces asignadas — son solo subinterfaces lógicas.\n2. Asignar interfaces Interfaces → Assignments → Add\nAsigna cada VLAN a una interfaz con nombre descriptivo:\nNetwork port Interface name em1.10 (VLAN_MGT) MGT em1.20 (VLAN_SRV) SRV em1.30 (VLAN_USR) USR em1.40 (VLAN_IOT) IOT em1.50 (VLAN_DMZ) DMZ Guarda. Ahora aparecen en Interfaces → como entradas editables.\n3. Configurar cada interfaz Interfaces → MGT (repite para cada una):\nEnable: ✓ IPv4 Configuration Type: Static IPv4 IPv4 address: 10.0.10.1 / 24 Block private networks: ✗ (esto va en WAN, no aquí) Block bogon networks: ✗ Guarda y aplica. Haz lo mismo para SRV (.20.1), USR (.30.1), IOT (.40.1), DMZ (.50.1).\nSi no habilitas la interfaz explícitamente, OPNsense no crea la entrada en la tabla de rutas ni activa DHCP para esa red.\n4. DHCP por VLAN Services → ISC DHCPv4 → [Interface]\nPara cada interfaz:\nInterfaz Range start Range end DNS MGT 10.0.10.100 10.0.10.200 10.0.10.1 SRV 10.0.20.100 10.0.20.200 10.0.20.1 USR 10.0.30.100 10.0.30.200 10.0.30.1 IOT 10.0.40.100 10.0.40.200 10.0.40.1 DMZ 10.0.50.100 10.0.50.200 10.0.50.1 Activa Enable DHCP server on [interface] y guarda.\n5. Lógica de firewall: el modelo mental OPNsense evalúa reglas por interfaz de entrada, de arriba hacia abajo, primera coincidencia gana. Hay un deny implícito al final de cada interfaz — no necesitas escribirlo.\nEl objetivo para cada VLAN interna:\nPermitir tráfico hacia internet (cualquier IP que no sea RFC 1918 ni bogon). Bloquear tráfico hacia otras VLANs y redes privadas (aislamiento). Permitir consultas DNS y DHCP hacia el router si usas el resolver local. Bloquear acceso a la GUI de OPNsense desde VLANs no administrativas. El orden importa: coloca los bloques específicos antes de los permisos amplios.\n6. Alias: RFC 1918 y Bogons Antes de escribir reglas, define aliases reutilizables.\nFirewall → Aliases → Add\nAlias: RFC1918\nCampo Valor Name RFC1918 Type Network Networks 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 Description Espacio privado RFC 1918 Alias: VLANS_ALL (todas las subredes propias, útil para reglas de bloqueo)\nCampo Valor Name VLANS_ALL Type Network Networks 10.0.10.0/24, 10.0.20.0/24, 10.0.30.0/24, 10.0.40.0/24, 10.0.50.0/24 Alias: OPNSENSE_GUI (proteger acceso a la gestión)\nCampo Valor Name OPNSENSE_GUI Type Port Ports 443, 80 7. Reglas de firewall por interfaz Las reglas se crean en Firewall → Rules → [Interface].\nPlantilla aplicable a USR, IOT y DMZ Estas tres VLANs son las menos confiables. Aplica este set de reglas en ese orden:\nRegla 1 — Permitir DNS hacia el router (opcional si usas Unbound)\nCampo Valor Action Pass Interface USR / IOT / DMZ Protocol TCP/UDP Source USR net / IOT net / DMZ net Destination This Firewall Dest. Port DNS (53) Description Permitir DNS local Regla 2 — Bloquear acceso a la GUI del firewall\nCampo Valor Action Block Protocol TCP Source *net Destination This Firewall Dest. Port OPNSENSE_GUI Description Bloquear acceso GUI Regla 3 — Bloquear hacia RFC 1918 (aislamiento inter-VLAN)\nCampo Valor Action Block Protocol any Source *net Destination RFC1918 Description Bloquear RFC 1918 Regla 4 — Permitir hacia internet\nCampo Valor Action Pass Protocol any Source *net Destination any Description Permitir internet El orden 2→3→4 es el que importa. La regla 3 bloquea todas las IPs privadas (incluyendo las otras VLANs y el propio router excepto el DNS ya permitido en regla 1). La regla 4 solo alcanza lo que no fue bloqueado antes — es decir, IPs públicas.\nMGT — Interfaz administrativa MGT es la red desde donde administras equipos. Tiene más permisos pero sigue sin acceso directo a IOT ni DMZ sin regla explícita:\nRegla 1 — Permitir todo desde MGT (o limitar por puerto si quieres ser estricto)\nCampo Valor Action Pass Protocol any Source MGT net Destination any Description MGT acceso total Si el principio de mínimo privilegio importa aquí, sustituye \u0026ldquo;any\u0026rdquo; por un alias con destinos explícitos. En muchos entornos MGT es la red del operador y esta concesión es aceptable.\nSRV — Servidores internos Los servidores pueden necesitar hablar entre ellos y hacia internet, pero no deben iniciar conexiones hacia USR ni IOT:\nRegla 1 — Bloquear hacia USR e IOT\nCampo Valor Action Block Source SRV net Destination USR net, IOT net Regla 2 — Permitir hacia DMZ (si los servidores publican hacia DMZ)\nCampo Valor Action Pass Source SRV net Destination DMZ net Regla 3 — Permitir internet\nCampo Valor Action Pass Source SRV net Destination any 8. Reglas en WAN: proteger el perímetro Firewall → Rules → WAN\nOPNsense por defecto bloquea todo tráfico entrante en WAN sin regla explícita — el deny implícito es suficiente para lo que no publiques. Refuerza el perímetro con estas reglas adicionales:\nBloquear RFC 1918 entrante (spoofing de origen privado)\nCampo Valor Action Block Interface WAN Source RFC1918 Destination any Log ✓ Description Bloquear spoof RFC1918 desde WAN Bloquear Bogons (ya hay opción en la interfaz WAN, pero una regla explícita permite logging)\nAlternativamente, activa Block private networks y Block bogon networks directamente en Interfaces → WAN — OPNsense genera estas reglas internamente y las aplica antes que las reglas manuales.\nPermitir servicios publicados desde DMZ (si aplica)\nSolo agrega reglas de permitir para los puertos que realmente publicas. Ejemplo para HTTPS:\nCampo Valor Action Pass Interface WAN Protocol TCP Destination DMZ net Dest. Port 443 Description HTTPS público → DMZ 9. Verificación Confirmar que las interfaces tienen IP:\nInterfaces → Overview — cada VLAN debe mostrar su IP .1 y estado up.\nConfirmar rutas:\nSystem → Routes → Status\nDebes ver una entrada /24 por cada VLAN apuntando a su interfaz respectiva.\nProbar aislamiento desde un cliente en USR:\n1# Debe fallar (RFC 1918 bloqueado) 2ping 10.0.20.1 # SRV gateway 3ping 10.0.40.50 # dispositivo IoT 4 5# Debe funcionar 6ping 8.8.8.8 7curl https://example.com Ver logs de bloqueos en tiempo real:\nFirewall → Log Files → Live View — filtra por interfaz y acción block para confirmar que los bloqueos se están generando con las reglas correctas.\nDiagnóstico desde la GUI:\nInterfaces → Diagnostics → Ping — selecciona la interfaz de origen para simular tráfico desde cada VLAN sin necesitar un cliente físico.\n10. Hardening adicional del perímetro Deshabilitar acceso a la GUI desde WAN:\nSystem → Settings → Administration → desactiva acceso HTTP/HTTPS desde WAN si no usas gestión remota. Usa una VPN para administración remota.\nIDS/IPS con Suricata:\nServices → Introspection → Suricata — actívalo en la interfaz WAN en modo IPS (inline). El conjunto de reglas ET Open cubre amenazas comunes sin coste adicional.\nLimitar intentos de acceso a la GUI:\nSystem → Settings → Administration → Login Protection — activa el rate limiting. Evita fuerza bruta contra la GUI incluso desde redes internas.\nActualizaciones automáticas de reglas IDS:\nServices → Introspection → Suricata → Download — configura actualización diaria de reglas. Las amenazas cambian; las reglas estáticas envejecen mal.\nResumen del modelo de confianza Desde \\ Hacia MGT SRV USR IOT DMZ Internet MGT ✓ ✓ ✓ ✓ ✓ ✓ SRV ✗ ✓ ✗ ✗ ✓ ✓ USR ✗ ✗ ✓ ✗ ✗ ✓ IOT ✗ ✗ ✗ ✓ ✗ ✓ DMZ ✗ ✗ ✗ ✗ ✓ ✓ WAN ✗ ✗ ✗ ✗ Puerto específico — El principio es simple: cada segmento puede salir a internet pero no puede hablar lateralmente con otros segmentos salvo que haya una regla explícita que lo justifique. MGT es la excepción controlada — es la red desde la que operas, y tiene visibilidad total a propósito.\n","link":"https://blog.bsdguy.org/posts/opnsense-vlans-firewall/","section":"posts","tags":["opnsense","vlan","firewall","seguridad","rfc1918"],"title":"OPNsense: 5 VLANs, Aislamiento y Seguridad de Perímetro"},{"body":"","link":"https://blog.bsdguy.org/tags/reference/","section":"tags","tags":null,"title":"Reference"},{"body":"","link":"https://blog.bsdguy.org/tags/rfc1918/","section":"tags","tags":null,"title":"Rfc1918"},{"body":"","link":"https://blog.bsdguy.org/tags/routeros/","section":"tags","tags":null,"title":"Routeros"},{"body":"","link":"https://blog.bsdguy.org/tags/seguridad/","section":"tags","tags":null,"title":"Seguridad"},{"body":"","link":"https://blog.bsdguy.org/tags/subnetting/","section":"tags","tags":null,"title":"Subnetting"},{"body":"Subnetting is one of those skills that seems intimidating until it clicks, and then it feels obvious. This post builds from first principles — binary representation, masks, and block sizes — through variable-length subnet masking (VLSM) and summarization.\nIP Address Structure An IPv4 address is 32 bits divided into two logical parts: network and host.\n1192.168.10.25 = 11000000.10101000.00001010.00011001 The subnet mask defines the split. A 255.255.255.0 mask (or /24 in CIDR) means the first 24 bits identify the network, the last 8 identify the host.\n1Address: 192.168.10.25 11000000.10101000.00001010.00011001 2Mask: 255.255.255.0 11111111.11111111.11111111.00000000 3 ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^ 4 Network portion Host portion CIDR Prefix Lengths The prefix length (/x) is just the count of consecutive 1-bits in the mask.\nPrefix Mask Hosts (usable) Block Size /24 255.255.255.0 254 256 /25 255.255.255.128 126 128 /26 255.255.255.192 62 64 /27 255.255.255.224 30 32 /28 255.255.255.240 14 16 /29 255.255.255.248 6 8 /30 255.255.255.252 2 4 /31 255.255.255.254 2 (point-to-point, RFC 3021) 2 /32 255.255.255.255 1 (host route) 1 Usable hosts = 2^(32−prefix) − 2, subtracting network and broadcast addresses.\nException: /31 is used for point-to-point links (RFC 3021) — both addresses are usable.\nThe Block Size Trick Block size = 256 − last octet of mask.\nFor a /26 (mask 255.255.255.192):\n1Block size = 256 - 192 = 64 Subnets in the last octet increment by 64:\n1192.168.1.0/26 → hosts .1–.62, broadcast .63 2192.168.1.64/26 → hosts .65–.126, broadcast .127 3192.168.1.128/26 → hosts .129–.190, broadcast .191 4192.168.1.192/26 → hosts .193–.254, broadcast .255 To find which subnet an address belongs to: divide the host octet by the block size, take the floor, multiply back.\n1Which subnet is 192.168.1.100/26? 2100 / 64 = 1 (floor) 31 × 64 = 64 → subnet is 192.168.1.64/26 Calculating Network, Broadcast, and Host Range Given 10.0.5.87/28:\nBlock size: 256 − 240 = 16 Network address: 87 / 16 = 5 (floor) → 5 × 16 = 80 → 10.0.5.80 Broadcast: network + block − 1 → 80 + 16 − 1 = 95 → 10.0.5.95 Host range: 10.0.5.81 – 10.0.5.94 VLSM: Allocating Subnets Efficiently Variable-Length Subnet Masking lets you carve a block into subnets of different sizes. The key rule: allocate largest subnets first to avoid fragmentation.\nExample Assign subnets from 172.16.0.0/24 for:\nSegment Hosts needed LAN A 100 LAN B 50 LAN C 25 WAN link 1 2 WAN link 2 2 Step 1 — LAN A (100 hosts): needs /25 (126 usable)\n1172.16.0.0/25 hosts: .1–.126 broadcast: .127 Step 2 — LAN B (50 hosts): needs /26 (62 usable)\n1172.16.0.128/26 hosts: .129–.190 broadcast: .191 Step 3 — LAN C (25 hosts): needs /27 (30 usable)\n1172.16.0.192/27 hosts: .193–.222 broadcast: .223 Step 4 — WAN links (2 hosts each): /30 (2 usable)\n1172.16.0.224/30 hosts: .225–.226 broadcast: .227 2172.16.0.228/30 hosts: .229–.230 broadcast: .231 Remaining space: 172.16.0.232/29 through 172.16.0.255 — available for future growth.\nSummarization Route summarization (aggregation) collapses multiple specific prefixes into one covering advertisement. This is what BGP route aggregation and OSPF inter-area summarization both rely on.\nTo summarize a set of prefixes:\nWrite all network addresses in binary. Find the longest common bit prefix. The summary prefix length equals that common bit count. Example Summarize 10.1.4.0/24, 10.1.5.0/24, 10.1.6.0/24, 10.1.7.0/24:\n110.1.4.0 = 00001010.00000001.00000100.00000000 210.1.5.0 = 00001010.00000001.00000101.00000000 310.1.6.0 = 00001010.00000001.00000110.00000000 410.1.7.0 = 00001010.00000001.00000111.00000000 5 ^^ 6 first difference at bit 23 Common prefix: 22 bits → summary is 10.1.4.0/22.\nWatch out for over-summarization. 10.1.4.0/22 also covers 10.1.4.0–10.1.7.255. If you only own .4.0–.7.0/24 but advertise /22, you\u0026rsquo;ll black-hole traffic for any addresses in that range you don\u0026rsquo;t actually have routes for.\nPrivate Address Space (RFC 1918) Range Prefix Common use 10.0.0.0–10.255.255.255 10.0.0.0/8 Large enterprises 172.16.0.0–172.31.255.255 172.16.0.0/12 Mid-size networks 192.168.0.0–192.168.255.255 192.168.0.0/16 SOHO / labs And 100.64.0.0/10 (RFC 6598) is reserved for carrier-grade NAT — you\u0026rsquo;ll see it on ISP CPE and in some cloud provider transit ranges.\nQuick Mental Math Tips Powers of 2: memorize 2^1 through 2^8 (2, 4, 8, 16, 32, 64, 128, 256). Everything else follows. Host count shortcut: for a /x prefix in the last octet, hosts = block size − 2. Subnet count: dividing a /24 into /26 gives 2^(26−24) = 4 subnets. /31 for P2P links: saves address space on transit links; supported by all modern platforms. Use it. /32 for loopbacks: always. Never put a loopback on a /24 — it wastes 254 addresses and can cause unnecessary routing entries. Related Topics Subnetting is the foundation for:\nOSPF area design — summarization at ABRs relies on contiguous address blocks BGP prefix filtering — prefix-lists match on prefix + le/ge operators ACL design — wildcard masks are the inverse of subnet masks (255 − mask octet) IPv6 — the same CIDR logic applies, just with 128-bit addresses and /64 as the standard LAN prefix ","link":"https://blog.bsdguy.org/posts/subnetting-reference/","section":"posts","tags":["networking","ip","subnetting","reference"],"title":"Subnetting: From First Principles to VLSM"},{"body":"","link":"https://blog.bsdguy.org/gallery/","section":"gallery","tags":null,"title":"Gallery"},{"body":"A quick look at the home lab I use for testing routing protocols and automation playbooks.\n","link":"https://blog.bsdguy.org/gallery/network-lab/","section":"gallery","tags":null,"title":"Home Network Lab"},{"body":"BGP Graceful Restart (GR) is one of those features you turn on everywhere because the vendor datasheet says it improves availability — and then you spend the next year debugging weird convergence behavior because of it.\nLet me break down what GR actually does, and more importantly, when it helps versus when it makes things worse.\nThe Problem It Solves When a BGP speaker restarts (software upgrade, process crash, failover), it tears down all its sessions. Every peer withdraws the routes it learned from that speaker, and the network reconverges. This takes time — seconds to minutes, depending on your topology.\nFor a route reflector serving 500 clients, that reconvergence is expensive.\nGraceful Restart allows a restarting speaker to signal to its peers: \u0026ldquo;I\u0026rsquo;m going down, but my forwarding plane is still up. Keep my routes for a bit.\u0026rdquo;\nHow It Works GR uses two BGP capabilities:\nGraceful Restart Capability (RFC 4724) — advertised during OPEN Long-lived Graceful Restart (LLGR, RFC 9494) — extends the stale timer 1Capability Code: 64 (Graceful Restart) 2 Restart Flags: 0x08 (Restart) 3 Restart Time: 120 seconds 4 AFI: IPv4 Unicast (1/1) - Forwarding State: preserved When the session drops, the receiving peer marks those routes as stale but keeps them in the RIB (and FIB) for the duration of the restart timer.\nThe Gotchas 1. Helper mode vs. Restarting mode Both sides must support GR, but they play different roles:\nRole Behavior Restarting Sets the R bit in OPEN, requests stale route retention Helper Keeps stale routes, waits for EOR (End-of-RIB) marker If your peer doesn\u0026rsquo;t support helper mode, GR gives you nothing.\n2. The stale timer is a guess The restart timer is configured statically. If your control plane takes longer to come up than the timer, peers will purge your routes before you\u0026rsquo;ve finished converging. Set it too long and you retain black-hole routes for too long.\n3. BFD + GR = danger If you\u0026rsquo;re running BFD for fast failure detection, a GR restart event will often trigger BFD to bring down sessions immediately — defeating the whole purpose of GR. You need to tune BFD timers or disable it on GR-enabled sessions.\nWhen GR Actually Helps In-service software upgrades (ISSU): The primary use case. Control plane restarts while forwarding continues. Route Reflectors: Reduces the blast radius of an RR restart. Single-homed stubs: Where a brief stale route beats a complete blackout. When to Skip It Dual-homed environments where you want fast failover to the backup path Anywhere you\u0026rsquo;re running BFD aggressively When your restart time is unpredictable (e.g., complex policy recompute) Takeaway BGP Graceful Restart is a surgical tool, not a general-purpose HA feature. Understand your restart timing, your BFD interactions, and your topology before enabling it wholesale.\n","link":"https://blog.bsdguy.org/posts/bgp-graceful-restart/","section":"posts","tags":["bgp","routing","high-availability"],"title":"BGP Graceful Restart: What It Is and When It Actually Helps"},{"body":"","link":"https://blog.bsdguy.org/tags/high-availability/","section":"tags","tags":null,"title":"High-Availability"},{"body":"","link":"https://blog.bsdguy.org/tags/routing/","section":"tags","tags":null,"title":"Routing"},{"body":"","link":"https://blog.bsdguy.org/tags/ansible/","section":"tags","tags":null,"title":"Ansible"},{"body":"Most network automation tutorials start with a playbook that configures a VLAN. That\u0026rsquo;s fine, but it skips the part where you understand why Ansible is structured the way it is and when you should reach for something else.\nHere\u0026rsquo;s a practical foundation.\nWhy Ansible for Networks? Ansible uses an agentless model — it connects via SSH (or NETCONF, or HTTPS depending on the platform). No agent to install. For network devices, this is almost always the right model.\n1# inventory.yml 2all: 3 children: 4 core: 5 hosts: 6 core-01: 7 ansible_host: 10.0.0.1 8 ansible_network_os: cisco.ios.ios 9 ansible_connection: network_cli 10 ansible_user: admin A Real Task: Collecting Interface State 1- name: Gather interface state from core switches 2 hosts: core 3 gather_facts: false 4 5 tasks: 6 - name: Collect interface data 7 cisco.ios.ios_facts: 8 gather_subset: 9 - interfaces 10 11 - name: Show interfaces that are up 12 debug: 13 msg: \u0026#34;{{ item.key }}: {{ item.value.operstatus }}\u0026#34; 14 loop: \u0026#34;{{ ansible_network_resources.interfaces | dict2items }}\u0026#34; 15 when: item.value.operstatus == \u0026#39;up\u0026#39; Templates with Jinja2 Configuration rendering is where Ansible shines for networks. Keep your logic in inventory/vars, your structure in templates.\n1{# templates/bgp.j2 #} 2router bgp {{ bgp_asn }} 3 bgp router-id {{ router_id }} 4 bgp log-neighbor-changes 5{% for peer in bgp_peers %} 6 neighbor {{ peer.ip }} remote-as {{ peer.asn }} 7 neighbor {{ peer.ip }} description {{ peer.description }} 8{% endfor %} 1# host_vars/core-01.yml 2bgp_asn: 65001 3router_id: 10.0.0.1 4bgp_peers: 5 - ip: 10.0.0.2 6 asn: 65002 7 description: \u0026#34;upstream-1\u0026#34; When to Use Something Else Ansible is great for push-based config management. It\u0026rsquo;s not great for:\nReal-time state queries — use NAPALM or direct API calls Event-driven response — use Ansible EDA or a proper event bus Complex diffs with rollback — look at Nornir + NAPALM The Mental Model Think of Ansible for networks as: declarative intent → rendered config → pushed to device. Keep your vars clean, your templates readable, and your playbooks idempotent.\n","link":"https://blog.bsdguy.org/posts/network-automation-ansible/","section":"posts","tags":["automation","ansible","python"],"title":"Network Automation with Ansible: A Practical Starting Point"},{"body":"","link":"https://blog.bsdguy.org/tags/ospf/","section":"tags","tags":null,"title":"Ospf"},{"body":"OSPF link-state advertisements are the currency of the protocol. Understanding which LSA type carries what information — and who generates it — is essential for troubleshooting convergence issues.\nQuick Reference Type Name Originated By Scope 1 Router LSA Every router Single area 2 Network LSA DR on broadcast segment Single area 3 Summary LSA ABR Area → backbone 4 ASBR Summary ABR Area → backbone 5 AS External ASBR Entire OSPF domain 7 NSSA External ASBR in NSSA Single NSSA area Type 1 — Router LSA Every OSPF router originates one Type 1 LSA per area it belongs to. It describes:\nRouter links (point-to-point) Transit links (multi-access segments) Stub links (no neighbor, just a subnet) Virtual links 1# Cisco IOS 2show ip ospf database router self-originate Type 3 — Summary LSA ABRs generate Type 3 LSAs to advertise intra-area routes into other areas. This is where OSPF route summarization lives.\n1area 10 range 10.10.0.0 255.255.0.0 This collapses all routes from area 10 into a single Type 3 when advertised toward area 0.\nType 5 vs Type 7 The classic confusion point. External routes (redistributed into OSPF) are carried as Type 5 LSAs domain-wide. But NSSA areas don\u0026rsquo;t accept Type 5s — so ASBRs within an NSSA originate Type 7 LSAs instead.\nThe ABR on the boundary converts Type 7 → Type 5 when flooding toward area 0.\nDebug Tip When an external route isn\u0026rsquo;t showing up where expected, check:\nIs the ASBR reachable? (Type 4 LSA present in the area?) Is the area a stub/NSSA blocking Type 5? Is there a Type 7 → Type 5 translation happening at the ABR? 1show ip ospf database external 2show ip ospf database nssa-external Knowing your LSA types turns a confusing show ip route gap into a five-minute debug.\n","link":"https://blog.bsdguy.org/posts/ospf-lsa-types/","section":"posts","tags":["ospf","routing","reference"],"title":"OSPF LSA Types: A Field Reference"},{"body":"Hey — I\u0026rsquo;m a network engineer with a focus on large-scale infrastructure, routing protocols, and network automation.\nI write about things I\u0026rsquo;m learning, breaking, and occasionally fixing.\nWhat I work with Routing: BGP, OSPF, IS-IS Automation: Ansible, Python (netmiko, NAPALM, pyATS) Cloud networking: AWS VPC, Azure VNET, Transit Gateway Vendors: Cisco IOS/IOS-XE/NX-OS, Juniper Junos, Arista EOS Monitoring: Prometheus, Grafana, SNMP, gNMI/gRPC Why this blog The internet is held together by people who spend their days staring at BGP tables, writing Jinja2 templates, and arguing about whether OSPF area 0 should span the DC fabric. This is a place for those people.\nContact Find me on GitHub or reach out via email.\n","link":"https://blog.bsdguy.org/about/","section":"","tags":null,"title":"About"},{"body":"","link":"https://blog.bsdguy.org/categories/","section":"categories","tags":null,"title":"Categories"}]