← 전체 글

되돌릴 수 없는 LSA

라우팅 프로토콜에 합류하는 것은 악수다. 떠나는 쪽은 끝내 보내지 못할 수도 있는 메시지 한 통으로 끝난다.

Michel Wijnberg

앞 글은 링크 스테이트 프로토콜에 그것을 지나는 경로가 되지 않으면서 합류하는 이야기였습니다. 이 글은 반대쪽 끝, 더 어렵고 제가 얕잡아 보았던 쪽 이야기입니다.

도착은 악수입니다. 양쪽이 협상하고, 양쪽이 확인하며, 패킷이 유실되면 다시 시도합니다. OSPF와 IS-IS의 모든 상태 기계는 도착을 신뢰할 수 있게 만들려고 존재합니다.

떠남에는 상태 기계가 없습니다. 라우터들이 무엇을 말했는지 기억하는 것이 존재 이유의 전부인 프로토콜을 향해 자신의 퇴장을 한 번 알리고 나면, 당신은 사라졌고 아무것도 바로잡을 수 없습니다.


네트워크는 당신을 붙들고 있다

아무에게도 알리지 않고 레코더를 멈춰 보십시오. 극적인 일은 아무것도 일어나지 않습니다. 그게 문제입니다.

당신의 프로세스가 사라져도 당신의 LSA는 사라지지 않습니다. 그것은 모든 라우터의 데이터베이스에 시퀀스 번호와 나이를 달고 앉아 있고, 나이가 최댓값에 이를 때까지 거기 있을 것입니다. OSPF에서는 한 시간입니다. 그 한 시간 동안 토폴로지에는 더 이상 존재하지 않는 라우터가 들어 있고, 당신이 어디로 갔는지 조용히 궁금해하는 이웃을 향한 인접을 광고하고 있습니다.

모니터링 도구 하나가 종료했다는 이유로, 네트워크가 한 시간 동안 사실이 아닌 것을 기술하는 것입니다.

그러니 제대로 작별해야 하고, 두 프로토콜 모두 같은 발상으로 귀결되는 방법을 갖고 있습니다. 없애고 싶은 것을 이것을 삭제하라는 뜻이 되는 형태로 다시 기점하는 것입니다.

  • OSPF는 LSA의 나이를 MaxAge, 즉 3600초로 설정해 플러딩하고, 이를 받은 모든 라우터가 자기 데이터베이스에서 그것을 씻어 내게 합니다(RFC 2328 §13.4).
  • IS-IS는 LSP를 잔여 수명 0으로 플러딩합니다. 같은 발상, 다른 필드입니다.

둘 다 한 통입니다. 확인 응답이 오가는 해체 절차도, “정말입니까”도, 재시도 루프도 없습니다. 그게 중요해질 무렵이면 프로세스는 이미 종료 중이기 때문입니다.


작별 속의 함정

여기가 틀리기 쉬운 대목이고, 인증된 IS-IS를 돌려 보지 않았다면 찾지 못했을 부분입니다.

퍼지 LSP는 아무것도 나르지 않습니다. 도달성도, 호스트명도, 능력 정보도 없습니다. 그 내용 전부가 “수명 0”이라고 말하는 자기 자신의 헤더입니다. 그래서 보호할 것이 아무것도 없는 메시지처럼 보입니다.

그래도 인증되어야 합니다. RFC 5304는 암호 인증을 쓰는 구현이 인증되지 않은 퍼지를 거부할 것을 요구하고, 퍼지가 인증 TLV 외의 어떤 TLV도 나르지 않을 것을 요구합니다. 인증되지 않은 퍼지가 무엇인지 물어보면 두 요구가 모두 이해됩니다. 그것은 다른 라우터의 상태를 삭제하는 서명 없는 패킷 한 통이며, 종료의 모양이 아니라 공격의 모양입니다.

즉 종료하면서 보내는 그 한 통, 재시도할 수 없는 그 한 통, 다른 내용이라고는 전혀 없는 그 한 통이야말로, 그럼에도 반드시 서명되어야 하는 메시지입니다. 이걸 틀리면 실패는 최악의 방식으로 조용합니다. 당신 쪽에서 종료는 깔끔해 보이고, 퍼지는 나가고, 인증하는 라우터들은 그것을 버리고, 당신의 유령은 수명이 다할 때까지 데이터베이스에 남습니다.

그리고 코드가 만들어 내는 모양이 정확히 그것입니다. 퍼지 LSP는 TLV를 하나도 싣지 않은 채 구성되고, 서명이 설정되어 있으면 마셜러가 인증 TLV를 덧붙입니다. 인증된 네트워크에서 그 메시지의 내용 전부는 자기 자신의 서명입니다. 그것은 최적화가 아닙니다. 떠나는 것과 떠난 것처럼 보이는 것 사이의 차이입니다.


보낼 기회가 주어지지 않는 작별

메커니즘에 앞서 그 밑의 가정을 짚겠습니다. 여기서 거꾸로 설계하기가 쉽기 때문입니다.

