← 전체 글

내 토폴로지가 나에게 거짓말한 날

제 제품에 대한 엔지니어링 포스트모템, 그리고 그것을 잡아낸 랩

Michel Wijnberg

저는 네트워크에 관한 진실을 듣기 위해 Osprey를 만들었습니다. 그런데 그것이 저에게 거짓말을 해 왔다는 사실을 알게 됐습니다. 자신 있게, 글로, 제가 사람들에게 시연하고 다니던 바로 그 캔버스 위에서 말입니다.

무슨 일이 있었는지, 어떻게 잡아냈는지, 무엇을 바꿨는지를 씁니다. 이 시리즈에서 가장 쓸모 있는 글입니다. 정직함에 대해 제가 써 온 다른 모든 말은, 감사 대상이 자기 자신의 작업이 되기 전까지는 값싸기 때문입니다.


제가 믿고 있던 것

Osprey의 경로 엔진은 거의 모든 경로 도구가 하는 일을 했습니다. 출발지와 목적지가 주어지면 OSPF 토폴로지 위에서 출발지로부터 영역을 인식하는 Dijkstra를 돌리고, 가장 싼 경로를 그렸습니다.

믿을 만한 이유가 있었습니다. 제가 신경 쓰던 RFC 2328 규칙들을 지켰습니다. 영역 내가 영역 간보다 우선하고, 영역 간은 백본을 경유하고, E1이 E2보다 앞서고, ECMP는 뭉개지 않고 열거했습니다. 테스트도 있었습니다. 랩에 대한 제 머릿속 모델과도 맞았습니다. 라우터 두 개를 클릭하면 그럴듯한 경로가 켜졌습니다.

동시에 그것은 영역 간 질의 여덟 번 중 한 번꼴로, 어떤 패킷도 결코 타지 않을 경로를 기술하고 있었습니다.


어떻게 알게 됐나

버그 리포트가 아니라, 잡일에서 알게 됐습니다.

전혀 다른 무언가를 위해 검증 하네스를 만들던 중이었고, 그것을 대조할 기준 진실 데이터셋이 필요했습니다. 랩에는 AS 200에 라우터 64대가 있고, 전부 SSH로 닿고, 전부 자기 라우팅 테이블을 기꺼이 출력해 줍니다. 그래서 멍청할 만큼 뻔한 일을 했습니다. 64대에 전부 로그인해서 각각의 show ip route ospf를 덤프했습니다.

그런 다음 눈으로 훑는 대신, 각 라우터의 실제 테이블을 Osprey의 경로 엔진이 주장하는 경로와 4,032개의 순서 있는 출발지/목적지 쌍 전부에 대해 비교했습니다.

그중 360개가 어긋났습니다.

“코스트가 조금 어긋났다”가 아닙니다. 그려진 경로 안에, 패킷이 결코 들르지 않는다고 증명할 수 있는 라우터가 들어 있었습니다.


대표 사례

한 가지를 보여 드립니다. 모든 행은 모델링이 아니라 장비에서 측정한 값입니다.

라우터목적지에 대해 하는 말포워딩 대상
dfw1-gw2metric 20031, type inter areadfw1-cr1
dfw1-cr1metric 30001, type intra areaord1-cr1dfw2-cr1아님
ord1-cr1metric 20001, type intra areajfk1-cr1
jfk1-cr1metric 10001, type intra arealhr1-cr1
lhr1-cr1metric 1, connected전달 완료

Osprey는 이렇게 그렸습니다.

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

패킷은 dfw2-cr1에 결코 도달하지 않습니다. Osprey의 경로가 더 쌌던 것은 맞습니다. 다만 어떤 라우터도 만들어 내지 않을 경로였을 뿐입니다.

서로 무관한 RFC 2328의 두 규칙이 dfw1-cr1로 하여금 그 싼 통로를 거부하게 만드는데, 저는 개별 중간 라우터 수준에서 그 두 규칙 중 어느 것도 구현하지 않았습니다.

