스위치의 MAC 학습
70분 안팎
학습 목표
입력된 프레임 순서로 MAC 학습표와 전달 포트 목록을 출력합니다.
개념
왜 스위치는 첫 프레임을 여러 포트로 보낼까요
학교의 학생 단말 s1과 s2가 같은 링크에 연결돼 있습니다. 스위치가 s2의 위치를 아직 모르면 s1의 첫 프레임을 목적지 포트 하나로 좁힐 수 없습니다. 이때 프레임을 폐기하면 처음 통신을 시작할 방법이 없어집니다. 이 레슨에서는 처음에는 넓게 전달하고 응답을 관찰한 뒤 전달 범위를 줄이는 과정을 입력 프레임 세 개로 재현합니다. IP 경로 계산 대신 링크 안의 전달 결정을 다룹니다.
이번 모듈의 Python 예제는 example.py에 저장해 python3 example.py로 실행합니다. 단계별 코드는 독립적입니다. 브라우저 실습은 표준 입력 전체를 읽고 결과만 출력합니다. 로컬 묶음은 새 폴더에서 수정하고 python3 test_docs.py로 문서를 확인한 뒤 전용 Linux VM에서 sudo bash check.sh를 실행합니다. Linux 커널 명령은 이 Mac에서 실행하지 않아 출력 칸을 비웁니다. 같은 VLAN 학생 두 단말을 기준으로 학교 프로젝트의 연결을 확장합니다.
학습하는 것은 출발지입니다
스위치가 포트 p1에서 출발지 MAC A인 프레임을 받으면 A는 p1 쪽에 있다는 항목을 저장합니다. 목적지 B의 위치는 그 프레임 하나로 알 수 없습니다. 목적지 주소가 적혀 있다는 사실과 그 주소가 어느 포트에 있는지 아는 것은 다릅니다. 학습표를 FDB라고 부르며 이번 모형은 MAC 문자열을 키로, 마지막으로 관측한 입력 포트를 값으로 저장합니다. IP와 MAC의 대응을 저장하는 ARP 캐시와 구분합니다.
다음으로 목적지 MAC을 FDB에서 조회합니다. 이미 알고 있고 그 포트가 입력 포트와 다르면 해당 포트 하나로 전달합니다. 목적지 포트가 입력 포트와 같으면 같은 쪽에 있는 것으로 판단해 다른 포트로 보내지 않습니다. 이번 모형에서는 이 경우 출력 포트 목록이 비어 있으므로 DROP을 출력합니다. 실제 스위치의 hairpin 설정처럼 입력 포트로 다시 전달하는 예외 기능은 이 과제에 포함하지 않습니다.
목적지를 모르면 입력 포트를 제외한 전달 가능한 포트로 복제합니다. 이것을 unknown unicast flooding이라고 합니다. 목적지 MAC 자체를 브로드캐스트 값으로 바꾸는 것이 아닙니다. 이미 목적지가 특정 단말인 프레임을 여러 포트로 전달할 뿐입니다. 뒤에서 VLAN을 도입하면 이 후보 포트도 같은 VLAN의 허용 포트로 제한됩니다. 지금은 하나의 VLAN 안에서 모든 포트가 정상 전달 상태라고 가정합니다.
응답이 학습을 완성합니다
p1 A B가 첫 입력이라면 A만 p1에 등록되고 p2와 p3으로 전달합니다. 이어서 p2 B A가 들어오면 B를 p2에 등록하고 이미 아는 A의 p1로만 보냅니다. 다시 p1 A B를 받으면 B를 알기 때문에 p2만 선택합니다. 처음과 마지막의 목적지가 같아도 중간의 응답 때문에 결과가 달라집니다. 학습표는 현재 입력 한 줄만의 함수가 아니라 앞선 프레임 순서에 영향을 받는 상태입니다.
A가 이동해 p3에서 프레임을 보내면 A의 기존 p1 값을 p3으로 갱신합니다. 이전 항목을 영구히 고정하면 이동 후 통신이 잘못된 방향으로 향할 수 있습니다. 같은 MAC이 두 포트에서 반복해서 나타나면 실제 환경에서는 이동, 주소 중복, 루프 등을 조사합니다. 이번 과제는 마지막으로 관측한 위치를 채택하는 규칙만 구현합니다. 문자열 출현 빈도가 높다고 공격이나 장비 고장을 확정하지 않습니다.
출발지 학습을 먼저 하고 목적지 판단을 나중에 합니다. 출발지와 목적지가 모두 A인 입력이라면 방금 A를 입력 포트에 등록했으므로 결과는 DROP입니다. 순서를 반대로 쓰면 예전 위치를 참조해 다른 포트로 보낼 수 있습니다. 현실의 프레임 유효성 검사는 더 복잡하지만 이 모형의 경계 사례는 처리 순서를 검증하도록 의도적으로 포함합니다. 기대 출력과 다르면 사전 갱신 시점을 먼저 확인합니다.
입력과 출력의 계약을 고정합니다
첫 줄은 1부터 9까지의 포트 수 N입니다. 포트는 p1부터 pN까지 존재합니다. 이후 각 줄은 입력포트 출발지 목적지 세 칸이며 MAC은 A부터 Z까지 한 글자로 축약합니다. 실제 Ethernet 주소를 축약한 기호이지 A가 유효한 실장비 MAC 형식이라는 뜻은 아닙니다. 목적지 BCAST만 브로드캐스트를 뜻합니다. 빈 줄은 무시하고 프레임이 없어도 마지막 TABLE을 출력합니다.
각 프레임마다 전달할 포트들을 숫자 순서로 공백으로 연결합니다. 목록이 비면 DROP 한 줄입니다. 마지막에는 TABLE A=p1 B=p2처럼 MAC 알파벳 순서로 학습표를 출력합니다. 학습된 MAC이 없으면 TABLE만 출력합니다. 출력에 설명 문장이나 입력 안내를 섞지 않습니다. 순서가 일정해야 같은 입력을 다시 실행했을 때 결과를 비교할 수 있으므로 집합을 그대로 출력하지 않습니다.
포트 수 범위 오류, 없는 입력 포트, 세 칸이 아닌 프레임, 잘못된 MAC 기호는 ERROR 한 줄로 끝냅니다. 입력 전체를 먼저 검증하므로 앞에 정상 프레임이 있어도 중간 출력 없이 ERROR만 남깁니다. 이는 채점 결과를 결정적으로 만드는 규칙이며 실제 장비가 모든 과거 전달을 취소한다는 뜻이 아닙니다. 학습자는 파싱과 상태 갱신을 나눠 오류 처리가 전달 로직을 오염시키지 않도록 구현합니다.
사전 조회 오류를 전달 오류와 구분합니다
목적지 조회에 table[dst]를 쓰면 처음 보는 주소에서 KeyError가 생길 수 있습니다. 이것은 링크 단절 메시지가 아니라 Python 자료 조회 오류입니다. table.get(dst)를 사용해 없음과 기존 포트를 구분합니다. 반대로 목적지 주소를 무조건 입력 포트에 등록하는 실수는 예외 없이 잘못된 결과를 내므로 응답 순서 테스트로 잡습니다. 프로그램이 종료되지 않았다는 사실만으로 전달 결정이 옳다고 판단하지 않습니다.
N=1인 경계에서는 미학습 목적지나 BCAST도 내보낼 다른 포트가 없어 DROP입니다. “브로드캐스트는 모든 포트”라는 문장을 입력 포트까지 포함한다고 구현하면 이 시험에서 틀립니다. A의 이동을 검사할 때는 세 프레임 뒤의 TABLE A=p3과 마지막 전달 포트를 함께 비교합니다. 최종 표만 맞추고 프레임별 전달을 틀리는 구현도 있으므로 두 종류의 결과가 모두 필요합니다.
동적 학습 항목은 실제 브리지에서 시간이 지나면 만료될 수 있습니다. 이번 실습은 시간·링크 다운·STP 상태를 모델링하지 않습니다. 따라서 오래된 항목을 유지하는 코드가 실습에서는 맞아도 실장비의 모든 행동을 설명하지 않습니다. 루프가 있는 토폴로지의 폭주나 멀티캐스트 제어도 별도 문제입니다. 실습 범위를 좁혀 프레임 순서와 학습·전달의 관계를 정확히 설명하는 데 집중합니다.
완료 후 s1이 처음 보낸 경우와 s2가 응답한 뒤의 경우를 구분해 동료에게 설명합니다. FDB의 A=p1을 보고 A의 IP 주소까지 알 수 있다고 말하지 않습니다. 같은 VLAN 안에서도 출발지 위치와 목적지 조회는 서로 다른 단계입니다. 딕셔너리·튜플·정렬의 일반 문법은 더 읽기에 연결된 서재에서 보완하고, 여기서는 학교 단말의 위치를 언제 배우는지 프레임 기록으로 증명합니다.
기술 확인: Linux 커널 브리지 문서. 이 레슨의 학습표 예제는 시간 만료 없는 별도 교육 모형입니다.
따라하기
첫 프레임과 응답을 계산합니다
p1에서 A를 배운 뒤 목적지 B가 없음을 확인합니다. 이어서 B의 응답으로 전달 범위가 줄어듭니다.
table={}
ports=['p1','p2','p3']
for port,src,dst in [('p1','A','B'),('p2','B','A'),('p1','A','B')]:
table[src]=port
target=table.get(dst)
selected=[p for p in ports if p!=port] if target is None else ([target] if target!=port else [])
print(' '.join(selected) if selected else 'DROP')
print('TABLE '+' '.join(k+'='+table[k] for k in sorted(table)))실행 결과
p2 p3 p1 p2 TABLE A=p1 B=p2
MAC 이동 후의 위치를 확인합니다
A의 최근 출발 포트가 p3으로 바뀌면 B가 A로 보낼 때 p3을 선택합니다.
table={}
for port,src in [('p1','A'),('p3','A'),('p2','B')]:
table[src]=port
print('A='+table['A'])
print('B='+table['B'])
print('B-to-A='+table['A'])실행 결과
A=p3 B=p2 B-to-A=p3
입력 포트로 재전송하지 않습니다
한 포트뿐이면 BCAST라도 전달할 다른 포트가 없습니다. 모형의 empty 목록과 DROP 대응을 확인합니다.
ports=['p1']
selected=[p for p in ports if p!='p1']
print(' '.join(selected) if selected else 'DROP')실행 결과
DROP
형식 오류를 상태 처리 전에 찾습니다
존재하지 않는 p3을 허용하지 않습니다. 포트 membership과 줄 구조를 먼저 검사합니다.
parts='p3 A B'.split()
ports=['p1','p2']
print('OK' if len(parts)==3 and parts[0] in ports else 'ERROR')실행 결과
ERROR
확인 문제
실습
첫 줄 N(1~9), 다음 각 줄 입력포트 출발지 목적지를 읽습니다. 포트 p1~pN, 출발지 A~Z, 목적지 A~Z 또는 BCAST입니다. 출발지를 먼저 학습한 뒤 목적지가 미학습이거나 BCAST면 입력 포트 외 전부로, 알려진 다른 포트면 그 포트로, 같은 포트면 DROP을 출력합니다. 포트는 숫자 순서입니다. 마지막 TABLE과 알파벳순 MAC=포트 항목을 출력합니다. 프레임이 없으면 TABLE만 출력하고, 형식 오류가 있으면 부분 출력 없이 ERROR 한 줄입니다. 빈 줄은 무시합니다. 상태 갱신·이동·단일 포트·오류를 구현합니다.
모범 답안
import sys,re
def solve(text):
lines=[s.strip() for s in text.splitlines() if s.strip()]
if not lines or not re.fullmatch(r"[1-9]",lines[0]):
return "ERROR"
n=int(lines[0]);ports=["p"+str(i) for i in range(1,n+1)];frames=[]
for line in lines[1:]:
parts=line.split()
if len(parts)!=3:
return "ERROR"
port,src,dst=parts
if port not in ports or not re.fullmatch(r"[A-Z]",src) or not (re.fullmatch(r"[A-Z]",dst) or dst=="BCAST"):
return "ERROR"
frames.append(parts)
table={};out=[]
for port,src,dst in frames:
table[src]=port
target=table.get(dst)
if dst=="BCAST" or target is None:
selected=[p for p in ports if p!=port]
else:
selected=[target] if target!=port else []
out.append(" ".join(selected) if selected else "DROP")
out.append("TABLE"+(" "+" ".join(k+"="+table[k] for k in sorted(table)) if table else ""))
return "\n".join(out)
print(solve(sys.stdin.read()))
더 읽기
면접 질문
- VLAN을 나누고 통신을 허용하는 구성을 설명합니다.