위의 모든 것은 정상 종료를 그립니다. 프로세스는 자기가 멈추는 중임을 알고 있고, 한 통 더 보낼 시간이 있습니다. 그건 쉬운 경우이고, 설계 기준으로 삼을 경우가 아닙니다.

설계 기준으로 삼아야 할 경우는 메시지가 아예 없는 쪽입니다. 기다려 주지 않는 kill 시그널. 전원을 잃는 호스트. 메모리 때문에 거둬지는 컨테이너. 그 모든 경우에 퍼지는 결코 보내지지 않고, 네트워크는 이미 존재하기를 그만둔 라우터를 붙든 채, 무슨 일이 있었다는 통지 하나 없이 남겨집니다.

그러므로 퍼지는 최선 노력이고, 설계가 거기에 기댈 수는 없습니다. 반드시 옳아야 하는 것은 그것이 결코 일어나지 않았다고 가정하는 경로입니다. 다시 올라와서, 자기 유령이 아직 설치되어 있는 것을 발견하고, 그것을 덮어쓰는 것. 그 경로가 작동한다면 정상 퍼지는 장애 창을 한 시간에서 1초로 줄이는 최적화입니다. 작동하지 않는다면 정상 퍼지는 정작 필요했던 순간에 실패하는 위안일 뿐입니다.

퍼지는 수렴을 개선합니다. 그것이 정확성 메커니즘일 수는 없습니다.

그래서 이 글의 나머지는 멈추는 것이 아니라 재시작하는 것에 관한 이야기입니다.


돌아와 보니 이미 거기 있는 자기 자신

문제의 나머지 절반은 재시작이고, 진짜 버그를 만들어 내는 쪽이 이쪽입니다.

깔끔하지 못하게 멈춘 레코더, 또는 네트워크가 잊는 것보다 빨리 재시작한 레코더는, 이전 실행의 LSA가 시퀀스 번호를 단 채 사방에 아직 설치되어 있는 상태로 돌아옵니다. 믿음을 얻으려면 새 기점이 그것을 이겨야 합니다.

한 줄짜리 비교처럼 들립니다. 아닙니다. 풀어 쓸 가치가 있는 이유가 있습니다.

OSPF 시퀀스 번호는 부호 있는 32비트입니다. 초깃값 0x80000001은 표현 가능한 가장 음수인 값이고, 거기서부터 올라갑니다. 그러므로 “이전 시퀀스 번호를 본 적 있는가”를 0을 값 없음 센티널로 두어 구현하는 자연스러운 방식은, 테스트를 통과해 살아남는 방식으로 틀립니다. 0은 결코 유효한 OSPF 시퀀스 번호가 아니지만, int32(0)은 유효한 모든 값보다 큽니다. 낡은 LSA는 “그런 건 없었다”를 뜻해야 했던 센티널과의 비교에서 언제나 지게 됩니다. 코드는 대신 명시적인 불 값을 들고 다니고, 뻔해 보이는 판본이 왜 함정인지 설명하는 주석을 답니다.

그다음은 언제 할 것인가이고, 뻔한 답 두 개가 다 틀립니다.

첫 번째 본능은 유령을 보는 즉시, 데이터베이스 교환 도중에 MaxAge 플러딩으로 씻어 내는 것입니다. Cisco 구현이 여기에 나쁘게 반응하는 것을 관찰했습니다. 인접이 아직 Loading인 동안 도착한 MaxAge 플러시가 플러딩을 대략 40초 주기의 리셋 사이클로 몰아넣었습니다. 제가 가리킬 수 있는 누군가의 사양 위반은 아닙니다. 그저 그 라우터들이 그렇게 했을 뿐입니다.

그것을 배운 뒤의 두 번째 본능은 기다리는 것입니다. 낡은 시퀀스 번호를 기록해 두었다가 인접이 Full에 도달하면 다시 기점하는 것. 이건 교착합니다. 이웃은 여전히 유령을 광고하고 있으니 그것이 우리 요청 목록에 남고, 목록은 완전히 비워지지 않고, Full에는 결코 도달하지 못하고, 그것을 해소했을 기점은 결코 일어나지 않습니다. 회복은 그때부터 약 30분 뒤의 갱신 타이머를 기다립니다.

답은 둘 다 아니며, 놓치기 쉬운 구분에 달려 있습니다. Loading 도중에 위험한 것은 MaxAge 플러시이지 기점이 아닙니다. 그래서 레코더는 Loading 도중에 즉시 다시 기점합니다. 자기 사본과 유령 양쪽보다 높은 시퀀스 번호를 실은 평범한 LSA로서, 그리고 지나가는 김에 이웃의 요청 목록에서 낡은 항목을 떨궈 냅니다. 평범한 LSA는 거기서 안전합니다. 플러시는 아닙니다.

이 중 어느 것도 사양에 없습니다. 네트워크가 두 번, 서로 다른 두 방식으로 실제로 한 일입니다.

