giovedì 24 gennaio 2019

OSPF negli switch Nexus: attenti al loop ...

Quando si dice "il diavolo si nasconde nei dettagli ...". Mi è capitato di recente, in una consulenza presso una grande azienda Enterprise, un problema che mi ha fatto riflettere e capire che non si finisce mai di imparare. Lo riporto in questo post perché il problema riguarda un aspetto dell'implementazione di OSPF negli switch Cisco della serie Nexus, tra i più utilizzati nei Data Center e non solo. 

Schematizzo prima il problema, con una topologia semplificata rispetto a quella reale, ma che non cambia la sostanza. La topologia è riportata nella figura seguente.

I due router RE e RW sono ASR9k, che quindi utilizzano il sistema operativo IOS XR (NOTA: ciò che dirò nel seguito vale anche per router che utilizzano IOS o IOS XE). NE e NW sono invece due switch Nexus (che nella realtà erano Nexus 7706) che quindi utilizzano come sistema operativo NX-OS. I due Nexus hanno un collegamento diretto in area 0 OSPF, mentre il collegamento NE-RE è in area 1 OSPF e il collegamento NW-RW in area 2 OSPF. Su entrambi i router RE e RW viene redistribuito in OSPF il prefisso IP 10.21.22.0/24, appreso da RE e RW in qualche modo (come è ininfluente). La redistribuzione è stata configurata come di tipo External 1, con metrica esterna 20. Infine, la metrica dell'interfaccia E1/1 su NE è stata posta a 1000, con l'intento di far passare il traffico entrante nei Nexus e diretto verso il prefisso redistribuito, in prima scelta attraverso il router RW.

NOTA: per chi di voi non ricordasse i dettagli della redistribuzione in OSPF, in particolare dei LSA di tipo 5 e del tipo di metrica External 1 o External 2, rimando all'Appendice di questo post precedente, sempre su questo blog.

IL PROBLEMA ...
Bene, lo scenario appena descritto sembra uno dei più classici che si possa trovare, all'apparenza innocuo. Ma qui arriva la sorpresa. Questo è l'entry della tabella di routing su NE, per il prefisso redistribuito 10.21.22.0/24:

NE# show ip route 10.21.22.0/24
IP Route Table for VRF "default"
'*' denotes best ucast next-hop
'**' denotes best mcast next-hop
'[x/y]' denotes [preference/metric]
'%<string>' in via output denotes VRF <string>

10.21.22.0/24, ubest/mbest: 1/0
    *via 10.1.1.2, Eth1/1, [110/1020], 00:00:24, ospf-1, type-1

che non è proprio l'obiettivo che ci eravamo prefissi. Qui c'è qualcosa che non quadra. La redistribuzione è di tipo External 1 (mostrato da "type-1" nella visualizzazione) e quindi perché NE per raggiungere il prefisso 10.21.22.0/24 sceglie di passare per il collegamento diretto NE-RE, con un costo totale (interno+esterno) del percorso 1020 (mostrato nella visualizzazione, il perché 1020 è lasciato come semplice esercizio), piuttosto che seguire il percorso NE-->NW-->RW, che ha costo totale 100 ? (NOTA: il costo totale 100 deriva dal fatto che le interfacce degli switch Nexus hanno una metrica impostata a 40 e che RW redistribuisce il prefisso con metrica esterna 20; i dettagli sono lasciati al lettore come esercizio).

Seguendo la "teoria" mi sarei aspettato che il Next-Hop fosse stato il router NW e non il router RE. E quindi cosa succede ? Di solito in questi casi, per lavarsi un po' la coscienza si pensa subito "ma qui c'è un bug, è evidente". Prima di fare un'affermazione del genere e prendere per "tonti" quelli che hanno fatto il codice dell'NX-OS (che poi tonti certamente non sono), bisogna analizzare il problema e vedere se esiste una soluzione. E la soluzione c'è, non è assolutamente un bug ...

LA SOLUZIONE
Una volta posto di fronte a questo problema, mi son chiesto subito, ma per quale ragione NE sceglie come ASBR di uscita RE e non RW, visto che il costo totale per raggiungere RE è 1000 e il costo totale per raggiungere RW è 80 ? (NOTA: posso fare questo ragionamento poiché le metriche esterne sono entrambe 20, altrimenti avrei dovuto considerare i costi complessivi interni+esterni). 

La prima cosa che mi è venuta in mente è che NE sceglie questo percorso perché l'ASBR RE è raggiungibile da un percorso intra-area mentre l'ASBR RW da un percorso inter-area. Questo è un fenomeno noto per i percorsi interni OSPF, dove indipendentemente dai costi vengono sempre e comunque preferiti i percorsi intra-area ai percorsi inter-area (vedi questo post scritto nel lontano 2014), ma non è scritto da nessuna parte che questo principio è anche valido per i percorsi esterni (ho consultato vari testi, tra cui la sacra bibbia del routing di Jeff Doyle "Routing TCP/IP"). 

