Tech_News

BGP 경로 누출을 라우터가 스스로 막는다: RFC 9234 BGP Role과 OTC 실전 정리

TeEm0 2026. 8. 20. 09:00
728x90

Cloudflare가 2026년 8월에 올린 BGP Role model: tracking the adoption of RFC 9234를 읽고, 예전에 겪었던 장애 하나가 떠올랐다. 어느 날 갑자기 특정 대역으로 가는 트래픽 RTT가 두 배로 뛰고 일부는 드롭되는데, 우리 쪽 설정은 아무것도 안 바뀐 상황. 결국 원인은 우리 upstream의 어느 고객사가 다른 upstream으로 경로를 되던진 것, 그러니까 전형적인 route leak이었다. 우리가 할 수 있는 건 전화 돌리고 기다리는 것뿐이었다.

RFC 9234는 이 문제를 "운영자가 정책으로 막아라"에서 "프로토콜이 알아서 거른다"로 옮기려는 시도다. 그리고 Cloudflare 글의 핵심 발견은, 그 시도가 실제 인터넷에서 얼마나 굴러가고 있는지를 측정했더니 Tier 1 두 곳이 OTC 속성을 벗겨내고 있더라는 것이다. 이건 단순 뉴스가 아니라, 검증 로직을 짜는 사람 입장에서 꽤 골치 아픈 얘기다.

왜 route leak은 계속 반복되는가

BGP 라우팅은 AS 간의 관계(customer-provider, peer-peer)로 굴러간다. Customer는 돈을 내고 provider를 통해 인터넷 전체에 도달하고, peer끼리는 보통 정산 없이 트래픽을 교환한다. 여기서 나오는 규칙이 valley-free다.

  • Provider나 peer에게서 배운 경로는 아래(customer)로만 내보낸다.
  • 위(provider)나 옆(peer)으로는 자기가 originate한 것과 자기 customer에게서 배운 것만 보낸다.

이 규칙을 어기면 경로가 계곡(valley)을 만든다. 내려갔다가 다시 올라가는 모양이다. 원문에서 예로 든 hairpin turn이 대표적인데, 어떤 AS가 provider A에게서 받은 경로를 provider B에게 그대로 넘기는 경우다. 이게 왜 최악이냐면, 그 중간 AS는 두 upstream 사이 트래픽을 중계할 돈도 못 받고 회선 용량도 없다. 그래서 지연이 늘거나 그냥 버려진다.

국내 환경으로 옮기면 이해가 쉽다. 멀티 클라우드 구성하면서 두 개 이상의 ISP나 클라우드 DX/Interconnect를 물리고, 양쪽에서 받은 full route 혹은 부분 경로를 필터 없이 재광고하는 사고. AS를 직접 운영하는 기업이면 언젠가 한 번은 밟는 지뢰다.

기존 방어책은 전부 사람 손에 의존한다. prefix-list, IRR 기반 as-set 필터, max-prefix. 이걸 모든 세션마다, 관계가 바뀔 때마다 정확히 유지해야 한다. 사람이 하는 일이니 틀린다. 그래서 RFC 9234가 나왔다.

BGP Role과 OTC: 동작 원리

Role — 세션 수립 시점에 관계를 합의한다

eBGP 세션마다 "나는 너에 대해 무엇인가"를 선언한다. Role은 다섯 가지: Provider, Customer, Peer, RS, RS-Client. 유효한 조합은 아래 다섯 쌍뿐이다.

Local RoleRemote Role
ProviderCustomer
CustomerProvider
RSRS-Client
RS-ClientRS
PeerPeer

양쪽이 다 Role을 보냈는데 조합이 안 맞으면 세션이 Role Mismatch (NOTIFICATION code 2, subcode 11)로 거절된다. 이게 오히려 좋은 점이다. "우리가 서로 관계를 다르게 알고 있었다"는 사실이 장애로 터지기 전에 handshake 단계에서 드러나기 때문이다.

부분 배포도 고려돼 있다. 내가 Role을 보내고 상대가 안 보내면 세션은 그냥 올라오고, 내 로컬 Role만으로도 절반의 leak 방어는 동작한다. 상대가 Role capability를 안 보내면 세션을 거절하는 strict mode도 있지만 opt-in이고, 원문 표현대로 지금 채택률에서는 대부분의 네트워크에 현실적이지 않다.

