← Todos los artículos

El día en que mi topología me mintió

Un post-mortem de ingeniería sobre mi propio producto, y el laboratorio que lo pilló

Michel Wijnberg

Construí Osprey para que me dijera la verdad sobre las redes. Luego descubrí que llevaba tiempo mintiéndome. Con seguridad, por escrito, en un lienzo que yo había estado enseñando a la gente.

Esto es lo que pasó, cómo lo pillé y qué cambié. Es la entrada más útil de esta serie, porque todo lo demás que he escrito sobre honestidad sale barato hasta que lo auditado es tu propio trabajo.


Lo que yo creía

El motor de caminos de Osprey hacía lo que hacen casi todas las herramientas de caminos. Dados un origen y un destino, ejecutaba un Dijkstra consciente de áreas desde el origen sobre la topología OSPF y dibujaba la ruta más barata.

Tenía buenas razones para fiarme. Respetaba las reglas del RFC 2328 que me importaban: intraárea preferido sobre interárea, interárea transitando por el backbone, E1 antes que E2, ECMP enumerado en lugar de colapsado. Tenía pruebas. Encajaba con mi modelo mental del laboratorio. Cuando pinchaba dos routers, se iluminaba un camino sensato.

También estaba describiendo, en una de cada ocho consultas interárea, una ruta que ningún paquete tomaría jamás.


Cómo lo descubrí

No por un informe de error. Por una tarea rutinaria.

Estaba montando un banco de validación para otra cosa completamente distinta y quería un conjunto de datos de verdad de referencia con el que contrastarlo. El laboratorio tiene 64 routers en el AS 200, todos alcanzables por SSH, todos dispuestos a imprimir su propia tabla de rutas. Así que hice lo tonto y evidente: entré en los 64 y volqué show ip route ospf de todos ellos.

Después, en lugar de mirarlo por encima, comparé la tabla real de cada router con lo que el motor de caminos de Osprey afirmaba que era el camino, para los 4.032 pares ordenados origen/destino.

360 no coincidían.

No “el coste está algo desviado”. El camino dibujado contenía un router que el paquete demostrablemente no visita nunca.


El caso canónico

Aquí va uno, con cada fila medida en el equipo y no modelada:

RouterDice sobre el destinoReenvía a
dfw1-gw2metric 20031, type inter areadfw1-cr1
dfw1-cr1metric 30001, type intra areaord1-cr1no a dfw2-cr1
ord1-cr1metric 20001, type intra areajfk1-cr1
jfk1-cr1metric 10001, type intra arealhr1-cr1
lhr1-cr1metric 1, connectedentregado

Osprey dibujó:

dfw1-gw2 → dfw1-cr1 → dfw2-cr1 → iad2-cr1 → ams1-cr1 → lhr1-cr1

El paquete nunca llega a dfw2-cr1. El camino de Osprey era más barato. Simplemente no era un camino que ningún router fuera a construir.

Dos reglas independientes del RFC 2328 hacen que dfw1-cr1 rechace el corredor barato, y yo no había implementado ninguna de las dos al nivel de un router intermedio individual:

§16 paso (3) / §16.2 ¶1: la puerta del backbone. Un router conectado a varias áreas examina solo los summary-LSA del backbone. El summary más barato de dfw2-cr1, con coste 20011, está realmente en la base de datos del área 4.6.23.0 de dfw1-cr1 (lo confirmé con show ip ospf database summary, porque yo tampoco me lo creía), pero no es un candidato, porque dfw1-cr1 es un ABR y ese summary no llegó por el área 0. El RFC 3509 §2.1/§2.2, el comportamiento ABR de Cisco que corre de verdad en estos equipos, condiciona esto a tener una conexión activa al backbone, que dfw1-cr1 tiene.

§16.2 paso (6): intra gana a inter, y punto. “If the paths present in the table are intra-area paths, do nothing with the LSA.” dfw1-cr1 ya tiene un camino intraárea con métrica 30001. El summary de 20011 pierde frente a él. La métrica no entra nunca en la comparación. Esta es una regla sobre el tipo de camino.

Cualquiera de las dos reglas por separado mata el corredor. Mi motor no podía ver ninguna, porque nunca le preguntó nada a dfw1-cr1. Le preguntó al router origen por su visión del mundo y luego trazó una línea a través de otros ocho routers como si la compartieran.


La forma del error

Vale la pena nombrarlo con precisión, porque el detalle concreto de OSPF es menos interesante que la categoría.

Yo había modelado un camino cuando la red implementa una secuencia de decisiones independientes. Esas dos cosas coinciden exactamente mientras todos los routers del recorrido usen la misma regla de decisión sobre el mismo conjunto de entrada. Dentro de una sola área OSPF así ocurre (misma LSDB, mismo algoritmo, mismo resultado), y por eso el recuento de defectos para los pares intraárea fue:

0 de 1.193.

