← Alle Beiträge

Der Tag, an dem meine Topologie mich anlog

Eine technische Nachbetrachtung zum eigenen Produkt, und das Labor, das den Fehler fand

Michel Wijnberg

Ich habe Osprey gebaut, damit es mir die Wahrheit über Netzwerke sagt. Dann fand ich heraus, dass es mich angelogen hatte. Selbstbewusst, schriftlich, auf einem Canvas, den ich Leuten vorgeführt hatte.

Das hier ist, was passiert ist, wie ich es gefunden habe und was ich geändert habe. Es ist der nützlichste Beitrag dieser Reihe, denn alles andere, was ich über Ehrlichkeit geschrieben habe, ist wohlfeil, solange nicht die eigene Arbeit geprüft wird.


Was ich glaubte

Ospreys Pfad-Engine tat, was fast jedes Pfadwerkzeug tut. Zu einer Quelle und einem Ziel ließ sie einen area-bewussten Dijkstra von der Quelle aus über die OSPF-Topologie laufen und zeichnete die billigste Route.

Ich hatte gute Gründe, ihr zu vertrauen. Sie beachtete die Regeln aus RFC 2328, die mir wichtig waren: intra-area vor inter-area, inter-area über das Backbone, E1 vor E2, ECMP aufgezählt statt zusammengefasst. Sie hatte Tests. Sie passte zu meinem mentalen Modell des Labors. Wenn ich zwei Router anklickte, leuchtete ein vernünftiger Pfad auf.

Sie beschrieb außerdem bei jeder achten Inter-Area-Abfrage eine Route, die kein Paket je nehmen würde.


Wie ich es herausfand

Nicht durch einen Fehlerbericht. Durch eine Fleißarbeit.

Ich baute gerade ein Validierungsgerüst für etwas ganz anderes und wollte einen Datensatz mit gesicherter Wahrheit, gegen den ich prüfen konnte. Das Labor hat 64 Router in AS 200, alle über SSH erreichbar, alle bereit, ihre eigene Routing-Tabelle auszugeben. Also tat ich das Naheliegende und Stumpfe: Ich meldete mich auf allen 64 an und zog von jedem show ip route ospf.

Dann verglich ich, statt es zu überfliegen, die echte Tabelle jedes Routers mit dem, was Ospreys Pfad-Engine als Pfad behauptete, und zwar für alle 4.032 geordneten Quelle/Ziel-Paare.

360 davon widersprachen.

Nicht “die Kosten stimmen leicht nicht”. Der gezeichnete Pfad enthielt einen Router, den das Paket nachweislich nie besucht.


Der kanonische Fall

Hier ist einer, jede Zeile am Gerät gemessen statt modelliert:

RouterSagt über das ZielLeitet weiter an
dfw1-gw2metric 20031, type inter areadfw1-cr1
dfw1-cr1metric 30001, type intra areaord1-cr1nicht dfw2-cr1
ord1-cr1metric 20001, type intra areajfk1-cr1
jfk1-cr1metric 10001, type intra arealhr1-cr1
lhr1-cr1metric 1, connectedzugestellt

Osprey zeichnete:

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

Das Paket erreicht dfw2-cr1 nie. Ospreys Pfad war billiger. Er war nur kein Pfad, den irgendein Router bauen würde.

Zwei voneinander unabhängige Regeln aus RFC 2328 sorgen dafür, dass dfw1-cr1 den billigen Korridor ablehnt, und ich hatte keine von beiden auf der Ebene eines einzelnen Zwischenrouters umgesetzt:

§16 Schritt (3) / §16.2 ¶1: das Backbone-Tor. Ein Router, der an mehreren Areas hängt, betrachtet ausschließlich Summary-LSAs aus dem Backbone. Das billigere Summary von dfw2-cr1 mit Kosten 20011 liegt tatsächlich in der Datenbank von dfw1-cr1 für Area 4.6.23.0 (ich habe das mit show ip ospf database summary bestätigt, weil ich es selbst nicht glaubte), aber es ist kein Kandidat, weil dfw1-cr1 ein ABR ist und dieses Summary nicht über Area 0 eingetroffen ist. RFC 3509 §2.1/§2.2, das ABR-Verhalten von Cisco, das auf diesen Geräten tatsächlich läuft, knüpft das an eine aktive Backbone-Verbindung, die dfw1-cr1 hat.

§16.2 Schritt (6): intra schlägt inter, Punkt. “If the paths present in the table are intra-area paths, do nothing with the LSA.” dfw1-cr1 hält bereits einen Intra-Area-Pfad mit Metrik 30001. Das Summary mit 20011 verliert dagegen. Die Metrik kommt im Vergleich gar nicht vor. Das ist eine Regel über den Pfadtyp.

Jede der beiden Regeln allein erledigt den Korridor. Meine Engine konnte keine von beiden sehen, denn sie fragte dfw1-cr1 überhaupt nichts. Sie fragte die Weltsicht des Quellrouters ab und zog dann eine Linie durch acht andere Router, als würden die sie teilen.


Die Form des Fehlers

Das lohnt eine genaue Benennung, denn das konkrete OSPF-Detail ist weniger interessant als die Kategorie.

Ich hatte einen Pfad modelliert, während das Netzwerk eine Folge unabhängiger Entscheidungen umsetzt. Diese beiden Dinge fallen genau so lange zusammen, wie jeder Router auf dem Weg dieselbe Entscheidungsregel über denselben Eingangsdaten anwendet. Innerhalb einer einzelnen OSPF-Area tun sie das (dieselbe LSDB, derselbe Algorithmus, dasselbe Ergebnis), und deshalb lautete die Fehlerzahl für Intra-Area-Paare:

0 von 1.193.