중요한 예외 하나. 같은 세션에서 복수의 관계를 갖는 경우(complex relationship)—예를 들어 일부 prefix는 provider-customer, 일부는 peer-peer—RFC 9234는 그런 세션에 Role을 설정하면 안 된다고 못 박는다. 관계별로 eBGP 세션을 쪼개고 각각에 Role을 붙여야 한다. 세션을 못 쪼개면 결국 prefix 단위 정책으로 돌아가는데, 그건 원래 문제로 회귀하는 것이다.

OTC — 경로에 "여기가 정점"이라고 도장을 찍는다

OTC(Only to Customer)는 optional transitive path attribute, type code 35다. 값은 AS 번호 하나. 그 경로를 처음으로 옆이나 아래로 보낸 AS를 기록한다. 즉 경로의 정점 표시이고, 그 이후로는 아래로만 갈 수 있다.

규칙은 Role에 따라 갈린다.

OTC를 붙이는 시점

  • Customer / Peer / RS-Client에게 광고하는데 OTC가 없으면 → 내 ASN으로 OTC를 붙인다.
  • Provider / Peer / RS에게서 받았는데 OTC가 없으면 → 상대 ASN으로 OTC를 붙인다.

OTC를 검사하는 시점

  • OTC 달린 경로는 Provider / Peer / RS에게 절대 광고하지 않는다.
  • Customer / RS-Client에게서 OTC 달린 경로가 오면 → leak, 거절.
  • Peer에게서 왔는데 OTC 값이 그 peer의 ASN이 아니면 → leak, 거절.

앞서의 hairpin에 대입해보자. AS64502가 peer AS64503에게 광고하면서 OTC=64502를 찍는다. AS64503은 자기 customer AS64504로 내려보내되 OTC는 그대로 둔다. AS64504가 실수로 다른 provider에게 이걸 광고하려 하면, 기회가 두 번 있다. AS64504가 RFC 9234를 지키면 애초에 provider로 안 나간다. 안 지켜서 나가더라도, 받는 provider 입장에서는 "customer에게서 OTC 달린 경로가 왔다" → leak 판정. 둘 중 하나만 동작해도 막힌다.

한 번 설정된 OTC는 바뀌지 않고 보존되어야 한다. optional transitive니까 RFC 9234를 모르는 라우터도 그냥 통과시키는 게 정상 동작이다. 이 두 가지 전제가 다음 섹션에서 깨진다.

실측: 누가 쓰고 있고, Tier 1은 무슨 짓을 하고 있나

Cloudflare가 채택률을 측정하려니 근본적인 어려움이 있었다. OTC는 보내는 쪽도 받는 쪽도 붙일 수 있다. 경로 64506 64507에 OTC=64507이 달려 있을 때, AS64507이 egress에서 찍은 건지 AS64506이 ingress에서 채운 건지 구분이 안 된다. 이 double-sided stamping은 최대한 많은 경로에 태그를 붙인다는 면에서는 좋지만, "누가 도입했나"를 세는 데는 방해가 된다.

그래서 RouteViews와 RIPE RIS의 RIB 덤프로 먼저 접근했다. 서로 다른 OTC 값을 단순히 세면 361개 AS가 나오지만, 이건 상대의 값을 채워준 케이스까지 포함된 부풀려진 수치다. AS_PATH의 첫 AS와 OTC 값이 같은 경우만 추리면(즉 collector를 향해 직접 OTC를 설정한 AS) 9개로 줄어든다. 공개 collector만으로는 여기가 한계라, Cloudflare는 자사의 글로벌 피어링 접점에서 어떤 peer AS가 OTC를 보내오는지 직접 관측하는 방식을 별도로 만들었다.

그리고 예상 못 한 게 나왔다. 대형 Tier 1 네트워크 두 곳이 자기가 전달하는 경로에서 OTC 속성을 제거하고 있었다. Cloudflare는 이 사업자들과 OTC 전파를 허용하도록 협의 중이라고 밝혔다.

이게 왜 심각한지 실무 관점에서 짚어보자.

  • 보호가 조용히 사라진다. OTC 도장이 벗겨지면 그 아래에서는 "정점 표시가 없는 평범한 경로"가 된다. leak을 잡아야 할 라우터가 잡을 근거를 잃는다. 에러도 로그도 안 남는다.
  • 디버깅이 지옥이 된다. 우리 쪽에서는 OTC를 분명히 붙여 보냈는데, 몇 홉 건너 관측점에서는 안 보인다. 내 설정 문제인지, 중간 경로 문제인지 구분하려면 여러 vantage point에서 봐야 한다.
  • 채택률 통계가 왜곡된다. "우리 도입했는데 왜 통계에 안 잡히지?"의 원인이 될 수 있다.