En cuanto entra un ABR en juego, el conjunto de entrada deja de ser compartido: un ABR mira deliberadamente un conjunto distinto de LSA que los routers de su alrededor. Mi modelo no tenía dónde poner ese hecho, porque mi modelo tenía un grafo y un Dijkstra, no sesenta y cuatro routers cada uno con una opinión.

ParesCon un salto que ningún router tomaría
Intraárea1.1930
Interárea2.839360 (12,68 %)
Todos4.032360 (8,93 %)

La solución, y cómo sé que funcionó

El motor ahora recorre la cadena. En cada salto evalúa la ruta propia que ese router tiene instalada hacia la dirección de destino, usando la LSDB de ese router, sus pertenencias a áreas, su estado de ABR y su mezcla de distancias administrativas; después sigue el siguiente salto que realmente selecciona, y repite.

Luego volví a pasar los mismos 4.032 pares, contra la misma verdad de referencia:

ComprobaciónResultado
Métrica propia del router reproducida exactamente4.032 / 4.032
Tipo de camino (intra / inter / externo) exacto4.032 / 4.032
Conjunto completo de siguientes saltos ECMP instalados idéntico4.032 / 4.032
La cadena termina en delivered en el destino solicitado4.032 / 4.032

No “mejorado”. Idéntico en cada par, incluido el conjunto completo de siguientes saltos de igual coste en lugar de un miembro elegido al azar.

El camino corregido: una tabla de 10 saltos con el área, los puertos y el coste de cada router
El camino corregido: una tabla de 10 saltos con el área, los puertos y el coste de cada router

Esa es la misma consulta que antes se inventaba un salto, ahora mostrando todos los routers que realmente toman una decisión, el área en la que la toman y los puertos físicos de ambos lados. El total, 30061, es la métrica que el propio origen tiene instalada: literalmente el número que show ip route imprime en lax1-gw1.

Y donde la cadena no es computable (OSPFv3, IS-IS, EIGRP, un ámbito parcial, un área con un enlace virtual), el motor no retrocede en silencio a la imagen antigua. Devuelve la vista con raíz en el origen con un model_note que nombra el motivo, dibujado como su propio paso de explicación. Esa negativa es el tema de la cuarta entrada, y existe por culpa de este fallo.


Uno más pequeño, para equilibrar

No toda herida autoinfligida es un algoritmo. Aquí va una de dos líneas que me costó una tarde.

El grabador GRE de Osprey forma una adyacencia OSPF real, así que tiene que originar un Router-LSA. Al principio anunciaba la subred interna del túnel como red stub: con buen aspecto, legal según el RFC y del todo razonable.

Todos los routers del área instalaron entonces una ruta hacia el prefijo del túnel. Esa ruta sobrescribió el camino subyacente sobre el que viajaba el propio túnel GRE. El transporte exterior se rompió. La adyacencia se cayó. El grabador se reconectó, volvió a originar el LSA y lo hizo otra vez.

La solución está ahora en el comentario del código, para que nadie la quite por accidente:

// Only the P2P link is advertised. NO stub network for the tunnel
// inner subnet. Advertising the tunnel subnet (even at max cost) causes SPF
// on other routers to install a route for the tunnel prefix, which can
// disrupt the GRE outer path and kill the adjacency.

Una herramienta de monitorización que tira abajo lo que está monitorizando es un tipo especial de bochorno, y no lo habría encontrado en un diagrama.


Para qué sirve el laboratorio en realidad

Yo describía el laboratorio como el sitio donde enseño Osprey. Esa era la descripción de puesto equivocada, y corregirla cambió mi forma de construir.

El laboratorio no está para que Osprey quede bien. Está para demostrar que Osprey se equivoca.

Lo que significa que el laboratorio solo vale en la medida en que pueda contradecirme: por eso es incómodo a propósito. Tres operadores que no comparten protocolo. Quince áreas OSPF donde tres demostrarían mejor. Un dominio IS-IS con diecisiete áreas de nivel 1. EIGRP, que no tiene base de datos de estado de enlace y rompe todas las suposiciones que hace el código de OSPF. Enlaces de doble pila cuyos costes v4 y v6 difieren por un factor 10 en los 124.

Nada de eso da una captura más bonita. Todo ello ha pillado algo.

Los 360 corredores ficticios no se encontraron leyendo con cuidado, ni con una prueba que yo fuera lo bastante listo para escribir de antemano. Se encontraron porque tenía 64 routers reales a los que se les podía preguntar qué pensaban de verdad, y por fin se lo pregunté a todos a la vez en lugar de fiarme de los dos o tres que había comprobado a mano.

Lo cual plantea la pregunta obvia, y es el tema de la siguiente entrada: si la red es lo único que puede decirte si tu modelo es correcto, ¿cómo conviertes eso en una batería de pruebas?


A continuación: Las pruebas unitarias de un ingeniero de redes.