라우팅 (Routing)

핵심 인사이트 (3줄 요약)

출발지에서 목적지까지 패킷의 최적 경로를 결정하고 전달하는 과정. 정적(수동)과 동적(프로토콜 기반)으로 구분. 거리벡터·링크상태·경로벡터 알고리즘이 핵심이다.


Ⅰ. 개요 (필수: 200자 이상)

개념: 라우팅(Routing)은 네트워크에서 패킷이 출발지에서 목적지까지 도달하기 위한 최적 경로를 결정하고, 라우팅 테이블을 기반으로 해당 경로로 패킷을 전달하는 Layer 3(네트워크 계층)의 핵심 기능이다.

💡 비유: 라우팅은 "네비게이션 시스템" 같아요. 출발지에서 목적지까지 가장 빠르거나 효율적인 경로를 찾아주죠. 도로 상황(네트워크 상태)에 따라 우회 경로를 추천하기도 하고, 고속도로(고대역폭 링크)를 우선 선택하기도 해요!

등장 배경 (필수: 3가지 이상 기술):

  1. 기존 문제점 - 네트워크 확장성: 단일 브로드캐스트 도메인은 호스트 수 증가 시 충돌·브로드캐스트 스톰으로 마비
  2. 기술적 필요성 - 경로 최적화: 복잡한 인터네트워크에서 최적 경로 산출 알고리즘 필요
  3. 시장/산업 요구 - 인터넷 연결: 수십억 개의 네트워크를 상호 연결하는 글로벌 인프라 구축

핵심 목적: 네트워크 간 연결성 제공, 최적 경로 선택, 트래픽 효율화, 장애 시 우회


Ⅱ. 구성 요소 및 핵심 원리 (필수: 가장 상세하게)

라우팅 테이블 구조 (필수: ASCII 아트):

┌─────────────────────────────────────────────────────────────────────────┐
│                        라우팅 테이블 (Routing Table)                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ┌───────────────────────────────────────────────────────────────────┐ │
│   │ 목적지 네트워크 │  서브넷마스크  │  다음 홉(NH)  │ 인터페이스 │ AD │ │
│   ├───────────────────────────────────────────────────────────────────┤ │
│   │  10.0.1.0      │ 255.255.255.0  │  직접 연결    │   eth0     │ 0  │ │
│   │  10.0.2.0      │ 255.255.255.0  │  10.0.1.1     │   eth0     │ 1  │ │
│   │  192.168.1.0   │ 255.255.255.0  │  10.0.1.254   │   eth0     │110 │ │
│   │  0.0.0.0       │ 0.0.0.0        │  10.0.1.254   │   eth0     │ 1  │ │
│   └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│   용어 설명:                                                            │
│   - 목적지 네트워크: 패킷이 향할 최종 네트워크 주소                       │
│   - 서브넷 마스크: 목적지 네트워크 식별용 비트 마스크                     │
│   - 다음 홉(Next Hop): 패킷이 전달될 다음 라우터의 IP                    │
│   - 인터페이스: 패킷이 나갈 로컬 포트                                    │
│   - AD(Administrative Distance): 경로 신뢰도 (낮을수록 우선)             │
│                                                                         │
│   AD 값 예시:                                                           │
│   - 직접 연결: 0                                                        │
│   - 정적 라우팅: 1                                                      │
│   - eBGP: 20                                                            │
│   - OSPF: 110                                                           │
│   - RIP: 120                                                            │
│   - iBGP: 200                                                           │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

라우팅 분류 (필수: 표):

분류정적 라우팅 (Static)동적 라우팅 (Dynamic)
설정 방식관리자 수동 입력프로토콜 자동 계산
경로 변경수동 (재설정 필요)자동 (장애 감지 시)
리소스 사용CPU/메모리 거의 없음CPU/메모리/대역폭 사용
확장성소규모에 적합대규모에 적합
복잡도단순복잡 (프로토콜 이해 필요)
예측성높음 (고정 경로)낮음 (상황에 따라 변화)
사용 사례스텁 네트워크, 기본 경로엔터프라이즈, 인터넷

라우팅 알고리즘 비교 (필수: 상세):