E allora, per dirimere la questione sono andato alla fonte, ossia alla RFC 2328, quella più aggiornata che specifica l'OSPFv2. Bene, alla sezione 16.4.1 "External path preferences" c'è scritto:
  • Intra-area paths using non-backbone areas are always the most preferred.
  • The other paths, intra-area backbone paths and inter-area paths, are of equal preference.
ossia, tradotto, prima vengono scelti gli ASBR raggiungibili con percorsi intra-area non-backbone e quindi gli ASBR raggiungibili attraverso percorsi inter-area o intra-area del backbone (area 0).

Possibile che in tanti anni che traffico (e come diceva Albertone Sordi, "m'arangio" (con una erre)) con questa roba non mi ero mai accorto di questo dettaglio ? E qui mi è venuto un sospetto, niente niente che qualche sistema operativo non utilizza queste regole e invece si rifà alla versione precedente, la RFC 1583, che alla sezione 16.4 "Calculating AS external routes", al punto (6) così recita:

Type 1 external paths are compared by looking at the sum of the distance to the forwarding address and the advertised type 1 metric (X+Y).  Type 2 external paths are compared by looking at the advertised type 2 metrics, and then if necessary, the distance to the forwarding addresses.

Anche qui, traducendo, la RFC 1583 dice ciò che sappiamo tutti, ossia che per determinare l'ASBR ottimo per il traffico verso i prefissi redistribuiti con metrica di tipo External 1, si deve tener conto del costo totale interno (ossia dal router fino all'ASBR), più il costo esterno. Non si fa alcuna menzione sulla preferenza di ASBR intra-area piuttosto che inter-area.

Conclusione: l'implementazione OSPF degli switch Nexus segue la RFC 2328.

Come risolvere il problema allora ? La prima soluzione che mi è venuta in mente è stata quella di utilizzare la nuova (si fa per dire, la RFC 5185 è del 2008) funzionalità che consente di costruire sullo stesso link adiacenze multiarea. Ho così realizzato due ulteriori adiacenze sul collegamento NE-NW, una in area 1 e l'altra in area 2. I comandi da eseguire sono in questo caso i seguenti (su entrambi gli switch NE e NW):