여기서 얻을 실무 교훈은 명확하다. Role/OTC를 켰다면 반드시 외부 관측으로 검증하라. 로컬 설정이 맞다고 인터넷 전역에서 그게 살아 있다는 보장이 없다.

검증: 실제로 OTC가 붙어 있는지 확인하기

RIPE RIS Live나 RouteViews 데이터를 직접 파싱해도 되지만, 빠르게 확인하려면 RIPE stat / RIS whois 쪽이 편하다. 아래는 bgpdump 계열로 로컬 RIB를 뜯어볼 때의 감을 잡는 예시다(속성 표시 형식은 도구·버전에 따라 다르므로 실제 출력은 환경에서 확인 필요).

# RouteViews RIB 덤프에서 OTC(attribute type 35) 달린 엔트리만 훑기
$ bgpdump -m rib.20260818.0000.bz2 2>/dev/null | head -3
TABLE_DUMP2|1755475200|B|203.0.113.1|64500|192.0.2.0/24|64500 64502 64507|IGP|...

# Junos에서 특정 prefix의 path attribute 전체 확인
user@rtr> show route 192.0.2.0/24 detail

inet.0: 950123 destinations, 1900456 routes
192.0.2.0/24 (2 entries, 1 announced)
        *BGP    Preference: 170/-101
                AS path: 64502 64507 I
                Only to Customer: 64502
                Localpref: 100
                Router ID: 198.51.100.7

핵심은 Only to Customer: 라인이 살아 있느냐다. 우리가 붙여 보낸 경로를 다른 대륙의 collector에서 조회했을 때 이 라인이 사라졌다면, 중간 어딘가에서 스트리핑이 일어난 것으로 봐야 한다.

설정과 흔한 함정

구현체별 설정 (핵심만)

벤더별로 지원 여부와 최소 버전이 다르다. 아래는 문법 감을 잡기 위한 것이고, 실제 적용 전에 해당 벤더 릴리스 노트에서 RFC 9234 지원 여부를 반드시 확인해야 한다.

! IOS-XR 계열: transit provider와의 세션 (내가 customer)
router bgp 64500
 neighbor 198.51.100.1
  remote-as 64501
  bgp role customer
  address-family ipv4 unicast
  !
 !
!
# Junos 계열: IX peer와의 세션
protocols {
    bgp {
        group IX-PEERS {
            type external;
            peer-as 64503;
            local-role peer;      /* strict 옵션은 상대도 Role을 보낼 때만 */
            neighbor 203.0.113.20;
        }
    }
}
# FRR: route server client로 붙는 IX 세션
router bgp 64500
 neighbor 203.0.113.1 remote-as 64510
 neighbor 203.0.113.1 local-role rs-client
 !
 ! 상대가 Role을 안 보내면 세션을 거절하려면(신중히):
 ! neighbor 203.0.113.1 local-role rs-client strict
# BIRD 2.x: provider 쪽 세션 (상대가 내 고객)
protocol bgp customer_a {
    local as 64500;
    neighbor 198.51.100.50 as 64520;
    local role provider;
    ipv4 { import filter { ... }; export all; };
}

흔한 함정

1) Role mismatch로 세션이 안 올라온다

제일 먼저 만나는 벽이다. 나는 peer라고 생각했는데 상대는 나를 customer로 알고 있었던 경우. 로그는 대략 이런 모양이다.

%BGP-3-NOTIFICATION: sent to neighbor 203.0.113.20 active 2/11 (Role Mismatch) 0 bytes

bgpd[2314]: %NOTIFICATION: sent to neighbor 203.0.113.20 2/11 (OPEN Message Error/Role Mismatch)
bgpd[2314]: neighbor 203.0.113.20: BGP Role mismatch, local role=peer, remote role=provider

당황해서 Role을 지우고 넘어가기 쉬운데, 이건 버그가 아니라 기능이다. 계약서와 실제 세션 설정이 어긋나 있다는 신호다. 상대 NOC에 관계를 확인하고, 진짜로 복합 관계라면 세션을 분리하거나 Role을 아예 빼는 게 맞다.

2) 유지보수 창구 없이 프로덕션 세션에 바로 켜기