Sobald ein ABR beteiligt ist, sind die Eingangsdaten nicht mehr geteilt: Ein ABR sieht mit Absicht auf eine andere Menge von LSAs als die Router um ihn herum. Mein Modell hatte keinen Ort für diese Tatsache, denn mein Modell hatte einen Graphen und einen Dijkstra, nicht vierundsechzig Router mit je einer eigenen Meinung.

PaareMit einem Hop, den kein Router nimmt
Intra-Area1.1930
Inter-Area2.839360 (12,68 %)
Alle4.032360 (8,93 %)

Die Korrektur, und woher ich weiß, dass sie wirkt

Die Engine läuft die Kette inzwischen ab. Bei jedem Hop wertet sie die eigene installierte Route dieses Routers zur Zieladresse aus, mit dessen eigener LSDB, dessen Area-Mitgliedschaften, dessen ABR-Status und dessen Mischung administrativer Distanzen, folgt dann dem Next Hop, den er tatsächlich wählt, und wiederholt das.

Dann ließ ich dieselben 4.032 Paare erneut gegen dieselbe gesicherte Wahrheit laufen:

PrüfungErgebnis
Eigene Metrik des Routers exakt reproduziert4.032 / 4.032
Pfadtyp (intra / inter / extern) exakt4.032 / 4.032
Vollständige installierte ECMP-Next-Hop-Menge identisch4.032 / 4.032
Kette endet mit delivered am angefragten Ziel4.032 / 4.032

Nicht “verbessert”. Bei jedem Paar identisch, einschließlich der vollständigen Menge gleichwertiger Next Hops statt eines willkürlich gewählten Mitglieds daraus.

Der korrigierte Pfad: eine Tabelle über 10 Hops mit der eigenen Area, den Ports und den Kosten jedes Routers
Der korrigierte Pfad: eine Tabelle über 10 Hops mit der eigenen Area, den Ports und den Kosten jedes Routers

Das ist dieselbe Abfrage, die früher einen Hop erfand, und sie zeigt jetzt jeden Router, der tatsächlich eine Entscheidung trifft, die Area, in der er sie trifft, und die physischen Ports auf beiden Seiten. Die Summe, 30061, ist die selbst installierte Metrik der Quelle: buchstäblich die Zahl, die show ip route auf lax1-gw1 ausgibt.

Und dort, wo die Kette nicht berechenbar ist (OSPFv3, IS-IS, EIGRP, ein unvollständiger Bereich, eine Area mit einem Virtual Link), fällt die Engine nicht stillschweigend auf das alte Bild zurück. Sie liefert die von der Quelle gewurzelte Sicht mit einem model_note, der den Grund benennt und als eigener Erklärungsschritt dargestellt wird. Diese Verweigerung ist das Thema von Beitrag vier, und es gibt sie wegen dieses Fehlers.


Ein kleinerer, zum Ausgleich

Nicht jede selbst zugefügte Wunde ist ein Algorithmus. Hier eine mit zwei Zeilen, die mich einen Nachmittag gekostet hat.

Ospreys GRE-Recorder baut eine echte OSPF-Nachbarschaft auf, muss also ein Router-LSA erzeugen. Anfangs kündigte er das innere Subnetz des Tunnels als Stub-Netzwerk an: sah richtig aus, war RFC-konform und völlig vernünftig.

Daraufhin installierte jeder Router in der Area eine Route in Richtung des Tunnel-Präfixes. Diese Route überstimmte den Underlay-Pfad, auf dem der GRE-Tunnel selbst ritt. Der äußere Transport brach. Die Nachbarschaft fiel weg. Der Recorder verband sich neu, erzeugte das LSA erneut und tat es wieder.

Die Korrektur steht jetzt im Codekommentar, damit sie niemand aus Versehen entfernt:

// 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.

Ein Monitoring-Werkzeug, das genau das lahmlegt, was es überwacht, ist eine ganz eigene Art von peinlich, und auf einem Diagramm hätte ich es nie gefunden.


Wofür das Labor wirklich da ist

Früher habe ich das Labor als den Ort beschrieben, an dem ich Osprey vorführe. Das war die falsche Stellenbeschreibung, und sie richtigzustellen hat verändert, wie ich baue.

Das Labor ist nicht dazu da, Osprey gut aussehen zu lassen. Es ist dazu da, Osprey zu widerlegen.

Das heißt, das Labor ist genau so viel wert, wie es mir widersprechen kann: Es ist deshalb mit Absicht unbequem. Drei Carrier, die kein Protokoll teilen. Fünfzehn OSPF-Areas, wo drei sich besser vorführen ließen. Eine IS-IS-Domäne mit siebzehn Level-1-Areas. EIGRP, das überhaupt keine Link-State-Datenbank hat und jede Annahme des OSPF-Codes bricht. Dual-Stack-Links, deren v4- und v6-Kosten bei allen 124 um den Faktor 10 auseinanderliegen.

Nichts davon ergibt einen schöneren Screenshot. Alles davon hat etwas gefunden.

Die 360 erfundenen Korridore wurden nicht durch sorgfältiges Lesen gefunden und auch nicht durch einen Test, den ich klug genug war vorher zu schreiben. Sie wurden gefunden, weil ich 64 echte Router hatte, die man fragen konnte, was sie tatsächlich denken, und weil ich sie endlich alle auf einmal fragte, statt den zwei oder drei zu vertrauen, die ich stichprobenartig geprüft hatte.

Was die naheliegende Frage aufwirft, und die ist das Thema des nächsten Beitrags: Wenn das Netzwerk das Einzige ist, was Ihnen sagen kann, ob Ihr Modell stimmt, wie machen Sie daraus eine Testsuite?


Als Nächstes: Die Unit-Tests eines Netzwerkingenieurs.