interface Ethernet1/2
  ip router ospf [stringa] multi-area 1
  ip router ospf [stringamulti-area 2

Il risultato è che tra NE e NW vengono realizzate due ulteriori adiacenze, oltre a quella in area 0 iniziale, una appartenente all'area 1 e l'altra all'area 2:

NE# show ip ospf neighbors
OSPF Process ID 1 VRF default
Total number of neighbors: 4
(M) indicates multi-area adjacency interface
Neighbor ID     Pri State            Up Time  Address         Interface
RE                   1 FULL/ -          00:40:52 10.1.1.2        Eth1/1
NW                  1 FULL/ -          00:35:50 10.0.0.2        Eth1/2
NW                  1 FULL/ -          00:02:39 10.0.0.2        Eth1/2(M)
NW                  1 FULL/ -          00:02:38 10.0.0.2        Eth1/2(M)

Così facendo, NE vede entrambi gli ASBR come intra-area e quindi segue il percorso a costo minore:

NE# show ip route 10.21.22.0/24
IP Route Table for VRF "default"
'*' denotes best ucast next-hop
'**' denotes best mcast next-hop
'[x/y]' denotes [preference/metric]
'%<string>' in via output denotes VRF <string>

10.21.22.0/24, ubest/mbest: 1/0
    *via 10.0.0.2, Eth1/2, [110/100], 00:01:45, ospf-1, type-1

ottenendo così la politica di routing desiderata. 

Soluzione elegante ma un po' complessa da gestire, soprattutto in reti di dimensioni maggiori. Scartabellando qua e la nella documentazione Cisco degli switch Nexus ho invece trovato una soluzione molto più banale, ma molto più efficace, anzi, per quello che dirò nella prossima sezione, sicuramente consigliata. C'è un comando che rende i Nexus, per questo aspetto, compatibili con la RFC 1583:

SW-NXOS(config)# router ospf stringa
SW-NXOS(config-router)# rfc1583compatibility

Eseguito questo comando sugli switch Nexus NE e NW, tutto funziona, senza bisogno di adiacenze multiarea.

Ma la storia non finisce qui ...

ATTENTI AL LOOP ...
I loop sono sempre in agguato, anche dove meno te lo aspetti !!!

Ho rifatto delle prove con la stessa topologia descritta sopra, con identico scenario e ho potuto verificare che i router Cisco con IOS, IOS XE e IOS XR supportano invece la RFC 1583, così come i router Juniper con il Junos OS. E questo, in reti miste, anche se mono-vendor Cisco, se non valutato con cura, può provocare facilmente dei forwarding loop

Supponiamo di considerare la rete test descritta nella figura seguente, dove tutti i router utilizzano IOS o IOS XE o IOS XR, che come detto supportano la RFC 1583, mentre lo switch SW-NXOS è un Nexus, che a questo punto sappiamo supportare la RFC 2328.






















I numeri vicino ai collegamenti sono le metriche (identiche) delle interfacce ai lati del collegamento. Per ciascun collegamento (o meglio, adiacenza) è indicata l'area di appartenenza.

Ora, il router RW-1 (compatibile RFC 1583) vedrà come Next-Hop per raggiungere il prefisso 10.21.22.0/24 lo switch SW-NXOS. Infatti RW-1 ha un costo totale, passando da RW-2, pari a 30 = 10 (metrica dell'interfaccia verso RW-2) + 20 (costo esterno della redistribuzione), mentre il percorso alternativo attraverso SW-NXOS e RE-1 ha costo totale 23 = 1 + 1 + 1 + 20. Tutto questo perché RW-1 non tiene conto dei percorsi intra/inter-area per raggiungere gli ASBR, ma confronta esclusivamente i costi complessivi. Lo switch SW-NXOS (compatibile RFC 2328) d'altra parte ha come Next-Hop RW-1 poiché considera migliore il percorso intra-area verso gli ASBR, indipendentemente dai costi. E quindi questo genera un classico forwarding loop tra SW-NXOS e RW-1 !!!

Rendendo lo switch Nexus compatibile con la RFC 1583 con il comando descritto alla fine della sezione precedente, il problema è risolto e il loop scompare (prova lasciata al lettore come esercizio).

CONCLUSIONI
Come ho scritto all'inizio del post, spesso il diavolo si nasconde nei dettagli, e i dettagli contano. Nel nostro caso trascurare (o anche, non conoscere) questi dettagli avrebbe portato a un routing non ottimale (rispetto almeno ai propri desiderata), e in altri casi potrebbe portare a dannosi forwarding loop. Poi una volta capito il problema qualche workaround si trova sempre. Ma può capitare che inconsapevolmente vi siano in produzione situazioni come quelle descritte in questo post (come detto l'idea mi è venuta da una situazione reale durante una migrazione di router), e quindi è necessario sapere come risolvere il problema. 
Ci sarebbe da trattare anche un altro problema di compatibilità che riguarda l'utilizzo di aree NSSA, tra la RFC 1587 e la sua evoluzione RFC 3101, entrambe dal titolo "The OSPF NSSA Option". Ma dato lo scarso utilizzo delle aree OSPF NSSA, tralascio questo aspetto.




1 commento:

  1. Ciao Tiziano e tutti,

    Questa regola che implementa NEXUS (ma sapevo anche IOSXE) è necessaria per una questione di loop prevention ed è spiegato nella sezione 'G.7 di RFC 2178' @ https://tools.ietf.org/search/rfc2178#appendix-G.7 dove si menziona una situazione di loop scoperta da Richard Woundy e dove viene evidenziato che questa nuova regola di preferenza non è backward compatibile e quindi causa loop tra chi la implementa (leggi sezione G7 di RFC 2178) e chi no (leggi RFC1583). RFC 2178 quindi introduce un knob ‘RFC1583Compatibility" che se settato su disabled consente di implementare la sezione G.7 di RFC 2178. RFC 2328 non fa altro che recepire la possibilità di implementare o meno questo knob, ovvero di implementare o meno la sezione G.7 di RFC 2178.
    A conferma di questo, nella sezione 16.4.1 di RFC 2328 citata nel blog viene infatti esplicitamente detto “This section only applies when RFC1583Compatibility is set to "disabled".”
    Per questa regola quindi io parlerei di implementazione o meno della sezione G.7 di RFC 2178 citata a sua volta da RFC 2328 piuttosto che di aderenza o meno all’ RFC 2328. E’ un dettaglio che a mio avviso fornisce maggiore chiarezza.

    Una cosa paradossale da sottolineare infine è che chi non implementa la ‘sezione G.7 di RFC 2178’ (tutti meno quantomeno NXOS e credo anche IOSXE ?) si espone sia ad una situazione di loop assoluta (cioè indipendente dall’interop) sia ad una situazione di loop verso chi la implementa (quantomeno NXOS e IOSXE ?).

    Da non sottovalutare infine il fatto che la tecnica di anycast OSPF viene utilizzata da molti servizi UDP in rete ed in primi dal DNS. Alla luce di quanto detto sarebbe pertanto a mio avviso consigliabile per una soluzione più solida un anycast OSPF ‘interno’ piuttosto di uno ‘esterno’ esterno.

    Ciao
    Andrea Di Donato

    RispondiElimina