Role capability는 OPEN 메시지에 실린다. 즉 기존 세션에 Role을 추가하면 세션이 재수립된다. 그리고 상대 설정이 어긋나 있으면 그대로 안 올라온다. Full route를 받는 transit 세션에서 이걸 낮에 하면 그 자체가 장애다. 반드시 작업 시간에, 한 세션씩, 롤백 준비하고 들어가야 한다.

3) strict mode 성급하게 켜기

Cloudflare 측정에서 collector를 향해 직접 OTC를 설정하는 AS가 9개 수준이라는 숫자가 말해주듯, 채택률은 아직 낮다. strict를 켜면 상대가 Role capability를 안 보내는 순간 세션이 거절된다. 지금 시점에서 IX peer 전체에 strict를 거는 건 자해에 가깝다.

4) 기존 필터를 걷어내기

가장 위험한 함정. Role/OTC를 켰다고 prefix-list나 IRR 기반 필터를 제거하면 안 된다. 위에서 본 것처럼 Tier 1이 OTC를 벗겨내는 경우가 실제로 있고, 상대가 RFC 9234를 아예 구현하지 않은 세션도 여전히 다수다. Role/OTC는 기존 방어를 대체하는 게 아니라 한 겹 더 얹는 것이다.

5) 복합 관계 세션에 Role을 얹기

같은 BGP 세션에서 어떤 prefix는 transit으로, 어떤 prefix는 peering으로 주고받는 구성이 국내 사업자 연동에서도 종종 보인다. RFC 9234는 이런 세션에 Role을 설정하지 말라고 명시한다. 억지로 하나를 고르면 정상 경로가 leak으로 걸려서 사라진다. 조용히 사라지기 때문에 원인 찾기가 특히 어렵다.

다층 방어에서의 위치

Role/OTC 하나만으로 라우팅 보안이 끝나지 않는다. 성격이 다른 방어들이라 조합해야 한다.

  • RPKI ROV: "이 prefix를 이 AS가 originate할 자격이 있나". origin hijack을 막는다. leak은 못 막는다.
  • ASPA: AS_PATH의 상하 관계가 말이 되는지 검증. 원문에 따르면 provider에게서 받은 경로와 그 외에서 받은 경로에 다른 알고리즘을 적용하는데, 어느 쪽인지 판단하는 근거가 바로 BGP Role이다. 그래서 둘 다 지원하는 라우터라면 Role과 ASPA는 같이 설정해야 한다.
  • RFC 9234 Role/OTC: 관계 위반(valley) 자체를 in-band로 차단.
  • MANRS: 위 항목들 + 필터링 + 주소 검증 + 연락처 최신화를 묶은 운영 기준선.

순서를 정하자면 RPKI ROV(origin 검증) → 정확한 prefix 필터 → Role/OTC → ASPA 순으로 얹는 게 현실적이다. 앞의 두 개 없이 Role만 켜는 건 순서가 뒤집힌 것이다.

정리

한 줄 요약: RFC 9234는 "이 경로는 여기가 정점"이라는 표시(OTC)와 "우리 관계는 이거다"라는 합의(Role)를 BGP 안에 넣어서, 사람이 쓴 정책 없이도 라우터가 route leak을 거절하게 만든다.

누가 언제 써야 하나.

  • ASN을 직접 운영하며 transit 2개 이상 또는 IX 피어링을 하는 조직 — 지금 검토할 가치가 충분하다. 특히 자기가 leak의 진원지가 되는 사고(hairpin)를 구조적으로 막을 수 있다.
  • IX route server 운영자 / 중견 ISP — RS·RS-Client Role은 효과가 큰 편이다. 다만 클라이언트 대상 사전 공지가 필수다.
  • 클라우드 DX/Interconnect만 쓰는 일반 기업 — 대개 상대(클라우드 사업자) 지원 여부에 달려 있다. 우선순위는 RPKI ROA 정확히 등록하기가 먼저다.
  • strict mode — 아직은 아니다. 채택률이 올라온 뒤에 세션 단위로 선별 적용.

그리고 이번 글의 진짜 교훈. 표준이 "보존해야 한다"고 써 있어도 인터넷은 그대로 안 굴러간다. Tier 1 두 곳이 OTC를 벗겨내고 있었다는 발견이 딱 그 얘기다. 켰으면 외부에서 관측해서 확인하고, 안 보이면 그때부터 추적을 시작해야 한다. 로컬 config만 믿으면 안 된다.

참고 자료

728x90