캡슐화와 역캡슐화
응용 데이터에 TCP·IP·이더넷 제어 정보가 붙고 벗겨지는 과정과 PDU·오버헤드를 가상 바이트 예제와 캡처 절차로 확인합니다.
계층 모델을 배웠으니, 이제 핵심 질문에 답할 수 있어야 합니다.
계층들 사이에서 데이터는 정확히 어떤 방식으로 전달될까요?
웹 브라우저에서 Hello라는 텍스트를 서버에 보낸다고 합시다.
이 텍스트가 상대방에게 도달하려면 응용 계층에서 시작하여 아래 계층으로 내려가며 각 계층의 정보가 추가되고, 수신 측에서는 아래 계층부터 올라가며 그 정보를 하나씩 벗깁니다.
이 과정이 바로 캡슐화(Encapsulation)와 역캡슐화(Decapsulation)입니다.
보내는 쪽: 캡슐화
캡슐화는 데이터가 상위 계층에서 하위 계층으로 내려가면서, 각 계층이 자기 계층에서 필요한 제어 정보를 덧붙이는 과정입니다.
대부분은 앞쪽의 헤더(Header)이지만, 이더넷처럼 뒤쪽에 오류 검사용 트레일러(Trailer)를 붙이는 경우도 있습니다.
하위 계층 페이로드에 포함되는 상위 PDU
전송할 응용 바이트를 준비합니다. IP 패킷을 Ethernet 헤더와 FCS 사이에 싣습니다. 각 단계의 값과 전환 이유를 비교합니다.
ENCAPSULATION · CONTAINMENT / OUTER CONTROL
상위 단위 전체가 하위 계층의 페이로드에 포함된다
TCP 헤더와 응용 데이터는 IPv4의 페이로드이고, IPv4 헤더까지 포함한 패킷은 Ethernet 헤더와 FCS 사이에 실립니다. 수신은 바깥 제어 정보를 처리하며 안쪽 데이터에 접근합니다.
IPv4/TCP를 Ethernet에 싣는 개념도이며 크기 비율은 실제 바이트 수와 다릅니다. 한 응용 메시지가 한 TCP 세그먼트에 대응한다는 뜻은 아닙니다.
청록 영역은 실리는 응용 데이터입니다. 가로 그림은 추가되는 헤더를 행별로 보여 주고, 모바일은 프레임·IP 패킷·TCP 세그먼트 전체의 포함 관계를 중첩으로 보여 줍니다. 각 헤더가 안쪽 데이터를 전부 포함한다는 뜻은 아닙니다. TCP 메시지 경계·HTTPS 평문·실제 바이트 크기는 이 개념도만으로 판단하지 않습니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 응용 데이터부터 Ethernet 프레임까지 진행합니다.
관계도의 보충 설명
| 구분 | 전체 도표의 내용 |
|---|---|
| 제목 | 하위 계층 페이로드에 포함되는 상위 PDU |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 응용 데이터 전송할 응용 바이트를 준비합니다. |
|
|
| TCP 세그먼트 응용 바이트 앞에 TCP 제어 헤더를 둡니다. |
|
|
| IPv4 패킷 TCP 세그먼트 전체를 IP 페이로드에 싣습니다. |
|
|
| Ethernet 프레임 IP 패킷을 Ethernet 헤더와 FCS 사이에 싣습니다. |
|
|
하위 계층 페이로드에 포함되는 상위 PDU
전송할 응용 바이트를 준비합니다. IP 패킷을 Ethernet 헤더와 FCS 사이에 싣습니다. 각 단계의 값과 전환 이유를 비교합니다.
ENCAPSULATION · CONTAINMENT / OUTER CONTROL
상위 단위 전체가 하위 계층의 페이로드에 포함된다
TCP 헤더와 응용 데이터는 IPv4의 페이로드이고, IPv4 헤더까지 포함한 패킷은 Ethernet 헤더와 FCS 사이에 실립니다. 수신은 바깥 제어 정보를 처리하며 안쪽 데이터에 접근합니다.
IPv4/TCP를 Ethernet에 싣는 개념도이며 크기 비율은 실제 바이트 수와 다릅니다. 한 응용 메시지가 한 TCP 세그먼트에 대응한다는 뜻은 아닙니다.
청록 영역은 실리는 응용 데이터입니다. 가로 그림은 추가되는 헤더를 행별로 보여 주고, 모바일은 프레임·IP 패킷·TCP 세그먼트 전체의 포함 관계를 중첩으로 보여 줍니다. 각 헤더가 안쪽 데이터를 전부 포함한다는 뜻은 아닙니다. TCP 메시지 경계·HTTPS 평문·실제 바이트 크기는 이 개념도만으로 판단하지 않습니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 응용 데이터부터 Ethernet 프레임까지 진행합니다.
관계도의 보충 설명
| 구분 | 전체 도표의 내용 |
|---|---|
| 제목 | 하위 계층 페이로드에 포함되는 상위 PDU |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 응용 데이터 전송할 응용 바이트를 준비합니다. |
|
|
| TCP 세그먼트 응용 바이트 앞에 TCP 제어 헤더를 둡니다. |
|
|
| IPv4 패킷 TCP 세그먼트 전체를 IP 페이로드에 싣습니다. |
|
|
| Ethernet 프레임 IP 패킷을 Ethernet 헤더와 FCS 사이에 싣습니다. |
|
|
응용 계층에서 시작합니다.
브라우저가 HTTP 요청 메시지를 생성합니다.
여기에는 요청 메서드, URL, 헤더, 바디 등이 포함됩니다.
이 시점의 데이터를 메시지(Message)라고 부릅니다.
이 메시지가 전송 계층으로 내려갑니다.
TCP는 응용 데이터를 바이트 스트림으로 받아 전송할 부분에 TCP 헤더를 붙입니다. 한 메시지가 여러 세그먼트로 나뉘거나 여러 메시지의 바이트가 한 세그먼트에 실릴 수 있습니다.
TCP 헤더에는 출발지/목적지 포트 번호, 순서 번호(Sequence Number), 확인 번호(Acknowledgment Number), 윈도우 크기 등이 담깁니다.
이 결과물을 세그먼트(Segment)라고 부릅니다.
세그먼트가 인터넷 계층으로 내려갑니다.
IP는 세그먼트 앞에 IP 헤더를 붙입니다.
IP 헤더에는 출발지/목적지 IP 주소, TTL(Time To Live), 프로토콜 번호 등이 담깁니다.
이 결과물을 패킷(Packet)이라고 부릅니다.
패킷이 네트워크 인터페이스 계층으로 내려갑니다.
이더넷은 패킷 앞에 이더넷 헤더(목적지 MAC, 출발지 MAC, EtherType)를, 뒤에는 트레일러(FCS: 오류 검출 코드)를 붙입니다.
이 최종 결과물을 프레임(Frame)이라고 부릅니다.
프레임은 최종적으로 비트(Bit)로 변환되어 물리적 매체(케이블, 전파)를 통해 전송됩니다.
각 계층 헤더의 구조
이더넷 헤더 (14바이트, FCS 제외)
이더넷 프레임의 기본 헤더는 목적지 MAC 6바이트, 출발지 MAC 6바이트, EtherType 2바이트로 구성됩니다.
프레임 끝에는 FCS 4바이트가 붙지만, 일반적인 패킷 캡처 도구에서는 NIC나 드라이버가 FCS를 제거한 뒤 전달하는 경우가 많습니다.
IPv4 헤더 (20~60바이트)
| 필드 | 크기·의미 |
|---|---|
| 버전 | Version, 4비트. IPv4는 4. |
| 헤더 길이 | IHL, 4비트. 32비트 단위이며 보통 5 = 20바이트. |
| 전체 길이 | Total Length, 2바이트. IP 헤더와 페이로드를 합한 길이. |
| 수명 제한 | TTL, 1바이트. 라우터를 지날 때 감소하며 0이면 전달하지 않음. 초기값은 시스템 설정에 따라 다름. |
| 상위 규약 | Protocol, 1바이트. 6 = TCP, 17 = UDP. |
| 출발지 | Source IP, 4바이트. 출발지 IPv4 주소. |
| 목적지 | Destination IP, 4바이트. 목적지 IPv4 주소. |
TCP 헤더 (20~60바이트)
| 필드 | 크기·의미 |
|---|---|
| 출발지 포트 | Source Port, 2바이트. 0~65535 범위. |
| 목적지 포트 | Destination Port, 2바이트. 목적지의 소켓 식별에 사용. |
| 순서 번호 | Sequence Number, 4바이트. 바이트 스트림의 위치. |
| 확인 번호 | Acknowledgment, 4바이트. 확인 응답 번호. |
| 헤더 길이 | Data Offset, 4비트. 옵션이 있으면 TCP 헤더가 길어짐. |
| 제어 플래그 | Flags, 비트 필드. SYN·ACK·FIN·RST 등. |
| 수신 윈도 | Window Size, 2바이트. 수신자가 광고하는 윈도 값이며 협상한 스케일이 적용될 수 있음. |
| 오류 검사 | Checksum, 2바이트. 전송 오류 검출용이며 암호학적 무결성 보장은 아님. |
받는 쪽: 역캡슐화
수신 측에서는 큰 흐름상 반대 방향의 처리가 일어납니다.
다만 실제 수신 경로에는 오류가 있는 프레임을 버리거나, IP 조각을 재조립하거나, TCP 바이트 스트림을 다시 맞추거나, TLS를 복호화하는 단계가 추가될 수 있습니다.
물리적 신호가 도착하면 네트워크 인터페이스 계층이 비트를 프레임으로 복원합니다.
일반적인 수신 필터는 목적지 MAC, 허용된 브로드캐스트·멀티캐스트 등을 확인하고 FCS로 전송 오류를 검사합니다. 캡처용 promiscuous 모드 등에서는 수신 조건이 달라질 수 있습니다.
문제가 없으면 이더넷 헤더와 트레일러를 벗겨내고, 안에 있는 패킷을 인터넷 계층으로 올려보냅니다.
인터넷 계층이 IP 헤더를 읽어 목적지 IP 주소를 확인하고, IP 헤더를 벗겨냅니다.
안에 있는 세그먼트를 전송 계층으로 올려보냅니다.
전송 계층은 출발지·목적지 주소와 포트 등으로 TCP 연결을 구분하고 해당 소켓에 바이트 스트림을 전달합니다.
TCP 세그먼트 경계와 HTTP 메시지 경계가 항상 일치하는 것은 아니며, HTTPS라면 TLS 복호화 뒤에 HTTP 메시지를 볼 수 있습니다.
응용 계층에서 애플리케이션(예: 웹 서버)이 HTTP 메시지를 파싱하여 요청을 처리합니다.
PDU 이름 정리
각 계층에서의 데이터 단위를 PDU(Protocol Data Unit)라고 합니다.
| 계층 | PDU와 구성 |
|---|---|
| 응용 | 메시지: 애플리케이션이 해석하는 데이터. |
| 전송 | TCP 세그먼트: TCP 헤더 + 데이터. UDP 데이터그램: UDP 헤더 + 데이터. |
| 인터넷 | IP 패킷: IP 헤더 + 상위 계층 데이터. |
| 링크 | 프레임: L2 헤더 + 페이로드. Ethernet에서는 FCS 같은 트레일러도 포함. |
| 물리 | 비트: 전기·빛·전파 신호로 전달. |
패킷은 넓은 의미로도 쓰입니다. 이 절에서는 계층을 구분하기 위해 IP 패킷, TCP 세그먼트, Ethernet 프레임이라고 부릅니다.
크기 오버헤드 계산
아래 계산은 VLAN 태그와 IP/TCP 옵션이 없는 프레임을 기준으로 합니다. preamble·IFG·TLS·응용 헤더를 제외한 프레임 내 TCP 페이로드 비율이며 실측 처리량이 아닙니다.
"""캡슐화로 인한 오버헤드를 계산"""
ethernet_header = 14 # 목적지MAC(6) + 출발지MAC(6) + 이더타입(2)
ip_header = 20 # 기본 IPv4 헤더
tcp_header = 20 # 기본 TCP 헤더 (옵션 제외)
fcs = 4 # Frame Check Sequence
ethernet_min_frame = 64 # 목적지 MAC부터 FCS까지의 최소 프레임 크기
total_overhead = ethernet_header + ip_header + tcp_header + fcs
# 일반적인 이더넷 MTU = 1500 바이트 (IP 패킷 최대 크기)
mtu = 1500
max_tcp_payload = mtu - ip_header - tcp_header # MSS
print(f"=== 캡슐화 오버헤드 ===")
print(f"이더넷 헤더: {ethernet_header} bytes")
print(f"IP 헤더: {ip_header} bytes")
print(f"TCP 헤더: {tcp_header} bytes")
print(f"FCS: {fcs} bytes")
print(f"총 오버헤드: {total_overhead} bytes")
print()
print(f"이더넷 MTU: {mtu} bytes")
print(f"TCP MSS: {max_tcp_payload} bytes")
print(f"최대 프레임: {mtu + ethernet_header + fcs} bytes")
print(f"데이터 효율: {max_tcp_payload/(mtu+ethernet_header+fcs)*100:.1f}%")
print()
# 작은 패킷의 비효율
small_data = 1 # 1바이트 데이터 (예: Telnet 키 입력)
small_frame_without_padding = small_data + ip_header + tcp_header + ethernet_header + fcs
small_frame = max(ethernet_min_frame, small_frame_without_padding)
padding = small_frame - small_frame_without_padding
print(f"=== 작은 패킷의 비효율 ===")
print(f"1바이트 전송 시 프레임 크기: {small_frame} bytes")
print(f"이더넷 padding: {padding} bytes")
print(f"데이터 효율: {small_data/small_frame*100:.1f}%")
print(f"→ 작은 TCP 세그먼트를 줄이려는 최적화가 필요한 이유")1바이트 데이터를 보내더라도 이더넷 최소 프레임 크기 때문에 목적지 MAC부터 FCS까지 최소 64바이트가 전송됩니다.
데이터 효율은 약 1.6%에 불과합니다.
padding까지 직접 계산하기: 1바이트 데이터에 IPv4·TCP 헤더 각 20바이트와 Ethernet 헤더 14바이트, FCS 4바이트를 더하면 59바이트입니다. 최소 프레임 64바이트까지 5바이트가 더 필요하므로 코드의 padding은 5이고, 효율은 1 / 64 × 100 ≈ 1.6%입니다. RFC 894의 Ethernet padding 설명은 이 채움 바이트가 IP 패킷의 일부가 아니며 IP Total Length에도 포함되지 않는다고 구분합니다.
독립 변형: 같은 옵션 없는 헤더 조건에서 TCP 데이터가 10바이트라면 프레임 크기·padding·효율은 얼마일까요? 최소 크기에 무조건 64를 더하는 것이 아니라, 계산한 크기가 최솟값보다 작은지 먼저 확인하세요.
최솟값과 더하는 값 구분
10 + 20 + 20 + 14 + 4 = 68바이트이므로 이미 최소 64바이트 이상입니다. padding은 0이고 효율은 10 / 68 × 100 ≈ 14.7%입니다. 64바이트는 모든 프레임에 추가하는 오버헤드가 아닙니다. 이 비율에는 preamble·IFG·TLS·응용 헤더와 재전송 비용을 넣지 않았습니다.
TCP의 Nagle 알고리즘은 작은 세그먼트를 묶으려는 최적화 중 하나이며, 응용의 지연 요구와 함께 판단해야 합니다.
중간 라우터에서의 처리
캡슐화와 역캡슐화는 송수신 양쪽에서만 일어나는 것이 아닙니다.
중간에 있는 라우터에서도 부분적인 역캡슐화와 재캡슐화가 일어납니다.
라우터 경계에서 바뀌는 링크 주소
서버 IP 패킷을 다음 홉 A로 가는 프레임에 싣습니다. B 인터페이스에서 새 프레임을 만듭니다. 각 단계의 값과 전환 이유를 비교합니다.
ROUTER FORWARDING · LOCAL MAC / ENDPOINT IP
라우터 경계에서 MAC은 다음 링크에 맞추고 종단 IP는 유지한다
NAT·터널이 없는 두 Ethernet 링크의 예시입니다. 라우터 A/B는 서로 다른 인터페이스이며 다음 링크의 헤더를 새로 만듭니다. 같은 L2의 스위치를 지날 때마다 MAC을 바꾸는 규칙은 아닙니다.
NAT·터널이 없는 두 Ethernet 링크의 예시입니다. A와 B는 라우터의 서로 다른 인터페이스입니다.
바뀌는 필드와 유지되는 필드를 따로 확인
교체: 출발지·목적지 MAC · 새 링크 FCS
갱신: TTL 감소 · IPv4 헤더 체크섬
유지: 종단 IP 주소 · 일반 전달의 TCP/응용 데이터
NAT·방화벽·프록시·터널링에서는 이 예시보다 깊은 정보를 보거나 주소를 바꿀 수 있습니다. 프레임 캡처에서는 FCS가 NIC·드라이버에 의해 빠질 수 있습니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 첫 Ethernet 링크부터 둘째 링크까지 진행합니다.
관계도의 보충 설명
| 구분 | 전체 도표의 내용 |
|---|---|
| 제목 | 라우터 경계에서 바뀌는 링크 주소 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 첫 Ethernet 링크 서버 IP 패킷을 다음 홉 A로 가는 프레임에 싣습니다. |
|
|
| 라우터 처리 라우터가 링크 정보를 처리하고 다음 홉을 고릅니다. |
|
|
| 둘째 링크 B 인터페이스에서 새 프레임을 만듭니다. |
|
|
라우터 경계에서 바뀌는 링크 주소
서버 IP 패킷을 다음 홉 A로 가는 프레임에 싣습니다. B 인터페이스에서 새 프레임을 만듭니다. 각 단계의 값과 전환 이유를 비교합니다.
ROUTER FORWARDING · LOCAL MAC / ENDPOINT IP
라우터 경계에서 MAC은 다음 링크에 맞추고 종단 IP는 유지한다
NAT·터널이 없는 두 Ethernet 링크의 예시입니다. 라우터 A/B는 서로 다른 인터페이스이며 다음 링크의 헤더를 새로 만듭니다. 같은 L2의 스위치를 지날 때마다 MAC을 바꾸는 규칙은 아닙니다.
NAT·터널이 없는 두 Ethernet 링크의 예시입니다. A와 B는 라우터의 서로 다른 인터페이스입니다.
바뀌는 필드와 유지되는 필드를 따로 확인
교체: 출발지·목적지 MAC · 새 링크 FCS
갱신: TTL 감소 · IPv4 헤더 체크섬
유지: 종단 IP 주소 · 일반 전달의 TCP/응용 데이터
NAT·방화벽·프록시·터널링에서는 이 예시보다 깊은 정보를 보거나 주소를 바꿀 수 있습니다. 프레임 캡처에서는 FCS가 NIC·드라이버에 의해 빠질 수 있습니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 첫 Ethernet 링크부터 둘째 링크까지 진행합니다.
관계도의 보충 설명
| 구분 | 전체 도표의 내용 |
|---|---|
| 제목 | 라우터 경계에서 바뀌는 링크 주소 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 첫 Ethernet 링크 서버 IP 패킷을 다음 홉 A로 가는 프레임에 싣습니다. |
|
|
| 라우터 처리 라우터가 링크 정보를 처리하고 다음 홉을 고릅니다. |
|
|
| 둘째 링크 B 인터페이스에서 새 프레임을 만듭니다. |
|
|
라우터는 프레임을 수신하면 링크 계층 헤더를 벗기고 IP 헤더에서 목적지 IP를 확인합니다.
라우팅 테이블을 조회해 다음 홉을 결정한 뒤, 다음 링크에 맞는 새로운 L2 헤더를 붙여 프레임을 재생성합니다.
다음 링크가 이더넷이라면 이때 다음 홉의 MAC 주소가 들어갑니다.
핵심은 일반적인 라우터가 L3(IP)까지 확인해 다음 홉을 결정하고, TCP 헤더나 HTTP 데이터는 그대로 통과시킨다는 것입니다.
NAT·터널이 없는 일반 IP 전달에서는 종단 IP 주소가 유지됩니다. Ethernet 링크가 라우터 경계에서 바뀌면 새 링크의 MAC 주소를 사용하며, 같은 L2 안의 스위치를 지날 때마다 교체하는 것은 아닙니다. IPv4 라우터는 TTL을 감소시키고 헤더 체크섬도 갱신합니다.
주소를 추적하는 연습: PC → 스위치 → 라우터의 인터페이스 A → 라우터의 인터페이스 B → 서버 경로에서, PC와 A는 첫 Ethernet 링크에, B와 서버는 두 번째 Ethernet 링크에 있다고 가정합니다. NAT·터널·프록시·패킷 단편화는 없습니다. 스위치 통과만으로 목적지 MAC을 서버 MAC으로 바꾸는지, 라우터가 새 프레임을 만들 때 목적지 IP도 바꾸는지 먼저 예측해 보세요.
같은 링크의 전달과 라우터 경계 확인
첫 링크의 프레임은 PC의 MAC에서 라우터 A의 MAC으로 향하며, 스위치는 같은 L2 전달을 위해 그 주소를 교체하지 않습니다. 라우터가 두 번째 링크에 새 프레임을 만들면 출발지 MAC은 B, 목적지 MAC은 서버가 됩니다. IP의 출발지·목적지는 PC·서버로 유지하지만 TTL은 감소하고 IPv4 헤더 체크섬은 갱신됩니다. IP 패킷이 그대로라는 말은 모든 비트가 동일하다는 뜻이 아닙니다.
다만 NAT, 방화벽, 프록시, 터널링 장비는 이보다 더 깊은 계층의 정보를 보거나 주소를 바꿀 수 있습니다.
Wireshark로 실제 확인하기
Wireshark는 네트워크 인터페이스를 통해 오가는 패킷을 캡처하여 각 계층의 헤더를 시각적으로 보여주는 프로그램입니다.
아래는 권한과 대상 인터페이스를 준비한 Linux 환경의 캡처 명령 예시입니다. 이 교재가 실제 실행한 캡처 결과는 아니며 eth0와 주소는 대상에 맞게 확인해야 합니다. 포트 80 필터가 HTTP 프로토콜을 검증하는 것은 아닙니다.
# Wireshark 대신 CLI로 패킷 캡처 (서버 환경)
# HTTP 트래픽 캡처 (80번 포트)
sudo tcpdump -i eth0 port 80 -c 5 -nn
# 헥스 덤프로 헤더 구조 확인
sudo tcpdump -i eth0 port 80 -c 1 -XX
# 특정 호스트와의 통신만 캡처
sudo tcpdump -i eth0 host 93.184.216.34 -c 10
# pcap 파일로 저장 (나중에 Wireshark에서 열기)
sudo tcpdump -i eth0 -w capture.pcap -c 100tcpdump의 -XX 옵션으로 캡처한 패킷의 헥스 덤프를 보면, 캡슐화 구조를 바이트 단위로 확인할 수 있습니다.
단, 앞에서 언급한 것처럼 이더넷 FCS는 보통 캡처 결과에 나타나지 않습니다.
다음 범위는 VLAN 태그가 없는 Ethernet + 옵션 없는 IPv4/TCP를 가정합니다. 실제 헤더 길이는 필드를 읽어 결정해야 하며 HTTPS의 암호화된 바이트를 평문 HTTP로 해석할 수는 없습니다.
- 링크 헤더:
0x0000~0x000d, MAC 주소와 EtherType. - IPv4 헤더:
0x000e~0x0021, 버전·TTL·주소 등. - TCP 헤더:
0x0022~0x0035, 포트·순서 번호·플래그 등. - TCP 페이로드:
0x0036부터. 평문 HTTP 데이터가 있다면 이 구간에서 시작할 수 있습니다.
Wireshark에서는 각 계층을 클릭하면 해당 바이트가 하이라이트됩니다.
캡처된 프레임 안에서는 계층별 헤더와 페이로드가 어떤 바이트 범위를 차지하는지 확인할 수 있습니다.
Python으로 직접 패킷 파싱
아래는 직접 만든 바이트 배열에서 필드를 읽는 교육용 예제입니다. IP 전체 길이는 20바이트 IPv4 + 20바이트 TCP = 40바이트이며 TCP 페이로드는 없습니다. 체크섬은 계산하지 않았고 padding·FCS도 없으므로 실제 회선에 보낼 유효 프레임이 아닙니다.
파서는 EtherType·IP 버전·입력 길이·체크섬·단편화 등을 검증하지 않습니다. 특히 parse_ipv4는 Total Length로 페이로드 끝을 제한하지 않으므로 임의 캡처 파일의 안전한 일반 파서로 사용할 수 없습니다. 실제 네트워크 연결 없이 이 고정 배열의 필드 관계만 확인합니다.
"""이더넷 프레임을 바이트 단위로 파싱하는 예시"""
import struct
def parse_ethernet(raw_bytes):
"""이더넷 헤더 파싱 (14바이트)"""
dst_mac = raw_bytes[0:6]
src_mac = raw_bytes[6:12]
ether_type = struct.unpack("!H", raw_bytes[12:14])[0]
dst = ":".join(f"{b:02x}" for b in dst_mac)
src = ":".join(f"{b:02x}" for b in src_mac)
protocols = {0x0800: "IPv4", 0x86DD: "IPv6", 0x0806: "ARP"}
proto = protocols.get(ether_type, f"0x{ether_type:04x}")
print(f"[이더넷] {src} → {dst} ({proto})")
return raw_bytes[14:] # 페이로드 반환
def parse_ipv4(raw_bytes):
"""IPv4 헤더 파싱 (20바이트)"""
version_ihl = raw_bytes[0]
ihl = (version_ihl & 0x0F) * 4 # 헤더 길이
total_length = struct.unpack("!H", raw_bytes[2:4])[0]
ttl = raw_bytes[8]
protocol = raw_bytes[9]
src_ip = ".".join(str(b) for b in raw_bytes[12:16])
dst_ip = ".".join(str(b) for b in raw_bytes[16:20])
proto_names = {6: "TCP", 17: "UDP", 1: "ICMP"}
proto = proto_names.get(protocol, str(protocol))
print(f"[IPv4] {src_ip} → {dst_ip} (TTL={ttl}, {proto})")
print(f" 헤더: {ihl}B, 전체: {total_length}B")
return raw_bytes[ihl:] # 페이로드 반환
def parse_tcp(raw_bytes):
"""TCP 헤더 파싱 (20바이트~)"""
src_port = struct.unpack("!H", raw_bytes[0:2])[0]
dst_port = struct.unpack("!H", raw_bytes[2:4])[0]
seq_num = struct.unpack("!I", raw_bytes[4:8])[0]
ack_num = struct.unpack("!I", raw_bytes[8:12])[0]
data_offset = (raw_bytes[12] >> 4) * 4
flags = raw_bytes[13]
flag_str = []
if flags & 0x02: flag_str.append("SYN")
if flags & 0x10: flag_str.append("ACK")
if flags & 0x01: flag_str.append("FIN")
if flags & 0x04: flag_str.append("RST")
if flags & 0x08: flag_str.append("PSH")
print(f"[TCP] :{src_port} → :{dst_port}")
print(f" SEQ={seq_num}, ACK={ack_num}")
print(f" 플래그: {', '.join(flag_str)}")
return raw_bytes[data_offset:] # 페이로드 반환
# 예시: 가상 패킷 파싱
sample_frame = bytes([
# 이더넷 헤더 (14B)
0xff,0xff,0xff,0xff,0xff,0xff, # 목적지 MAC
0x00,0x1a,0x2b,0x3c,0x4d,0x5e, # 출발지 MAC
0x08,0x00, # IPv4
# IPv4 헤더 (20B)
0x45,0x00,0x00,0x28,0x00,0x00,0x40,0x00,
0x40,0x06,0x00,0x00, # TTL=64, TCP
0xc0,0xa8,0x01,0x64, # 192.168.1.100
0x5d,0xb8,0xd8,0x22, # 93.184.216.34
# TCP 헤더 (20B)
0xcc,0x95,0x00,0x50, # 포트 52373 → 80
0x00,0x00,0x00,0x01, # SEQ=1
0x00,0x00,0x00,0x00, # ACK=0
0x50,0x02,0xff,0xff,0x00,0x00,0x00,0x00, # SYN
])
ip_data = parse_ethernet(sample_frame)
tcp_data = parse_ipv4(ip_data)
payload = parse_tcp(tcp_data)
print(f"\n페이로드: {len(payload)} bytes")가상 배열의 위치를 따라가기: sample_frame은 Ethernet 14 + IPv4 20 + TCP 20 = 54바이트입니다. Ethernet 뒤인 offset 14의 0x45에서 IHL의 하위 4비트 5를 읽으면 IP 헤더는 5 × 4 = 20바이트입니다. 따라서 TCP는 offset 34에서 시작하며, 그 TCP 헤더의 offset 12에 있는 0x50의 상위 4비트 5도 5 × 4 = 20바이트를 뜻합니다. 페이로드 시작은 offset 54이고 배열 길이도 54라서 출력은 0바이트입니다. SYN 플래그와 SEQ=1이 있다고 HTTP 요청 본문까지 들어 있는 것은 아닙니다.
직접 확인하기: sample_frame의 맨 뒤에 바이트 하나를 붙이고 IP Total Length 0x0028은 그대로 둔다면, 현재 파서는 몇 바이트를 TCP 페이로드로 돌려줄까요? 그 결과를 실제 IP 패킷의 유효한 TCP 데이터라고 판단할 수 있는지도 설명하세요.
파서 반환값과 패킷 경계 대조
현재 파서는 Total Length로 끝을 자르지 않으므로 추가한 1바이트를 페이로드로 돌려줍니다. 하지만 IP 전체 길이 40은 IP 헤더 20과 TCP 헤더 20까지만 포함하므로 그 뒤의 바이트는 이 IP 패킷의 TCP 데이터라고 확정할 수 없습니다. 실제 일반 파서라면 입력 길이·IP 전체 길이·헤더 길이·단편화·체크섬 등의 조건을 검사해야 합니다. 여기서는 원 파서의 한계를 고정 배열에서 확인한 것이며 캡처나 회선 전송을 하지 않습니다.
실무 트러블슈팅에서의 계층별 접근
패킷 캡처에서 문제를 진단할 때, 각 계층을 순서대로 확인합니다.
| 계층 | 확인할 정보·도구와 해석 |
|---|---|
| L2 | MAC·EtherType, tcpdump -e. 브로드캐스트 증가와 ARP 문제를 확인합니다. |
| L3 | IP·TTL, ping·traceroute. TTL 소진은 루프 외에 긴 경로 등도 가능하며 ICMP 도달 불가의 코드·출처를 함께 봅니다. |
| L4 | 포트·TCP 플래그, ss·netstat. SYN만 보일 때는 서버·경로·반환 경로·필터링 등 여러 원인이 가능합니다. |
| L7 | HTTP 상태·DNS 응답, curl·dig. 503·NXDOMAIN의 의미와 해당 서비스 상태를 확인합니다. |
다음 Linux 명령들은 계층별 조회 예시입니다. 고정 IP와 example.com이 같은 목적지라는 전제는 없으므로 서로의 성공·실패를 같은 연결의 증거로 결합하지 않습니다.
# L2: ARP 테이블에서 게이트웨이 MAC 확인
arp -n | grep "$(ip route | grep default | awk '{print $3}')"
# L3: 목적지까지 경로 확인
traceroute -n 93.184.216.34
# L4: TCP 연결 상태 확인
ss -tn state established | head -20
# L7: HTTP 응답 확인
curl -I https://example.com 2>/dev/null | head -5다음 장에서는 이 계층 구조의 가장 아래, 물리적 세계에서 데이터가 어떻게 전달되는지를 살펴봅니다.