← Todos os artigos

O dia em que minha topologia mentiu para mim

Um postmortem de engenharia sobre o meu próprio produto, e o laboratório que o pegou

Michel Wijnberg

Construí o Osprey para que ele me contasse a verdade sobre redes. Depois descobri que ele vinha mentindo para mim. Com confiança, por escrito, numa tela que eu vinha demonstrando para as pessoas.

Isto é o que aconteceu, como eu peguei e o que eu mudei. É o texto mais útil desta série, porque tudo o mais que escrevi sobre honestidade é barato até que a coisa auditada seja o seu próprio trabalho.


O que eu acreditava

O motor de caminhos do Osprey fazia o que quase toda ferramenta de caminho faz. Dados uma origem e um destino, ele rodava um Dijkstra ciente de áreas a partir da origem sobre a topologia OSPF e desenhava a rota mais barata.

Eu tinha bons motivos para confiar. Ele respeitava as regras da RFC 2328 com que eu me importava: intra-área preferido sobre interárea, interárea transitando o backbone, E1 antes de E2, ECMP enumerado em vez de colapsado. Tinha testes. Batia com o meu modelo mental do laboratório. Quando eu clicava em dois roteadores, um caminho sensato acendia.

Ele também estava descrevendo, em uma consulta interárea a cada oito, uma rota que pacote nenhum jamais tomaria.


Como eu descobri

Não por um relato de bug. Por uma tarefa braçal.

Eu estava montando um arcabouço de validação para outra coisa completamente diferente e queria um conjunto de dados de verdade de referência para conferir. O laboratório tem 64 roteadores no AS 200, todos alcançáveis por SSH, todos dispostos a imprimir a própria tabela de roteamento. Então fiz a coisa burra e óbvia: entrei nos 64 e despejei show ip route ospf de cada um deles.

Depois, em vez de olhar por cima, comparei a tabela real de cada roteador com o que o motor de caminhos do Osprey afirmava ser o caminho, para todos os 4.032 pares ordenados origem/destino.

360 deles discordaram.

Não “o custo está um pouco fora”. O caminho desenhado continha um roteador que o pacote comprovadamente nunca visita.


O caso canônico

Aqui vai um, com cada linha medida no equipamento e não modelada:

RoteadorO que diz sobre o destinoEncaminha para
dfw1-gw2metric 20031, type inter areadfw1-cr1
dfw1-cr1metric 30001, type intra areaord1-cr1não dfw2-cr1
ord1-cr1metric 20001, type intra areajfk1-cr1
jfk1-cr1metric 10001, type intra arealhr1-cr1
lhr1-cr1metric 1, connectedentregue

O Osprey desenhou:

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

O pacote nunca chega a dfw2-cr1. O caminho do Osprey era mais barato. Ele só não era um caminho que roteador nenhum construiria.

Duas regras independentes da RFC 2328 fazem dfw1-cr1 recusar o corredor barato, e eu não havia implementado nenhuma das duas no nível de um roteador intermediário individual:

§16 passo (3) / §16.2 ¶1: o portão do backbone. Um roteador ligado a várias áreas examina apenas os summary-LSAs do backbone. O summary mais barato de dfw2-cr1, de custo 20011, está genuinamente no banco de dados da área 4.6.23.0 de dfw1-cr1 (confirmei com show ip ospf database summary, porque eu também não acreditei), mas não é um candidato, porque dfw1-cr1 é um ABR e aquele summary não chegou pela área 0. A RFC 3509 §2.1/§2.2, o comportamento de ABR da Cisco que de fato roda nesses equipamentos, condiciona isso a haver uma conexão ativa com o backbone, que dfw1-cr1 tem.

§16.2 passo (6): intra ganha de inter, ponto final. “If the paths present in the table are intra-area paths, do nothing with the LSA.” dfw1-cr1 já tem um caminho intra-área com métrica 30001. O summary de 20011 perde para ele. A métrica nunca entra na comparação. Esta é uma regra sobre o tipo de caminho.

Qualquer uma das duas regras, sozinha, mata o corredor. Meu motor não conseguia enxergar nenhuma das duas, porque nunca perguntou nada a dfw1-cr1. Ele perguntou a visão de mundo do roteador de origem e então traçou uma linha por outros oito roteadores como se eles a compartilhassem.


O formato do erro

Vale nomear isso com precisão, porque o detalhe específico do OSPF é menos interessante que a categoria.

Eu havia modelado um caminho quando a rede implementa uma sequência de decisões independentes. Essas duas coisas coincidem exatamente enquanto todo roteador do trajeto usar a mesma regra de decisão sobre o mesmo conjunto de entrada. Dentro de uma única área OSPF elas usam (mesmo LSDB, mesmo algoritmo, mesmo resultado), e é por isso que a contagem de defeitos para pares intra-área foi:

0 de 1.193.

No instante em que um ABR entra, o conjunto de entrada deixa de ser compartilhado: um ABR olha deliberadamente para um conjunto diferente de LSAs em relação aos roteadores à sua volta. Meu modelo não tinha onde colocar esse fato, porque meu modelo tinha um grafo e um Dijkstra, e não sessenta e quatro roteadores cada um com uma opinião.

ParesCom um salto que roteador nenhum tomaria
Intra-área1.1930
Interárea2.839360 (12,68%)
Todos4.032360 (8,93%)

A correção, e como eu sei que funcionou

O motor agora percorre a cadeia. A cada salto ele avalia a rota que aquele roteador instalou em direção ao endereço de destino, usando o LSDB daquele roteador, suas associações de área, seu status de ABR e sua mistura de distâncias administrativas; depois segue o próximo salto que ele de fato seleciona, e repete.

Aí rodei os mesmos 4.032 pares de novo, contra a mesma verdade de referência:

VerificaçãoResultado
Métrica do próprio roteador reproduzida exatamente4.032 / 4.032
Tipo de caminho (intra / inter / externo) exato4.032 / 4.032
Conjunto completo de próximos saltos ECMP instalados idêntico4.032 / 4.032
A cadeia termina em delivered no destino solicitado4.032 / 4.032

Não “melhorou”. Idêntico em cada par, incluindo o conjunto completo de próximos saltos de custo igual em vez de um membro escolhido arbitrariamente.

O caminho corrigido: uma tabela de 10 saltos com a área, as portas e o custo de cada roteador
O caminho corrigido: uma tabela de 10 saltos com a área, as portas e o custo de cada roteador

Essa é a mesma consulta que antes inventava um salto, agora mostrando cada roteador que de fato toma uma decisão, a área em que a toma e as portas físicas dos dois lados. O total, 30061, é a métrica que a própria origem tem instalada: literalmente o número que o show ip route imprime em lax1-gw1.

E onde a cadeia não é computável (OSPFv3, IS-IS, EIGRP, um escopo parcial, uma área com enlace virtual), o motor não recua em silêncio para a figura antiga. Ele devolve a visão enraizada na origem com um model_note nomeando o motivo, renderizado como um passo próprio da explicação. Essa recusa é o assunto do quarto texto, e ela existe por causa deste bug.


Um menor, para equilibrar

Nem toda ferida autoinfligida é um algoritmo. Aqui vai uma de duas linhas que me custou uma tarde.

O gravador GRE do Osprey forma uma adjacência OSPF real, então precisa originar um Router-LSA. No começo, ele anunciava a sub-rede interna do túnel como rede stub: parecia certo, era legal segundo a RFC e completamente razoável.

Todo roteador da área então instalou uma rota em direção ao prefixo do túnel. Essa rota sobrepôs-se ao caminho de underlay sobre o qual o próprio túnel GRE trafegava. O transporte externo quebrou. A adjacência caiu. O gravador reconectou, originou o LSA de novo, e fez tudo outra vez.

A correção está agora no comentário do código, para que ninguém a remova por acidente:

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

Uma ferramenta de monitoramento que derruba a coisa que está monitorando é um tipo especial de vergonha, e eu não teria encontrado isso num diagrama.


Para que serve o laboratório de verdade

Eu costumava descrever o laboratório como o lugar onde eu demonstro o Osprey. Essa era a descrição de cargo errada, e acertá-la mudou meu jeito de construir.

O laboratório não está ali para deixar o Osprey bonito. Está ali para provar que o Osprey está errado.

O que significa que o laboratório só vale na medida em que consegue me contradizer: por isso ele é desconfortável de propósito. Três operadoras que não compartilham protocolo. Quinze áreas OSPF onde três demonstrariam melhor. Um domínio IS-IS com dezessete áreas de nível 1. EIGRP, que não tem banco de dados de estado de enlace nenhum e quebra toda suposição que o código de OSPF faz. Enlaces dual-stack cujos custos v4 e v6 diferem por 10× em todos os 124.

Nada disso rende uma captura de tela mais bonita. Tudo isso já pegou alguma coisa.

Os 360 corredores fictícios não foram encontrados por leitura cuidadosa, nem por um teste que eu fosse esperto o bastante para escrever de antemão. Foram encontrados porque eu tinha 64 roteadores reais a quem se podia perguntar o que eles de fato pensavam, e finalmente perguntei a todos de uma vez em vez de confiar nos dois ou três que eu havia conferido por amostragem.

O que levanta a pergunta óbvia, e ela é o assunto do próximo texto: se a rede é a única coisa capaz de dizer se o seu modelo está certo, como você transforma isso numa suíte de testes?


A seguir: Os testes unitários de um engenheiro de redes.