← 전체 글

네트워크 엔지니어의 단위 테스트

네트워크 자체가 유일한 오라클일 때 토폴로지 엔진을 어떻게 테스트할 것인가?

Michel Wijnberg

토폴로지 엔진을 테스트하는 데에는 껄끄러운 성질이 하나 있습니다. 그것이 하는 일의 대부분에 대해 기대하는 답을 적어 둘 수가 없다는 것입니다.

직접 지어낸 그래프에 대해 Dijkstra 구현을 단위 테스트할 수는 있습니다. 그것은 당신의 Dijkstra가 Dijkstra라는 것을 증명합니다. 실제 LSDB에서 만들어 낸 그래프가 라우터들이 진짜로 쓰고 있는 그래프인지에 대해서는 아무것도 말해 주지 않습니다. 그리고 오직 그것만이 중요하고, 오직 그것만이 흥미로운 방식으로 틀릴 수 있습니다.

그래서 질문은 이렇게 됩니다. 오라클은 무엇인가?


라우터가 참조 구현입니다

제가 정착한 답은 이것을 둘로 나눕니다. RFC 2328은 명세입니다. 프로토콜이 무엇을 해야 하는지를 말합니다. 랩의 라우터들은 행동 오라클입니다. 물어보면 구현이 실제로 무엇을 하는지를 말해 줍니다. Osprey는 둘 다 만족해야 하고, 둘이 갈라지는 지점에서는 그 갈라짐 자체가 발견입니다. 다행인 것은 그 오라클이 바로 거기 있고, 물어보면 답해 준다는 것입니다.

그러면 검증은 파이프라인이 됩니다.

실제 라우터 64대
      ↓  show ip route ospf  /  show ip ospf database summary
기준 진실: 각 라우터가 스스로 설치한 테이블

Osprey의 재구성, 같은 순간, 같은 스코프

4,032개 순서 있는 쌍 전부를 서로 독립적인 네 가지 단언으로 비교

이것이 그 비교의 재구성 쪽입니다. 한 라우터 자신의 테이블을, Osprey가 쥐고 있는 모습입니다.

lax1-gw1의 라우팅 테이블: 유형 배지, 메트릭, ECMP 넥스트 홉 쌍이 있는 경로 428개
lax1-gw1의 라우팅 테이블: 유형 배지, 메트릭, ECMP 넥스트 홉 쌍이 있는 경로 428개

경로 428개가 유형별로 나뉩니다(C 직결, O 영역 내, O IA 영역 간, E2 외부). 각각에 메트릭, 광고 라우터, 그리고 뒤에 이어질 이야기에서 중요한 넥스트 홉 집합 전체가 붙습니다. 그래서 172.16.2.31, 172.16.2.32는 정렬해서 먼저 온 하나가 아니라, 실제 그대로의 2방향 ECMP로 나타납니다.

개수보다 네 가지 단언이 더 중요합니다.

단언왜 따로 두는가
메트릭 정확히 일치우연히 길이만 맞은 틀린 경로를 잡아냅니다
경로 유형 정확히 일치(intra / inter / external)잘못된 규칙으로 도달한 옳은 코스트를 잡아냅니다
ECMP 넥스트 홉 집합 전체 동일유효한 멤버 하나만 고르고 답이라 부르는 것을 잡아냅니다
종료 상태 = 요청한 목적지에서 delivered일찍 멈추고도 그럴듯해 보이는 순회를 잡아냅니다

이 중 어느 셋이 통과해도 모델은 틀릴 수 있습니다. 가정이 아닙니다. 이전 글의 그 버그가 만들어 낸 경로는 양 끝점이 맞고 실제보다 싼 코스트를 가졌습니다. 더 게으른 비교라면 정확히 그것을 그냥 통과시켰을 것입니다.

지금은 넷 모두가 4,032쌍 중 4,032쌍에서 통과합니다. 제가 신경 쓰는 숫자는 4,032가 아닙니다. 입니다. 제가 실패할 수 있게 만들어 둔 서로 독립적인 방법의 수 말입니다.