┌─────────────────────────────────────────────────────────────────────────┐
│                    라우팅 알고리즘 분류                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. 거리 벡터 (Distance Vector)                                         │
│     ┌───────────────────────────────────────────────────────────────┐  │
│     │ 핵심 개념: 인접 라우터와 거리 정보 교환                         │  │
│     │ 알고리즘: 벨만-포드 (Bellman-Ford)                             │  │
│     │ 메트릭: 홉 수 (Hop Count)                                      │  │
│     │ 프로토콜: RIP, IGRP                                            │  │
│     │                                                                 │  │
│     │ 장점: 구현 단순, 메모리 적음                                    │  │
│     │ 단점: 느린 수렴, 루프 가능성 (Count-to-Infinity)               │  │
│     │                                                                 │  │
│     │ 동작 예시:                                                      │  │
│     │   A ──1── B ──1── C                                            │  │
│     │                                                                 │  │
│     │   A의 거리 벡터: {B:1, C:2}                                    │  │
│     │   B의 거리 벡터: {A:1, C:1}                                    │  │
│     │   C의 거리 벡터: {A:2, B:1}                                    │  │
│     └───────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  2. 링크 상태 (Link State)                                              │
│     ┌───────────────────────────────────────────────────────────────┐  │
│     │ 핵심 개념: 전체 네트워크 토폴로지 파악 후 SPF 계산              │  │
│     │ 알고리즘: 다익스트라 (Dijkstra's Shortest Path First)          │  │
│     │ 메트릭: 비용 (Cost, 대역폭 기반)                               │  │
│     │ 프로토콜: OSPF, IS-IS                                          │  │
│     │                                                                 │  │
│     │ 장점: 빠른 수렴, 루프 방지, 계층적 확장                         │  │
│     │ 단점: 메모리/CPU 사용 많음, 구현 복잡                          │  │
│     │                                                                 │  │
│     │ 동작 과정:                                                      │  │
│     │   ① Hello 패킷으로 인접 라우터 발견                            │  │
│     │   ② LSA(Link State Advertisement) 전파                         │  │
│     │   ③ 전체 토폴로지 맵 구성 (LSDB)                               │  │
│     │   ④ 다익스트라로 SPF 트리 계산                                 │  │
│     │   ⑤ 라우팅 테이블 생성                                         │  │
│     └───────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  3. 경로 벡터 (Path Vector)                                             │
│     ┌───────────────────────────────────────────────────────────────┐  │
│     │ 핵심 개념: AS 경로 정보 포함으로 루프 방지                      │  │
│     │ 프로토콜: BGP (Border Gateway Protocol)                        │  │
│     │ 메트릭: AS-PATH 길이, 정책                                     │  │
│     │                                                                 │  │
│     │ 장점: 대규모 네트워크 확장, 정책 기반 라우팅                    │  │
│     │ 단점: 느린 수렴, 설정 복잡                                      │  │
│     │                                                                 │  │
│     │ BGP 경로 선택 순서:                                             │  │
│     │   1. Highest Weight (Cisco 전용)                               │  │
│     │   2. Highest Local Preference                                  │  │
│     │   3. Shortest AS-PATH                                          │  │
│     │   4. Lowest Origin Type                                        │  │
│     │   5. Lowest MED                                                │  │
│     │   6. eBGP > iBGP                                               │  │
│     │   7. Lowest IGP cost to BGP next-hop                          │  │
│     └───────────────────────────────────────────────────────────────┘  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

주요 라우팅 프로토콜 구조 (필수: ASCII 아트):

┌─────────────────────────────────────────────────────────────────────────┐
│                        OSPF 계층 구조                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ┌─────────────────────────────────────────────────────────────────┐  │
│   │                     AS (Autonomous System)                       │  │
│   │  ┌───────────────────────────────────────────────────────────┐  │  │
│   │  │                    Area 0 (Backbone)                       │  │  │
│   │  │         ┌─────────────────────────────────────┐           │  │  │
│   │  │         │           Backbone 라우터            │           │  │  │
│   │  │         │   ┌─────┐         ┌─────┐          │           │  │  │
│   │  │         │   │ ABR │─────────│ ABR │          │           │  │  │
│   │  │         │   └──┬──┘         └──┬──┘          │           │  │  │
│   │  │         └──────┼───────────────┼─────────────┘           │  │  │
│   │  └────────────────┼───────────────┼─────────────────────────┘  │  │
│   │                   │               │                             │  │
│   │  ┌────────────────┴────┐    ┌────┴────────────────────────┐   │  │
│   │  │      Area 1          │    │         Area 2              │   │  │
│   │  │  ┌─────┐   ┌─────┐   │    │   ┌─────┐   ┌─────┐        │   │  │
│   │  │  │내부 │───│내부 │   │    │   │내부 │───│내부 │        │   │  │
│   │  │  │라우터│   │라우터│   │    │   │라우터│   │라우터│        │   │  │
│   │  │  └─────┘   └─────┘   │    │   └─────┘   └─────┘        │   │  │
│   │  └──────────────────────┘    └─────────────────────────────┘   │  │
│   │                                                                  │  │
│   │  라우터 유형:                                                    │  │
│   │  - Internal Router: 단일 Area 내부                               │  │
│   │  - ABR (Area Border Router): Area 간 연결                        │  │
│   │  - ASBR (AS Boundary Router): 외부 AS 연결                       │  │
│   │  - Backbone Router: Area 0 내부                                  │  │
│   └─────────────────────────────────────────────────────────────────┘  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│                        BGP 인터넷 구조                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│                     ┌─────────────────────┐                            │
│                     │      Tier 1 ISP     │                            │
│                     │    (AT&T, Verizon)  │                            │
│                     └──────────┬──────────┘                            │
│                    ┌───────────┼───────────┐                           │
│                    ▼           ▼           ▼                           │
│           ┌────────────┐ ┌────────────┐ ┌────────────┐                │
│           │ Tier 2 ISP │ │ Tier 2 ISP │ │ Tier 2 ISP │                │
│           └─────┬──────┘ └─────┬──────┘ └─────┬──────┘                │
│                 │              │              │                         │
│        ┌────────┴───┐   ┌──────┴────┐   ┌────┴────────┐               │
│        ▼            ▼   ▼           ▼   ▼             ▼               │
│   ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐        │
│   │기업망 AS│ │클라우드 │ │기업망 AS│ │대학 AS  │ │CDN AS   │        │
│   │ (AS1)   │ │(AS2)    │ │(AS3)    │ │(AS4)    │ │(AS5)    │        │
│   └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘        │
│                                                                         │
│   BGP 용어:                                                             │
│   - eBGP: 서로 다른 AS 간 BGP 세션                                      │
│   - iBGP: 동일 AS 내부 BGP 세션                                         │
│   - AS-PATH: 패킷가 거친 AS 번호 목록                                   │
│   - Peering: ISP 간 상호 연결                                           │
│   - Transit: 인터넷 연결성 제공 서비스                                   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

핵심 알고리즘/공식 (해당 시 필수):

[다익스트라 알고리즘 (OSPF SPF)]

1. 초기화:
   - 시작 노드 거리 = 0, 다른 모든 노드 = ∞
   - 방문하지 않은 모든 노드를 집합 Q에 추가

2. 반복:
   - Q에서 거리가 가장 작은 노드 u 선택
   - u를 Q에서 제거
   - u의 모든 인접 노드 v에 대해:
     if d[u] + cost(u,v) < d[v]:
         d[v] = d[u] + cost(u,v)
         prev[v] = u

3. 종료:
   - Q가 비면 종료
   - prev[] 배열로 최단 경로 역추적

시간 복잡도: O((V+E) log V) with priority queue

[OSPF 비용 계산]

Cost = 참조 대역폭 / 인터페이스 대역폭

기본 참조 대역폭: 10^8 bps (100 Mbps)

예시:
- FastEthernet (100 Mbps): 10^8 / 10^8 = 1
- GigabitEthernet (1 Gbps): 10^8 / 10^9 = 0.1 → 1 (최소값)
- 10 GigabitEthernet: 10^8 / 10^10 = 0.01 → 1

참조 대역폭 변경 (고속 링크 권장):
  auto-cost reference-bandwidth 100000  (100 Gbps 기준)

[최장 프리픽스 매칭 (Longest Prefix Match)]

라우팅 결정 시 가장 구체적인(긴) 프리픽스 선택

예시: 목적지 10.1.1.5
  10.0.0.0/8    → 매칭 (프리픽스 길이 8)
  10.1.0.0/16   → 매칭 (프리픽스 길이 16) ★ 선택
  10.1.1.0/24   → 매칭 (프리픽스 길이 24) ★★ 선택

→ 10.1.1.0/24로 라우팅 (가장 긴 프리픽스)

[관리 거리 (Administrative Distance)]

같은 목적지에 여러 프로토콜이 경로를 제공할 때 우선순위 결정

┌─────────────────────────────────────────┐
│ 출처           │ AD 값 │ 설명           │
├─────────────────────────────────────────┤
│ 직접 연결       │   0  │ 인터페이스 직접│
│ 정적 라우팅     │   1  │ ip route       │
│ EIGRP 요약     │   5  │                │
│ eBGP           │  20  │ 외부 BGP       │
│ EIGRP 내부     │  90  │                │
│ IGRP           │ 100  │ (구식)         │
│ OSPF           │ 110  │                │
│ IS-IS          │ 115  │                │
│ RIP            │ 120  │                │
│ EIGRP 외부     │ 170  │                │
│ iBGP           │ 200  │ 내부 BGP       │
│ Unknown        │ 255  │ 사용 안 함     │
└─────────────────────────────────────────┘

코드 예시 (필수: Python 라우팅 시뮬레이터):

from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple, Set
from enum import Enum, auto
from collections import defaultdict
import heapq

# ============================================================
# 라우팅 테이블 및 프로토콜 시뮬레이터
# ============================================================

class RouteType(Enum):
    """라우트 타입"""
    CONNECTED = auto()   # 직접 연결
    STATIC = auto()      # 정적 라우팅
    OSPF = auto()        # OSPF
    RIP = auto()         # RIP
    BGP = auto()         # BGP


@dataclass
class Route:
    """라우트 엔트리"""
    network: str          # 목적지 네트워크
    prefix_len: int       # 프리픽스 길이
    next_hop: str         # 다음 홉
    interface: str        # 인터페이스
    metric: int           # 메트릭
    route_type: RouteType
    admin_distance: int   # 관리 거리

    @property
    def network_address(self) -> Tuple[int, ...]:
        """네트워크 주소를 튜플로 변환"""
        return tuple(int(x) for x in self.network.split('.'))

    def matches(self, dest_ip: str) -> bool:
        """목적지 IP가 이 라우트와 매칭되는지 확인"""
        dest_parts = [int(x) for x in dest_ip.split('.')]
        net_parts = self.network_address

        # 프리픽스 길이만큼 비트 비교
        bits_to_check = self.prefix_len
        for i in range(4):
            if bits_to_check <= 0:
                break
            mask_bits = min(8, bits_to_check)
            mask = (0xFF << (8 - mask_bits)) & 0xFF
            if (dest_parts[i] & mask) != (net_parts[i] & mask):
                return False
            bits_to_check -= 8
        return True

    def __str__(self):
        return (f"{self.network}/{self.prefix_len} via {self.next_hop} "
                f"[{self.route_type.name}, AD:{self.admin_distance}, "
                f"Metric:{self.metric}]")


class RoutingTable:
    """라우팅 테이블"""

    def __init__(self):
        self.routes: List[Route] = []

    def add_route(self, route: Route) -> None:
        """라우트 추가"""
        self.routes.append(route)
        # 프리픽스 길이 내림차순 정렬 (최장 매칭 우선)
        self.routes.sort(key=lambda r: r.prefix_len, reverse=True)

    def lookup(self, dest_ip: str) -> Optional[Route]:
        """최장 프리픽스 매칭으로 라우트 검색"""
        matching_routes = []

        for route in self.routes:
            if route.matches(dest_ip):
                matching_routes.append(route)

        if not matching_routes:
            return None

        # 1. 최장 프리픽스 매칭
        max_prefix = max(r.prefix_len for r in matching_routes)
        longest_matches = [r for r in matching_routes if r.prefix_len == max_prefix]

        # 2. 가장 낮은 AD 선택
        min_ad = min(r.admin_distance for r in longest_matches)
        best_routes = [r for r in longest_matches if r.admin_distance == min_ad]

        # 3. 가장 낮은 메트릭 선택
        return min(best_routes, key=lambda r: r.metric)

    def print_table(self) -> None:
        """라우팅 테이블 출력"""
        print("\n" + "=" * 70)
        print("                    라우팅 테이블")
        print("=" * 70)
        print(f"{'목적지':<20} {'다음 홉':<15} {'AD':<5} {'메트릭':<8} {'타입'}")
        print("-" * 70)
        for route in self.routes:
            print(f"{route.network}/{route.prefix_len:<15} {route.next_hop:<15} "
                  f"{route.admin_distance:<5} {route.metric:<8} {route.route_type.name}")
        print("=" * 70)


# ============================================================
# 다익스트라 알고리즘 (OSPF SPF 시뮬레이션)
# ============================================================

@dataclass
class Link:
    """링크 정보"""
    router1: str
    router2: str
    cost: int


class NetworkTopology:
    """네트워크 토폴로지"""

    def __init__(self):
        self.routers: Set[str] = set()
        self.links: List[Link] = []
        self.adjacency: Dict[str, Dict[str, int]] = defaultdict(dict)

    def add_router(self, name: str) -> None:
        """라우터 추가"""
        self.routers.add(name)

    def add_link(self, router1: str, router2: str, cost: int) -> None:
        """링크 추가 (양방향)"""
        self.links.append(Link(router1, router2, cost))
        self.adjacency[router1][router2] = cost
        self.adjacency[router2][router1] = cost

    def dijkstra(self, source: str) -> Tuple[Dict[str, int], Dict[str, str]]:
        """다익스트라 알고리즘으로 최단 경로 계산"""
        distances = {router: float('infinity') for router in self.routers}
        previous = {router: None for router in self.routers}
        distances[source] = 0

        # 우선순위 큐: (거리, 라우터)
        pq = [(0, source)]
        visited = set()

        while pq:
            current_dist, current = heapq.heappop(pq)

            if current in visited:
                continue
            visited.add(current)

            for neighbor, cost in self.adjacency[current].items():
                if neighbor in visited:
                    continue
                new_dist = current_dist + cost
                if new_dist < distances[neighbor]:
                    distances[neighbor] = new_dist
                    previous[neighbor] = current
                    heapq.heappush(pq, (new_dist, neighbor))

        return distances, previous

    def get_path(self, source: str, dest: str) -> List[str]:
        """소스에서 목적지까지의 경로 반환"""
        _, previous = self.dijkstra(source)
        path = []
        current = dest
        while current:
            path.append(current)
            current = previous[current]
        return list(reversed(path))

    def print_topology(self) -> None:
        """토폴로지 출력"""
        print("\n" + "=" * 50)
        print("          네트워크 토폴로지")
        print("=" * 50)
        for link in self.links:
            print(f"  {link.router1} ←──({link.cost})──→ {link.router2}")
        print("=" * 50)


# ============================================================
# RIP (거리 벡터) 시뮬레이터
# ============================================================

class RIPRouter:
    """RIP 라우터 시뮬레이터"""

    MAX_HOPS = 15
    INFINITY = 16

    def __init__(self, name: str):
        self.name = name
        self.neighbors: Dict[str, 'RIPRouter'] = {}  # 이름 → 라우터
        self.distance_vector: Dict[str, Tuple[int, str]] = {}  # 목적지 → (거리, 다음홉)
        self.distance_vector[name] = (0, name)  # 자기 자신

    def add_neighbor(self, neighbor: 'RIPRouter') -> None:
        """인접 라우터 추가"""
        self.neighbors[neighbor.name] = neighbor
        self.distance_vector[neighbor.name] = (1, neighbor.name)

    def receive_update(self, from_router: str,
                       vector: Dict[str, Tuple[int, str]]) -> bool:
        """인접 라우터로부터 거리 벡터 수신"""
        changed = False

        for dest, (dist, _) in vector.items():
            if dest == self.name:
                continue

            new_dist = dist + 1  # 1홉 추가

            if new_dist > self.MAX_HOPS:
                new_dist = self.INFINITY

            if dest not in self.distance_vector or \
               new_dist < self.distance_vector[dest][0]:
                self.distance_vector[dest] = (new_dist, from_router)
                changed = True

        return changed

    def send_update(self) -> Dict[str, Tuple[int, str]]:
        """자신의 거리 벡터를 인접 라우터에게 전송"""
        return self.distance_vector.copy()

    def print_routing_table(self) -> None:
        """라우팅 테이블 출력"""
        print(f"\n[{self.name}] RIP 라우팅 테이블:")
        print(f"{'목적지':<15} {'홉 수':<10} {'다음 홉'}")
        print("-" * 40)
        for dest, (dist, next_hop) in sorted(self.distance_vector.items()):
            if dist < self.INFINITY:
                print(f"{dest:<15} {dist:<10} {next_hop}")


class RIPNetwork:
    """RIP 네트워크 시뮬레이터"""

    def __init__(self):
        self.routers: Dict[str, RIPRouter] = {}

    def add_router(self, name: str) -> RIPRouter:
        router = RIPRouter(name)
        self.routers[name] = router
        return router

    def connect(self, r1: str, r2: str) -> None:
        """두 라우터 연결"""
        self.routers[r1].add_neighbor(self.routers[r2])
        self.routers[r2].add_neighbor(self.routers[r1])

    def simulate_convergence(self, max_iterations: int = 10) -> None:
        """RIP 수렴 시뮬레이션"""
        print("\n" + "=" * 50)
        print("     RIP 거리 벡터 교환 시뮬레이션")
        print("=" * 50)

        for iteration in range(max_iterations):
            print(f"\n--- Iteration {iteration + 1} ---")
            any_changed = False

            for router in self.routers.values():
                # 인접 라우터에게 자신의 벡터 전송
                my_vector = router.send_update()

                for neighbor_name, neighbor in router.neighbors.items():
                    changed = neighbor.receive_update(router.name, my_vector)
                    if changed:
                        any_changed = True
                        print(f"  {neighbor_name}: {router.name}으로부터 업데이트 수신")

            if not any_changed:
                print("\n✓ 수렴 완료!")
                break

        # 최종 상태 출력
        for router in self.routers.values():
            router.print_routing_table()


# ============================================================
# BGP 경로 선택 시뮬레이터
# ============================================================

@dataclass
class BGPRoute:
    """BGP 라우트"""
    prefix: str
    as_path: List[int]           # AS 경로
    next_hop: str
    local_pref: int = 100        # 로컬 선호도
    med: int = 0                 # Multi-Exit Discriminator
    origin: str = "IGP"          # IGP, EGP, INCOMPLETE
    is_ebgp: bool = True         # eBGP 여부
    igp_cost: int = 0            # Next-hop까지의 IGP 비용


class BGPPathSelector:
    """BGP 경로 선택기"""

    def __init__(self):
        self.routes: List[BGPRoute] = []

    def add_route(self, route: BGPRoute) -> None:
        self.routes.append(route)

    def select_best_path(self) -> Optional[BGPRoute]:
        """BGP 경로 선택 알고리즘"""
        if not self.routes:
            return None

        candidates = self.routes.copy()

        # 1. Highest Weight (로컬 설정, 생략)

        # 2. Highest Local Preference
        max_lp = max(r.local_pref for r in candidates)
        candidates = [r for r in candidates if r.local_pref == max_lp]
        if len(candidates) == 1:
            return candidates[0]

        # 3. Shortest AS-PATH
        min_as_len = min(len(r.as_path) for r in candidates)
        candidates = [r for r in candidates if len(r.as_path) == min_as_len]
        if len(candidates) == 1:
            return candidates[0]

        # 4. Lowest Origin Type (IGP < EGP < INCOMPLETE)
        origin_order = {"IGP": 0, "EGP": 1, "INCOMPLETE": 2}
        min_origin = min(origin_order[r.origin] for r in candidates)
        candidates = [r for r in candidates if origin_order[r.origin] == min_origin]
        if len(candidates) == 1:
            return candidates[0]

        # 5. Lowest MED
        min_med = min(r.med for r in candidates)
        candidates = [r for r in candidates if r.med == min_med]
        if len(candidates) == 1:
            return candidates[0]

        # 6. eBGP over iBGP
        ebgp_routes = [r for r in candidates if r.is_ebgp]
        if ebgp_routes:
            candidates = ebgp_routes
            if len(candidates) == 1:
                return candidates[0]

        # 7. Lowest IGP cost to BGP next-hop
        min_igp = min(r.igp_cost for r in candidates)
        candidates = [r for r in candidates if r.igp_cost == min_igp]

        # 8. Oldest route (첫 번째 선택)
        return candidates[0] if candidates else None

    def print_candidates(self) -> None:
        """후보 경로 출력"""
        print("\n" + "=" * 70)
        print("                    BGP 경로 후보")
        print("=" * 70)
        print(f"{'프리픽스':<18} {'AS-PATH':<20} {'LP':<5} {'MED':<5} {'Origin'}")
        print("-" * 70)
        for route in self.routes:
            as_path = ' '.join(str(asn) for asn in route.as_path)
            print(f"{route.prefix:<18} {as_path:<20} {route.local_pref:<5} "
                  f"{route.med:<5} {route.origin}")
        print("=" * 70)


# ============================================================
# 사용 예시
# ============================================================

if __name__ == "__main__":
    print("=" * 60)
    print("         라우팅 프로토콜 시뮬레이터 데모")
    print("=" * 60)

    # 1. 라우팅 테이블 조회 테스트
    print("\n1. 라우팅 테이블 최장 프리픽스 매칭")
    print("-" * 40)

    rt = RoutingTable()
    rt.add_route(Route("0.0.0.0", 0, "10.0.0.1", "eth0", 1, RouteType.STATIC, 1))
    rt.add_route(Route("10.0.0.0", 8, "10.0.0.1", "eth0", 1, RouteType.OSPF, 110))
    rt.add_route(Route("10.1.1.0", 24, "10.0.0.254", "eth0", 2, RouteType.OSPF, 110))
    rt.add_route(Route("192.168.1.0", 24, "10.0.0.254", "eth0", 3, RouteType.RIP, 120))

    rt.print_table()

    # 조회 테스트
    test_ips = ["10.1.1.5", "10.2.3.4", "192.168.1.100", "8.8.8.8"]
    for ip in test_ips:
        route = rt.lookup(ip)
        if route:
            print(f"  {ip} → {route.network}/{route.prefix_len}")
        else:
            print(f"  {ip} → 매칭되는 라우트 없음")

    # 2. 다익스트라 (OSPF SPF) 테스트
    print("\n\n2. 다익스트라 알고리즘 (OSPF SPF)")
    print("-" * 40)

    topo = NetworkTopology()
    for r in ['A', 'B', 'C', 'D', 'E']:
        topo.add_router(r)

    topo.add_link('A', 'B', 2)
    topo.add_link('A', 'C', 5)
    topo.add_link('B', 'C', 1)
    topo.add_link('B', 'D', 3)
    topo.add_link('C', 'D', 1)
    topo.add_link('D', 'E', 2)

    topo.print_topology()

    distances, previous = topo.dijkstra('A')
    print(f"\nA에서 각 라우터까지의 최단 거리:")
    for router, dist in sorted(distances.items()):
        path = topo.get_path('A', router)
        print(f"  {router}: {dist} (경로: {' → '.join(path)})")

    # 3. RIP 거리 벡터 시뮬레이션
    print("\n\n3. RIP 거리 벡터 시뮬레이션")
    print("-" * 40)

    rip_net = RIPNetwork()
    for name in ['R1', 'R2', 'R3', 'R4']:
        rip_net.add_router(name)

    rip_net.connect('R1', 'R2')
    rip_net.connect('R2', 'R3')
    rip_net.connect('R3', 'R4')
    rip_net.connect('R1', 'R4')

    rip_net.simulate_convergence()

    # 4. BGP 경로 선택
    print("\n\n4. BGP 경로 선택 알고리즘")
    print("-" * 40)

    bgp = BGPPathSelector()
    bgp.add_route(BGPRoute(
        prefix="203.0.113.0/24",
        as_path=[100, 200, 300],
        next_hop="10.0.1.1",
        local_pref=100,
        med=10
    ))
    bgp.add_route(BGPRoute(
        prefix="203.0.113.0/24",
        as_path=[100, 300],  # 더 짧은 AS-PATH
        next_hop="10.0.2.1",
        local_pref=100,
        med=20
    ))
    bgp.add_route(BGPRoute(
        prefix="203.0.113.0/24",
        as_path=[100, 400, 300],
        next_hop="10.0.3.1",
        local_pref=200,  # 더 높은 Local Pref
        med=5
    ))

    bgp.print_candidates()
    best = bgp.select_best_path()
    print(f"\n✓ 선택된 최적 경로: AS-PATH {best.as_path} "
          f"(Local Pref: {best.local_pref})")