Manual 07 · RADIUS en sitio (Fase 2)
RADIUS es el camino de grado operador: tiempo, ancho de banda, corte en caliente y contabilidad real del consumo, con cualquier fabricante que hable el protocolo. Es también la respuesta cuando un firmware no expone portal externo (caso típico de Ruijie).
Por qué no vive en la nube. RADIUS habla UDP (1812 autenticación, 1813 contabilidad, 3799 corte). Railway —y la mayoría de las plataformas de aplicaciones— sólo exponen TCP/HTTP. El servidor RADIUS va en el sitio del ISP, junto al NAS, y habla con
zonaw.ipsmart.apppor HTTPS. No es una limitación del diseño: es cómo funciona el protocolo.
1. Arquitectura
Teléfono ──── AP/NAS ──── FreeRADIUS (en sitio) ──HTTPS──► zonaw.ipsmart.app
│ │ │
UDP 1812/1813 │ agente de corte ◄──HTTPS────┘
▲ │ (órdenes CoA pendientes)
└──── UDP 3799 ┘
- El NAS pregunta «¿este usuario entra?» → FreeRADIUS consulta
GET /api/radius/authorizede IPSmart Z, que responde con los atributos de esa sesión. - El NAS reporta consumo → FreeRADIUS lo reenvía a
POST /api/radius/authorize, y el panel muestra bytes reales. - El corte anticipado lo pide IPSmart Z dejando una orden; el agente en sitio la recoge de
GET /api/radius/ordenesy lanza el paquete CoA/Disconnect contra el NAS.
2. Levantar el servidor
En una máquina del sitio (un mini PC, una VM, un contenedor en el servidor del ISP) con acceso a la red de gestión del NAS:
mkdir -p /opt/zonaw-radius && cd /opt/zonaw-radius
docker-compose.yml:
services:
radius:
image: freeradius/freeradius-server:3.2-alpine
container_name: zonaw-radius
restart: unless-stopped
network_mode: host # RADIUS necesita ver las IPs reales de los NAS
volumes:
- ./config:/etc/raddb
environment:
- ZONAW_URL=https://zonaw.ipsmart.app
- ZONAW_CLAVE_TAREAS=${ZONAW_CLAVE_TAREAS}
agente:
image: node:22-alpine
container_name: zonaw-agente
restart: unless-stopped
network_mode: host
working_dir: /app
volumes:
- ./agente:/app
command: node agente.js
environment:
- ZONAW_URL=https://zonaw.ipsmart.app
- ZONAW_CLAVE_TAREAS=${ZONAW_CLAVE_TAREAS}
- NAS_HOST=10.60.0.1
- NAS_SECRETO=${NAS_SECRETO}
ZONAW_CLAVE_TAREAS es el valor de Z_CLAVE_TAREAS del servicio de IPSmart Z. Sin él, los puntos de mantenimiento responden 401.
3. Clientes: quién puede preguntarle al RADIUS
config/clients.conf:
client nas-plaza {
ipaddr = 10.60.0.1
secret = PON-AQUI-UN-SECRETO-LARGO
shortname = plaza-bolivar
nas_type = other
require_message_authenticator = yes
}
- Un secreto distinto por NAS. Reusar el mismo en toda la red significa que comprometer un AP compromete la autenticación de todas las zonas.
require_message_authenticator = yesprotege contra el ataque BlastRADIUS (CVE‑2024‑3596). No lo quites.
4. Consultar a IPSmart Z desde FreeRADIUS
config/mods-available/rest:
rest {
connect_uri = "${ZONAW_URL}"
authorize {
uri = "${..connect_uri}/api/radius/authorize?username=%{User-Name}&mac=%{Calling-Station-Id}"
method = 'get'
tls = ${..tls}
header = "X-Clave-Tareas: ${ZONAW_CLAVE_TAREAS}"
}
accounting {
uri = "${..connect_uri}/api/radius/authorize"
method = 'post'
body = 'json'
header = "X-Clave-Tareas: ${ZONAW_CLAVE_TAREAS}"
}
pool {
start = 2
min = 1
max = 10
idle_timeout = 60
}
}
Y en config/sites-enabled/default, dentro de las secciones correspondientes:
authorize {
preprocess
rest
pap
}
accounting {
detail
rest
}
ln -s ../mods-available/rest config/mods-enabled/rest
docker compose up -d
docker logs -f zonaw-radius
5. Qué responde IPSmart Z
{
"Session-Timeout": 1800,
"Idle-Timeout": 600,
"Acct-Interim-Interval": 300,
"Mikrotik-Rate-Limit": "1024k/4096k",
"WISPr-Bandwidth-Max-Up": 1024000,
"WISPr-Bandwidth-Max-Down": 4096000,
"Class": "e3b0c442-98fc-4c14-9afb-f4c8996fb924"
}
| Atributo | Para qué |
|---|---|
Session-Timeout |
Los segundos que le quedan. Se recalcula en cada consulta, no es un valor fijo: un cliente que se reconecta no reinicia su reloj |
Idle-Timeout |
Cierra la sesión del que se fue sin desconectarse |
Acct-Interim-Interval |
Cada cuánto reporta consumo el NAS (300 s es un buen equilibrio) |
Mikrotik-Rate-Limit |
Límite en equipos MikroTik. subida/bajada, en ese orden |
WISPr-Bandwidth-Max-* |
Lo mismo, en el atributo estándar que entienden los demás fabricantes. En bits por segundo |
Class |
El id de la sesión de IPSmart Z. El NAS lo devuelve en la contabilidad, y es lo que permite cruzar el consumo con la persona |
6. El agente de corte
agente/agente.js — pregunta cada 5 segundos si hay que desconectar a alguien y lanza el paquete CoA:
// Pregunta por órdenes, manda el Disconnect-Request al NAS (UDP 3799) y
// confirma. Es lo único que no se puede hacer desde la nube.
const CADA_MS = 5000
async function pasada() {
const r = await fetch(`${process.env.ZONAW_URL}/api/radius/ordenes`, {
headers: { 'X-Clave-Tareas': process.env.ZONAW_CLAVE_TAREAS },
})
const { ordenes } = await r.json()
const cumplidas = []
for (const orden of ordenes) {
try {
await desconectar(orden.mac) // radclient / paquete CoA
cumplidas.push(orden.id)
} catch (e) {
console.error('No se pudo desconectar', orden.mac, e.message)
}
}
if (cumplidas.length) {
await fetch(`${process.env.ZONAW_URL}/api/radius/ordenes`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Clave-Tareas': process.env.ZONAW_CLAVE_TAREAS },
body: JSON.stringify({ ids: cumplidas }),
})
}
}
setInterval(pasada, CADA_MS)
El GET de órdenes también sirve de latido: si el agente deja de preguntar, el panel de IPSmart Z marca ese RADIUS como caído a los 10 minutos. Un agente muerto sin aviso es una zona que no corta a nadie.
Desconexión a mano, para probar:
echo "User-Name=z-1a2b3c4d,Framed-IP-Address=10.60.1.34" | \
radclient -x 10.60.0.1:3799 disconnect PON-AQUI-UN-SECRETO-LARGO
Respuesta esperada: Disconnect-ACK. Si responde Disconnect-NAK, el NAS no encontró la sesión (revisa que el identificador coincida); si no responde nada, el NAS no tiene habilitado CoA.
7. Habilitar CoA en el NAS
MikroTik:
/radius incoming set accept=yes port=3799
/radius add service=hotspot address=10.60.0.5 secret="..." timeout=3s
/ip hotspot profile set [find name=hsprof1] use-radius=yes radius-interim-update=5m
UniFi: Settings → Profiles → RADIUS con el servidor y el secreto; la red de invitados en modo RADIUS MAC auth o 802.1X según el diseño.
Omada: Settings → Authentication → RADIUS Profile.
Ruijie: Authentication → RADIUS Server, y comprobar que la contabilidad esté activada — sin ella no hay consumo ni corte por inactividad.
8. Comprobaciones
# ¿Autentica?
echo "User-Name=z-1a2b3c4d,User-Password=xxxxxxxxxxxx" | \
radtest -x z-1a2b3c4d xxxxxxxxxxxx 127.0.0.1 0 testing123
# ¿Está llegando la contabilidad?
docker exec zonaw-radius tail -f /var/log/radius/radacct/*/detail-*
# ¿El agente sigue vivo? (en el panel: ficha de la zona → equipo)
curl -s -H "X-Clave-Tareas: $ZONAW_CLAVE_TAREAS" https://zonaw.ipsmart.app/api/radius/ordenes
9. Cuando algo no funciona
| Síntoma | Causa | Arreglo |
|---|---|---|
Access-Reject siempre |
El módulo rest no está en la sección authorize, o la clave de tareas es incorrecta |
Sección 4 |
| Autentica pero sin límite de banda | El NAS no entiende el atributo enviado | Usar el Mikrotik-Rate-Limit o el WISPr-* según fabricante |
| El consumo no aparece en el panel | El NAS no devuelve Class, o la contabilidad está apagada |
Sección 5 y 7 |
| El corte no llega nunca | El agente está caído, o el NAS no acepta CoA | Sección 6 y 7 |
| Funciona y de pronto todos son rechazados | El certificado de zonaw.ipsmart.app cambió y el contenedor no lo confía |
docker compose restart radius tras actualizar los certificados raíz |
| Latencia alta en la autenticación | El sitio tiene mal enlace a internet y cada consulta va a la nube | Subir el pool de conexiones, y evaluar caché local de sesiones |
10. Lista de comprobación
- El servidor RADIUS está en el sitio, no en la nube.
- Un secreto distinto por NAS, y
require_message_authenticator = yes. - Módulo
restapuntando azonaw.ipsmart.appcon la clave de tareas. -
Session-Timeoutrecalculado (probar reconectando: no debe reiniciarse el reloj). - Contabilidad llegando: el panel muestra bytes reales.
- Agente de corte corriendo, con latido visible en el panel.
-
radclient … disconnectprobado a mano y respondiendoDisconnect-ACK. - Prueba de corte real cronometrada, con un teléfono.