오라클이 없으면, 퍼징하십시오

기준 진실은 네트워크에 물어볼 수 있을 때 통합니다. 문제의 나머지 절반에는 통하지 않습니다. 장비가 형식이 깨졌거나, 잘렸거나, 그냥 이상한 무언가를 보내올 때 어떻게 되는가 하는 문제입니다.

Osprey에는 프로토콜 파서에 대한 절대 규칙이 하나 있고, 그것은 코드 품질에 대한 포부가 아니라 제품 제약입니다. 이상을 기록하고 계속 간다. 절대 죽지 않는다. 형식이 깨진 LSA 하나에 죽는 모니터링 시스템은, 하필 흥미로운 일이 벌어지고 있는 바로 그 순간에 당신의 가시성을 앗아 갑니다.

벤더가 당신을 놀라게 할 방법을 전부 열거할 수는 없습니다. 그래서 퍼징을 합니다.

make fuzz                          # every target, 10s each
make fuzz FUZZTIME=60s             # longer
make fuzz FUZZPKG=./internal/isis/ # one package

이 저장소의 현재 수치입니다.

퍼즈 타깃110
테스트 파일419
벤치마크98

퍼즈 타깃 110개는 허영 지표가 아닙니다. 공격자나 결함 있는 에이전트가 바이트를 좌우하는 파싱 경계마다 대략 하나씩입니다. OSPFv2와 OSPFv3 패킷과 모든 LSA 유형, IS-IS의 PDU와 TLV, BGP 메시지와 경로 속성, BMP 헤더, SNMP varbind 디코딩. 그 하나하나가 “이 장비가 나에게 무엇을 보낼까?”에 대한 정직한 답이 “전혀 모르겠습니다”인 자리입니다.

벤치마크가 있는 이유도 그와 맞닿아 있습니다. 프로토콜 파서는 핫 패스에 있고 버퍼에 sync.Pool을 써서 패킷당 할당 0으로 작성되어 있습니다. 그리고 b.ReportAllocs()가 붙은 벤치마크만이, 다음에 누군가 편리한 fmt.Sprintf를 하나 추가할 때 그것이 조용히 퇴행하는 것을 막아 줍니다.


진짜 데이터베이스가 필요한 테스트

세 번째 부류는 두 접근 모두를 거부합니다. Osprey의 어려운 질의들은 집계와 다중 조인을 동반한 CTE입니다. “활성 상태이면서 한 번도 폴링된 적 없는 장비는 무엇인가”, “어느 영역이 분할되었는가”, “시점 T의 그래프를 재구성하라”. 그것을 테스트하려고 데이터베이스를 목으로 만들면, 증명되는 것은 당신의 목이 당신의 기대와 일치한다는 사실뿐입니다.

그래서 그것들은 testcontainers를 통해 진짜 PostgreSQL을 상대로 돌아가고, 프로젝트 규칙은 명시적입니다. CTE / 집계 / 다중 조인 질의에는 실제 DB 테스트가 필요합니다. 빈 집합, NULL, 경계 조건, 그리고 가장 많은 버그를 잡아내는 상호 배타성까지 다룹니다. 어떤 질의가 장비를 범주로 나눈다면, 범주들의 합은 전체와 같아야 합니다. 그것이 얼마나 자주 어긋나는지, 그리고 숫자 하나하나가 따로 보면 다 그럴듯한 대시보드에서 그것이 얼마나 보이지 않는지는 놀라울 정도입니다.


벤더는 RFC를 읽어 주지 않습니다

마지막 부류는 내부 테스트를 아무리 많이 해도 찾을 수 없는 것들이고, 랩이 밥값을 하는 자리입니다. 실제 장비는 당신이 읽은 표준을 구현하지 않습니다. 그 옆에 있는 무언가를 구현합니다.

이 랩에서 몇 가지, 전부 실제로 시간을 잡아먹은 것들입니다.

