에듀테크 프로젝트 대시보드
스위치와 브리지
이더넷 프레임 & MAC 주소에서 스위치 한 대가 MAC 주소를 학습하고 플러딩하는 기본기를 봤다. 그런데 실제 학교·회사 네트워크는 스위치가 한 대가 아니다. 스위치 여러 대를 어떻게 연결하고, 그 사이로 프레임이 어떻게 흘러가는지 — 그리고 케이블을 잘못 연결하면 무슨 일이 생기는지를 이 글에서 다룬다.
가장 단순한 연결 장비인 허브는 전기 신호를 그대로 증폭해서 들어온 포트만 빼고 전부로 내보낸다. MAC 주소 같은 건 아예 보지 않는다 — 그냥 전선을 여러 갈래로 나눈 것과 다름없다. 문제는 허브에 물린 모든 장치가 하나의 충돌 도메인을 공유한다는 것. 두 장치가 동시에 보내면 신호가 부딪혀(collision) 둘 다 다시 보내야 한다.
브리지(그리고 브리지를 포트 수십 개로 확장한 스위치)는 다르다. 각 포트로 들어온 프레임의 목적지 MAC 주소를 보고, 그 MAC이 연결된 포트로만 내보낸다. 그 결과 포트 하나하나가 독립된 충돌 도메인이 된다 — 포트가 8개면 충돌 도메인도 8개.
| 구분 | 허브 (Hub) | 브리지 / 스위치 |
|---|---|---|
| 동작 계층 | 1계층 (물리) — 신호만 증폭 | 2계층 (데이터링크) — MAC 주소 확인 |
| 전달 방식 | 들어온 포트 제외 전부 (무조건) | MAC 테이블 조회 → 아는 포트만, 모르면 플러딩 |
| 충돌 도메인 | 전체가 하나 | 포트마다 하나씩 |
| 학습 능력 | 없음 | 출발지 MAC을 스스로 학습 |
스위치 3대가 사슬처럼 연결돼 있다. SW1에는 PC-A·PC-B, SW2에는 PC-C, SW3에는 PC-D·PC-E가 물려 있다. PC-A → PC-D 버튼을 눌러 A가 아직 한 번도 통신한 적 없는 D에게 프레임을 보내면 무슨 일이 벌어지는지 관찰하자.
| 스위치 | MAC 주소 | 포트 |
|---|---|---|
| (모두 비어 있음) | ||
L12/L23 = 스위치끼리 연결된 업링크 포트. 각 스위치는 자신을 지나간 프레임의 출발지 MAC만 학습한다.
순서대로 눌러보면 이렇게 된다:
SW1이 A를 자기 포트에 학습하지만 D의 위치를 몰라 L12로 플러딩. SW2도 A를 L12 포트에 학습하고 역시 몰라서 L23으로 플러딩. SW3도 A를 L23 포트에 학습하고, D·E 양쪽으로 플러딩 — D는 받아서 처리하고 E는 자기 것이 아니라 버린다.SW3이 D를 학습하고, A는 이미 알고 있으므로(L23 방향) 플러딩 없이 그 포트로만 보낸다. SW2·SW1도 마찬가지로 이미 아는 경로로만 조용히 전달한다.배선 실수나 "혹시 몰라서 이중화"라는 생각으로, SW1과 SW2 사이를 케이블 2개로 연결했다고 하자. 이제 두 스위치 사이에는 고리(루프)가 생긴다. 목적지를 모르는 프레임이 플러딩되면 어떻게 될까 — 아래에서 직접 확인해보자.
지금까지 두 스위치 사이를 오간 프레임 복제본: 0 개
위에서 본 대로, 목적지를 모르는 프레임(혹은 브로드캐스트)은 루프가 있는 한 영원히 복제되며 순환한다. 회선은 순식간에 포화되고 스위치 CPU도 마비된다 — 이걸 브로드캐스트 폭풍(broadcast storm)이라 부른다. 이더넷 프레임에는 IP의 TTL 같은 "생존 시간"이 없어서, 물리적으로 루프가 있으면 스위치 스스로는 멈출 방법이 없다.
스위치 한 대의 핵심은 dict 하나(MAC → 포트)였다. 스위치가 여러 대면 이 dict가 스위치마다 하나씩 따로 존재하고, 서로 업링크 포트로 연결될 뿐이다.
class Switch: def __init__(self, name): self.name = name self.mac_table = {} # { mac 주소: 포트 } # 스위치 이름 → Switch 객체. 스위치 3대가 사슬로 연결된 상태. switches = {"SW1": Switch("SW1"), "SW2": Switch("SW2"), "SW3": Switch("SW3")} # 스위치끼리 어느 포트로 이어져 있는지 (업링크 맵) uplink_port = { ("SW1", "SW2"): "L12", ("SW2", "SW1"): "L12", ("SW2", "SW3"): "L23", ("SW3", "SW2"): "L23", } def handle_frame(sw_name, in_port, src_mac, dst_mac): sw = switches[sw_name] sw.mac_table[src_mac] = in_port # 1) 출발지는 무조건 학습 if dst_mac in sw.mac_table and sw.mac_table[dst_mac] != in_port: return [sw.mac_table[dst_mac]] # 2) 알면 정확히 그 포트 하나 return [p for p in all_ports(sw_name) if p != in_port] # 3) 모르면 나머지 전부 플러딩
루프가 없는 사슬·나무 구조에서는 이 로직만으로 언젠가 모든 스위치가 학습을 끝내고 조용해진다. 하지만 uplink_port에 같은 스위치 쌍이 포트 2개로 이어져 있다면? handle_frame은 그 사실을 전혀 모르기 때문에 — 위 3번 시뮬레이션처럼 영원히 플러딩을 반복한다. STP는 바로 이 지점에서, 코드를 건드리지 않고도 물리적 포트 하나를 아예 죽여서 문제를 막는다.