Age, Seq#, Cksum 열을 보여 주는 LSDB 브라우저. 라우터 세 대는 실제 값을 갖고 있고(경과 시간 1492, 978, 572초, 시퀀스 번호는 0x8000057x 부근), 두 대는 대시로 표시된다. 그 두 대는 SNMP 재구성만 존재하며, 표 위의 머리글이 그렇게 밝히고 있다
Age, Seq#, Cksum 열을 보여 주는 LSDB 브라우저. 라우터 세 대는 실제 값을 갖고 있고(경과 시간 1492, 978, 572초, 시퀀스 번호는 0x8000057x 부근), 두 대는 대시로 표시된다. 그 두 대는 SNMP 재구성만 존재하며, 표 위의 머리글이 그렇게 밝히고 있다

이 글이 다루는 두 열이 바로 LSA를 되돌리기 어렵게 만드는 그 두 가지입니다. 누군가 버리기까지 3600에 도달해야 하는 경과 시간과, 넘어서야 하는 시퀀스 번호입니다. 레코더가 인접 관계를 쥐고 있는 곳에서는 둘 다 실제 값입니다. SNMP 워크만이 LSA를 재구성한 곳에서는, 브라우저는 그럴듯한 0 대신 대시를 찍습니다.


인접 하나를 삼켜 버린 5초 규칙

그리고 흥미로운 실패. 제가 가장 좋아하는 종류, 즉 옳은 규칙 둘이 합쳐져 그른 결과가 되는 경우입니다.

RFC 2328 §12.4는 같은 LSA의 두 기점 사이에 최소 5초를 요구합니다. 합리적인 속도 제한이고 Osprey는 그것을 지킵니다.

이와 별개로, 인접이 Full에 도달하기 전에 보내는 대체 LSA는 의도적으로 링크를 싣지 않습니다. 아직 형성을 마치지 못한 인접을 광고하는 것은 아직 존재하지 않는 이웃 관계에 대한 주장이 되기 때문입니다.

이제 이 둘을 같은 1초 안에 놓아 보십시오. 레코더는 Full이 아니므로 링크 없이 유령을 덮어씁니다. 바로 그 직후 인접이 Full에 도달하고, 레코더는 자신의 진짜 LSA, 즉 링크가 들어 있는 것을 기점하려 합니다. 그 기점은 5초 창 안에 떨어지고 속도 제한기에 버려집니다.

오류가 나지 않습니다. 재시도도 없습니다. 레코더는 이제 정확하고 준수하는 방식으로, 링크가 없는 라우터 하나를 광고하고 있으며, 주기적 갱신이 돌아올 때까지 계속 그것을 광고할 것입니다.

그 갱신은 30분입니다.

2026-08-05 랩 배포에서 두 사건이 같은 1초에 떨어졌을 때 측정했습니다. 레코더 자신의 인접이 30분 동안 down으로 표시되었습니다. 인접이 올라와 있는지를 말하는 것이 일의 전부인 제품에서 말입니다. 그리고 정직한 부분은, 모든 구성 요소가 설계대로 동작했다는 것입니다. 속도 제한기는 RFC를 따랐습니다. Full 이전 LSA는 링크를 주장하지 않은 것이 옳았습니다. 버그는 그 둘 사이의 틈에 있었고, 이 부류의 버그는 언제나 거기 있습니다.

수정은 합쳐진 일회성 재시도입니다. 속도 제한기에 거절당한 기점은 버려지지 않고, 창이 열리는 시점을 향해 다시 장전됩니다.


떠나는 쪽이 더 어려운 절반인 이유

도착은 피드백이 있는 협상입니다. Hello가 틀렸다면 인접은 형성되지 않고, 당신은 즉시, 요란하게, 앞으로 나아가지 않는 상태 기계 안에서 그것을 알게 됩니다.

떠남은 곧 존재하기를 그만둘 프로세스가 보내는, 확인 응답 없는 메시지 한 통입니다. 첫 시도에서 올바르게 구성되고 올바르게 서명되어야 하며, 그 실패는 당신이 서 있는 자리에서 보면 성공과 정확히 똑같아 보입니다.

그 비대칭이 이 코드에서 종료 경로가 시작 경로보다 주석이 많은 이유이고, 내용이 없는 그 한 통에 가장 엄격한 요건이 붙어 있는 이유입니다.


요점

깔끔하게 떠날 줄 모르는 모니터링 시스템은 수동적이지 않습니다. 멈추기 전까지만 수동적이고, 그 다음에는 남의 토폴로지 데이터베이스 속 한 시간짜리 허구입니다.

어려움의 대부분은 사양에 있지 않습니다. 규칙과 규칙의 이음매에 있습니다. 옳은 속도 제한, 옳은 Full 이전 LSA, 명백히 맞아 보이면서 조용히 틀린 센티널 값. 어느 것도 프로토콜을 오해한 것이 아닙니다. 전부 같은 모양, 즉 옳은 것 둘이 만나는 모양입니다.


다음: 합류할 데이터베이스 자체가 없는 프로토콜, 그리고 정중히 물어도 MIB가 말해 주지 않는 것. 데이터베이스가 없는 프로토콜.