있지도 않은 MIB. 랩의 EVPN 서비스 파드에 있는 vEOS 리프들은 IS-IS가 아니라 OSPF로 발견해야 했습니다. 전혀 근사하지 않은 이유로, vEOS가 두 ISIS-MIB 트리 중 어느 것도 구현하지 않기 때문입니다. 부분적으로가 아닙니다. 아예 없습니다. 아무리 올바른 SNMP 코드를 써도 고쳐지지 않습니다. 유일하게 옳은 대응은 그들이 실제로 지원하는 프로토콜로 발견하고, 그 이유를 문서로 남기는 것입니다.

표준 이전의 방언. MPLS L2VPN 슈도와이어에는 표준 MIB가 있습니다. RFC 5601입니다. 실제 IOS는 그 대신 표준 이전의 Cisco 방언을 싣고 나옵니다. 그래서 폴러는 둘 다 구현하고 자동으로 물러섭니다. 그리고 “우리는 둘 다 지원합니다”는 정확히 썩기 쉬운 부류의 주장이므로, 두 드라이버는 패리티 게이트에 묶여 표준 MIB 경로가 Cisco 경로에서 조용히 갈라지지 못하게 되어 있습니다.

시간이 잊은 암호. 여기 라우터들은 IOL 이미지 두 개로 돕니다(124대는 IOS 15.2(4)S7, 나머지 68대는 15.4(2)T4). 그리고 전부가 SHA-1 키 교환과 ssh-rsa 호스트 키만 제공합니다. 15.2 무리는 그 위에 암호 방식도 CBC가 한계입니다. 기본 설정의 현대 OpenSSH 클라이언트는 키 교환 자체를 거부하므로, 연결은 인증을 시도해 보기도 전에 죽습니다. Osprey의 SSH 클라이언트는 레거시 폴백을 명시적으로 지고 다닙니다. 그러지 않으면 대안은 벤더의 슬라이드에서는 동작하고 고객의 실제 자산에서는 동작하지 않는 터미널 기능이기 때문입니다.

한 번도 시작된 적 없는 서버. 이 랩의 이전 64라우터 구성에서는 어디에나 ip ssh version 2가 설정되어 있었지만 RSA 키는 한 번도 생성되지 않았고, 그래서 SSH 서버는 그냥 돌고 있지 않았습니다. 모든 노드에서, 그것도 보이지 않게. show ip sshDisabled라고 했고 설정은 완결되어 보였습니다.

이 중 어느 것도 명세를 읽어서 발견되는 것이 아닙니다. 자기 코드를 실제 장비에 겨누고 그것이 실패하는 것을 지켜보다가 발견되는 것들입니다.


토폴로지 엔진은 사실 무엇인가

검증 하네스를 만들던 어느 시점에, 이 물건의 모양이 저에게 더 또렷해졌습니다.

입력:   지저분하고, 부분적이고, 모순되는 네트워크 상태
출력:   결정론적 모델
오라클: 라우터 자신

그것은 거의 컴파일러입니다. 그렇게 생각할 때 쓸모 있는 귀결은, 컴파일러는 그럴듯함이 아니라 적합성으로 평가된다는 것입니다. “생성된 코드가 대충 맞아 보이는데요”를 받아들이는 사람은 없습니다.

추측하기를 거부하는 제품에 대해 제가 시리즈를 쓸 수 있는 이유는, 제가 유별나게 원칙적이어서가 아닙니다. 요청만 하면 언제든 저를 반박해 줄 기계가 64대 있고, 그 전부에게 한꺼번에 묻는 하네스가 있기 때문입니다.

그것이 없다면 “Osprey는 경로를 정확히 계산한다”는 의견입니다. 그것이 있으면, 네 가지 단언에 대해 4,032분의 4,032이고, 화요일 오후에 재현 가능한 사실입니다.

차이는 그것이 전부입니다.


이것이 시리즈 2의 마지막 글입니다. 시리즈 1인 IGP 세 개, 지도 하나, 제로 풋프린트, 홉 바이 홉의 진실, Osprey가 추측하기를 거부하는 것은 이번 글이 시험대에 올린 그 원칙들을 다룹니다.