§16 단계 (3) / §16.2 첫 문단: 백본 게이트. 여러 영역에 붙어 있는 라우터는 백본 summary-LSA 검사합니다. dfw2-cr1의 코스트 20011짜리 더 싼 summary는 실제로 dfw1-cr1의 영역 4.6.23.0 데이터베이스 안에 있습니다(저도 믿기지 않아서 show ip ospf database summary로 확인했습니다). 그러나 그것은 후보가 아닙니다. dfw1-cr1이 ABR이고 그 summary가 영역 0을 통해 들어온 것이 아니기 때문입니다. RFC 3509 §2.1/§2.2, 곧 이 장비들에서 실제로 돌아가는 Cisco ABR 동작은 이 조건을 활성 백본 연결이 있는지에 걸어 두는데, dfw1-cr1은 그것을 가지고 있습니다.

§16.2 단계 (6): 영역 내가 영역 간을 이깁니다, 끝. “If the paths present in the table are intra-area paths, do nothing with the LSA.” dfw1-cr1은 이미 메트릭 30001짜리 영역 내 경로를 쥐고 있습니다. 20011짜리 summary는 그것에 집니다. 메트릭은 비교에 아예 들어오지 않습니다. 이것은 경로 유형에 관한 규칙입니다.

두 규칙 중 어느 하나만으로도 그 통로는 죽습니다. 제 엔진은 둘 다 볼 수 없었습니다. dfw1-cr1에게 아무것도 묻지 않았기 때문입니다. 제 엔진이 물은 것은 출발 라우터가 보는 세상이었고, 그러고 나서 다른 여덟 대의 라우터를 관통하는 선을 마치 그들도 같은 세상을 본다는 듯 그었습니다.


실수의 모양

구체적인 OSPF 세부보다 그것이 속한 범주가 더 흥미로우므로, 정확히 이름을 붙여 둘 가치가 있습니다.

저는 하나의 경로를 모델링했지만, 네트워크가 구현하는 것은 서로 독립적인 결정들의 연쇄입니다. 이 둘은 도중의 모든 라우터가 같은 입력 집합 위에서 같은 판단 규칙을 쓰는 한 정확히 일치합니다. 단일 OSPF 영역 안에서는 그것이 성립합니다(같은 LSDB, 같은 알고리즘, 같은 결과). 그래서 영역 내 쌍의 결함 수는 이러했습니다.

1,193개 중 0개.

ABR이 끼어드는 순간 입력 집합은 더 이상 공유되지 않습니다. ABR은 주변 라우터들과 다른 LSA 집합을 의도적으로 봅니다. 제 모델에는 그 사실을 놓을 자리가 없었습니다. 제 모델에는 그래프 하나와 Dijkstra 한 번이 있었을 뿐, 저마다 의견을 가진 예순네 대의 라우터는 없었기 때문입니다.

어떤 라우터도 택하지 않을 홉을 포함
영역 내1,1930
영역 간2,839360(12.68%)
전체4,032360(8.93%)

해법, 그리고 그것이 통했다는 것을 아는 방법

엔진은 이제 체인을 따라갑니다. 각 홉에서 그 라우터 자신의 LSDB, 영역 소속, ABR 여부, 어드미니스트레이티브 디스턴스 조합을 써서 그 라우터가 목적지 주소를 향해 설치해 둔 경로를 평가하고, 그 라우터가 실제로 고른 넥스트 홉을 따라간 뒤 이를 반복합니다.

그런 다음 같은 4,032쌍을, 같은 기준 진실에 대해 다시 돌렸습니다.

검사결과
라우터 자신의 메트릭을 정확히 재현4,032 / 4,032
경로 유형(영역 내 / 영역 간 / 외부) 정확히 일치4,032 / 4,032
설치된 전체 ECMP 넥스트 홉 집합이 동일4,032 / 4,032
요청한 목적지에서 체인이 delivered로 종료4,032 / 4,032

“개선됐다”가 아닙니다. 모든 쌍에서 동일하며, 임의로 고른 한 멤버가 아니라 등가 코스트 넥스트 홉 집합 전체까지 동일합니다.

수정된 경로: 각 라우터 자신의 영역, 포트, 코스트가 담긴 10홉 표
수정된 경로: 각 라우터 자신의 영역, 포트, 코스트가 담긴 10홉 표

