En corto

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python3
"""Codifica rutas classless (RFC 3442) para la opción DHCP 121."""
import ipaddress

def encode(routes):
    out = bytearray()
    for dest, via in routes:
        net = ipaddress.ip_network(dest, strict=False)
        n = (net.prefixlen + 7) // 8                 # octetos significativos del destino
        out.append(net.prefixlen)
        out += net.network_address.packed[:n]
        out += ipaddress.ip_address(via).packed
    return bytes(out)

routes = [
    ("192.0.2.0/24", "192.168.1.254"),   # la otra sede, por el router de la VPN
    ("0.0.0.0/0",    "192.168.1.1"),     # la ruta por defecto: obligatoria si mandas la 121
]
raw = encode(routes)
print("hex      ", raw.hex())
print("mikrotik ", "0x" + raw.hex())
print("isc      ", ", ".join(str(b) for b in raw))
print("pfsense  ", ":".join(f"{b:02x}" for b in raw))
1
2
3
4
hex       18c00002c0a801fe00c0a80101
mikrotik  0x18c00002c0a801fe00c0a80101
isc       24, 192, 0, 2, 192, 168, 1, 254, 0, 192, 168, 1, 1
pfsense   18:c0:00:02:c0:a8:01:fe:00:c0:a8:01:01

El formato

Cada ruta de la opción 121 son tres campos pegados, sin separadores:

CampoTamañoEjemplo 192.0.2.0/24 via 192.168.1.254
Longitud del prefijo1 octeto18 (24)
Destino, solo los octetos significativos: ⌈prefijo / 8⌉0 a 4 octetosc0 00 02 (192.0.2)
Router4 octetosc0 a8 01 fe

Un /24 lleva tres octetos de destino, un /16 dos, un /32 cuatro, y la ruta por defecto /0 ninguno: 00 seguido directamente del router. Varias rutas se concatenan; la opción entera cabe en 255 octetos (unas 28 rutas /24).

La regla que rompe la red

RFC 3442, sección 3: “If the DHCP server returns both a Classless Static Routes option and a Router option, the DHCP client MUST ignore the Router option.” Si empiezas a mandar la 121 con una sola ruta a la otra sede, los clientes que la entienden pierden la puerta de enlace de la opción 3 y se quedan sin salida a Internet. La ruta por defecto va dentro de la 121, siempre, como en el ejemplo. Los clientes que no entienden la 121 siguen usando la opción 3, así que déjala configurada también.

Dónde se pone

ISC dhcpd (dhcpd.conf): hay que declarar la opción, y conviene mandarla también como 249, que es la que piden los Windows antiguos:

1
2
3
4
5
6
7
8
9
option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
option ms-classless-static-routes code 249 = array of unsigned integer 8;

subnet 192.168.1.0 netmask 255.255.255.0 {
  range 192.168.1.100 192.168.1.199;
  option routers 192.168.1.1;
  option rfc3442-classless-static-routes 24, 192,0,2, 192,168,1,254, 0, 192,168,1,1;
  option ms-classless-static-routes     24, 192,0,2, 192,168,1,254, 0, 192,168,1,1;
}

Kea (2.4 o posterior) entiende la opción por nombre y con notación legible, sin hex:

1
2
3
4
"option-data": [
  { "name": "classless-static-route", "code": 121,
    "data": "192.0.2.0/24 - 192.168.1.254, 0.0.0.0/0 - 192.168.1.1" }
]

pfSense / OPNsense: con el backend ISC, Services → DHCP Server → Additional BOOTP/DHCP Options, número 121, tipo String, valor en hex con dos puntos (18:c0:00:02:…); añade otra con 249 para Windows. Con el backend Kea, la opción 121 todavía no está en la interfaz (a fecha de este post hay peticiones abiertas en los dos proyectos); si tu versión permite opciones personalizadas de Kea, usa el JSON de arriba; si no, es el motivo para seguir en ISC.

MikroTik RouterOS:

1
2
3
4
/ip dhcp-server option add name=classless code=121 value=0x18c00002c0a801fe00c0a80101
/ip dhcp-server option add name=classless-ms code=249 value=0x18c00002c0a801fe00c0a80101
/ip dhcp-server option sets add name=routes options=classless,classless-ms
/ip dhcp-server network set [find address=192.168.1.0/24] dhcp-option-set=routes

Qué clientes la entienden

ClienteOpción 121
Windows (Vista y posteriores)Sí; también acepta la 249
Linux con NetworkManager o systemd-networkdSí, sin configurar nada
Linux con dhclientSolo si rfc3442-classless-static-routes está en la lista request de dhclient.conf y hay un hook que instale las rutas (Debian y Ubuntu lo traen en /etc/dhcp/dhclient-exit-hooks.d/)
macOS e iOSSí
AndroidHistóricamente la ignora; no cuentes con ella
FreeBSD dhclientNo sin dhclient-script a medida; dhcpcd sí

Compruébalo en el cliente con ip route (Linux), route print (Windows) o netstat -rn (macOS) tras renovar el lease.

Trampas

  • /32 con router en otra subred: válido en la codificación, pero el cliente necesita una ruta previa al router; ordena las rutas de más específica a menos y pon primero la del propio segmento si hace falta.
  • Rutas hacia redes que el DHCP no controla (un servidor DHCP en una VLAN que anuncia rutas por otra) se aceptan igual: la 121 es una lista de texto, nadie la valida. Un error de un octeto se convierte en una ruta hacia una red inventada.
  • Los clientes no revisan las rutas hasta renovar el lease; tras cambiar la opción, T1 (la mitad del lease) es lo que tardarán en verla, o fuerza la renovación.
  • Windows y la 249: si solo mandas la 121, los Windows modernos la usan; la 249 es por compatibilidad y contiene exactamente los mismos bytes.
  • Anuncios /0 desde un DHCP ajeno: cualquier servidor DHCP de la LAN puede reescribir la ruta por defecto de todos los clientes con la 121; DHCP snooping en el switch es la única defensa real.

Referencia: RFC 3442.

Ver también