예전에는 없는 홉을 지어내던 바로 그 질의가, 이제는 실제로 결정을 내리는 모든 라우터와 그 결정이 내려진 영역, 그리고 양쪽의 물리 포트를 보여 줍니다. 총합 30061은 출발지 자신이 설치한 메트릭이며, 말 그대로 lax1-gw1에서 show ip route가 찍어 내는 숫자입니다.

그리고 체인을 계산할 수 없는 곳(OSPFv3, IS-IS, EIGRP, 부분 스코프, 가상 링크를 가진 영역)에서는 엔진이 조용히 옛 그림으로 물러나지 않습니다. 이유를 밝히는 model_note를 달아 출발지를 뿌리로 하는 뷰를 돌려주고, 그것을 별도의 설명 단계로 렌더링합니다. 그 거부가 네 번째 글의 주제이고, 그것이 존재하는 이유가 바로 이 버그입니다.


균형을 위해, 더 작은 이야기 하나

자해가 모두 알고리즘인 것은 아닙니다. 제 오후 하나를 잡아먹은 두 줄짜리 이야기입니다.

Osprey의 GRE 레코더는 진짜 OSPF 인접을 맺으므로 Router-LSA를 생성해야 합니다. 초기에는 터널의 내부 서브넷을 스텁 네트워크로 광고했습니다. 보기에 맞고, RFC상 적법하며, 완전히 합리적인 선택이었습니다.

그러자 영역 안의 모든 라우터가 터널 프리픽스로 향하는 경로를 설치했습니다. 그 경로가 GRE 터널 자신이 타고 있던 언더레이 경로를 덮어썼습니다. 외부 전송이 깨졌습니다. 인접이 떨어졌습니다. 레코더가 재접속해 LSA를 다시 생성했고, 같은 일이 또 벌어졌습니다.

해법은 이제 코드 주석에 들어 있습니다. 누가 실수로 지우지 못하도록 말입니다.

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

자기가 감시하는 대상을 자기가 쓰러뜨리는 모니터링 도구는 특별한 종류의 창피이고, 도면 위에서는 결코 찾아내지 못했을 문제입니다.


랩은 사실 무엇을 위한 것인가

저는 랩을 “Osprey를 시연하는 곳”이라고 설명하곤 했습니다. 그것은 잘못된 직무 기술이었고, 그것을 바로잡자 만드는 방식이 달라졌습니다.

랩은 Osprey를 좋아 보이게 하려고 있는 것이 아닙니다. Osprey가 틀렸음을 증명하려고 있는 것입니다.

그 말은 랩이 저를 반박할 수 있는 만큼만 가치가 있다는 뜻입니다. 그래서 일부러 까다롭게 만들었습니다. 프로토콜을 하나도 공유하지 않는 통신사 세 곳. 세 개면 시연이 더 잘될 텐데도 열다섯 개인 OSPF 영역. level-1 영역이 열일곱 개인 IS-IS 도메인. 링크 상태 데이터베이스가 아예 없어서 OSPF 코드의 전제를 전부 깨뜨리는 EIGRP. 그리고 124개 전부에서 v4와 v6 코스트가 10배씩 차이 나는 듀얼 스택 링크.

그중 어느 것도 스크린샷을 더 예쁘게 만들지 않습니다. 그리고 전부가 무언가를 잡아냈습니다.

360개의 허구 통로는 꼼꼼히 읽어서 찾은 것도, 제가 미리 쓸 만큼 영리했던 테스트로 찾은 것도 아닙니다. 실제로 무슨 생각을 하는지 물어볼 수 있는 진짜 라우터 64대가 있었고, 표본으로 두세 대만 확인하고 믿는 대신 드디어 전부에게 한꺼번에 물었기 때문에 찾아낸 것입니다.

여기서 당연한 질문이 나오고, 그것이 다음 글의 주제입니다. 모델이 맞는지 알려 줄 수 있는 것이 네트워크뿐이라면, 그것을 어떻게 테스트 스위트로 바꿀 것인가?


다음 글: 네트워크 엔지니어의 단위 테스트.