본문 바로가기
TYLER SONGBlog
← Blog index
CS8 min read

Packet: 네트워크 계층에서 출발지와 목적지로 전달되는 데이터 단위

패킷(Packet)은 네트워크 계층(Network Layer)이 데이터를 전달할 때 사용하는 단위다. IP 패킷은 헤더와 페이로드로 구성되며, 라우터는 목적지 IP 주소와 라우팅 테이블을 기준으로 다음 홉(Next Hop)을 결정한다. 링크의 최대 전송 단위(MTU)를 넘는 패킷은 분할되거나 폐기될 수 있으므로 크기와 경로 MTU를 함께 고려해야 한다.

By 송민성

1. 개념

패킷(Packet)은 네트워크 계층에서 전달되는 데이터 단위다. 인터넷 프로토콜(IP, Internet Protocol)은 패킷에 출발지 IP 주소, 목적지 IP 주소, 수명(TTL) 같은 제어 정보를 붙인다.

IPv4 패킷의 기본 구조는 다음과 같다.

text
[IPv4 헤더][페이로드]

IPv4 헤더는 옵션이 없으면 20바이트다. 페이로드에는 일반적으로 전송 계층(Transport Layer)의 TCP 세그먼트(TCP Segment), UDP 데이터그램(UDP Datagram), ICMP 메시지가 들어간다.

계층별 데이터 단위는 구분해서 사용한다.

계층데이터 단위주요 주소
응용 계층(Application Layer)메시지(Message)URL, 도메인 등
전송 계층(Transport Layer)TCP 세그먼트, UDP 데이터그램포트 번호
네트워크 계층(Network Layer)IP 패킷IP 주소
링크 계층(Link Layer)프레임(Frame)MAC 주소

일상적인 설명에서는 이들을 모두 패킷이라고 부르기도 한다. 프로토콜 동작을 분석할 때는 계층에 맞는 이름을 구분해야 한다.

2. 왜 사용하는가

패킷 교환(Packet Switching)은 데이터를 작은 단위로 나누어 여러 통신 흐름이 네트워크 자원을 공유하게 한다.

하나의 전용 회선을 통신이 끝날 때까지 점유하지 않는다. 각 라우터는 도착한 패킷을 독립적으로 처리하고, 목적지 주소에 맞는 다음 경로로 전달한다.

패킷 단위 전송은 다음 기능을 가능하게 한다.

  • 여러 사용자의 데이터를 같은 링크에서 교대로 전달
  • 라우팅 테이블을 이용한 목적지별 경로 선택
  • 혼잡이나 장애에 따른 경로 변경
  • 손실된 데이터만 전송 계층에서 재전송
  • 방화벽과 라우터에서 주소, 프로토콜, 포트 기준으로 트래픽 처리

IP 자체는 최선형 전달(Best-Effort Delivery)을 제공한다. 패킷의 도착, 순서, 중복 방지를 보장하지 않는다. TCP 같은 상위 프로토콜이 신뢰성 기능을 추가한다.

3. 동작 원리

캡슐화

웹 클라이언트가 TCP로 데이터를 보낼 때 각 계층은 자신의 헤더를 추가한다.

text
응용 데이터
↓
[TCP 헤더][응용 데이터]
↓
[IP 헤더][TCP 헤더][응용 데이터]
↓
[이더넷 헤더][IP 헤더][TCP 헤더][응용 데이터][FCS]

수신 측은 반대 순서로 헤더를 해석한다. 이 과정을 역캡슐화(Decapsulation)라고 한다.

라우팅

라우터는 패킷의 목적지 IP 주소를 라우팅 테이블과 비교한다. 여러 경로가 일치하면 가장 긴 접두사 일치(Longest Prefix Match)를 선택한다.

text
목적지: 192.168.10.25

192.168.0.0/16  → 라우터 A
192.168.10.0/24 → 라우터 B

192.168.10.0/24가 더 긴 접두사와 일치하므로 라우터 B가 선택된다.

라우터를 통과할 때 IP 패킷은 새 링크 계층 프레임에 담긴다. 출발지와 목적지 MAC 주소는 홉(Hop)마다 바뀌지만, 일반적인 라우팅에서는 종단의 출발지와 목적지 IP 주소가 유지된다. 네트워크 주소 변환(NAT, Network Address Translation)이 적용되면 IP 주소나 포트가 바뀔 수 있다.

수명

IPv4의 수명(TTL, Time To Live) 필드는 라우터를 통과할 때마다 최소 1씩 감소한다. 값이 0이 되면 라우터는 패킷을 폐기한다. 이 동작은 잘못된 라우팅으로 패킷이 계속 순환하는 상황을 제한한다.

IPv6에서는 같은 역할의 필드를 홉 제한(Hop Limit)이라고 부른다.

MTU와 단편화

최대 전송 단위(MTU, Maximum Transmission Unit)는 링크가 한 프레임에 실을 수 있는 네트워크 계층 패킷의 최대 크기다. 일반적인 이더넷 MTU는 1,500바이트지만 네트워크 설정과 터널 사용 여부에 따라 달라진다.

IPv4 패킷이 다음 링크의 MTU보다 크면 다음 상황이 발생한다.

  • 단편화 허용: 라우터나 송신 호스트가 패킷을 여러 조각으로 나눌 수 있음
  • 단편화 금지(DF 비트 설정): 패킷을 폐기하고 ICMP 오류를 반환
  • 경로 MTU 탐색(Path MTU Discovery): 송신 측이 경로에서 전송 가능한 크기에 맞춤

IPv6 라우터는 패킷을 단편화하지 않는다. 너무 큰 패킷을 받으면 ICMPv6 Packet Too Big 메시지를 보내며, 필요한 단편화는 송신 호스트가 수행한다.

4. 코드 예제

다음 코드는 옵션 없는 IPv4 헤더를 만들고 다시 파싱한다. 실제 네트워크로 전송하지 않으므로 관리자 권한 없이 실행할 수 있다.

python
import ipaddress import struct def internet_checksum(data: bytes) -> int: if len(data) % 2 == 1: data += b"\x00" total = 0 for offset in range(0, len(data), 2): word = (data[offset] << 8) | data[offset + 1] total += word total = (total & 0xFFFF) + (total >> 16) return (~total) & 0xFFFF def build_ipv4_packet( source: str, destination: str, payload: bytes, protocol: int = 17, ttl: int = 64, ) -> bytes: version = 4 ihl = 5 version_and_ihl = (version << 4) | ihl dscp_and_ecn = 0 total_length = 20 + len(payload) if total_length > 65_535: raise ValueError("IPv4 패킷 전체 길이는 65,535바이트를 넘을 수 없습니다.") identification = 0x1234 flags_and_fragment_offset = 0 header_checksum = 0 source_bytes = ipaddress.IPv4Address(source).packed destination_bytes = ipaddress.IPv4Address(destination).packed header_without_checksum = struct.pack( "!BBHHHBBH4s4s", version_and_ihl, dscp_and_ecn, total_length, identification, flags_and_fragment_offset, ttl, protocol, header_checksum, source_bytes, destination_bytes, ) header_checksum = internet_checksum(header_without_checksum) header = struct.pack( "!BBHHHBBH4s4s", version_and_ihl, dscp_and_ecn, total_length, identification, flags_and_fragment_offset, ttl, protocol, header_checksum, source_bytes, destination_bytes, ) return header + payload def parse_ipv4_packet(packet: bytes) -> dict[str, object]: if len(packet) < 20: raise ValueError("IPv4 기본 헤더보다 짧은 데이터입니다.") ( version_and_ihl, dscp_and_ecn, total_length, identification, flags_and_fragment_offset, ttl, protocol, header_checksum, source_bytes, destination_bytes, ) = struct.unpack("!BBHHHBBH4s4s", packet[:20]) version = version_and_ihl >> 4 header_length = (version_and_ihl & 0x0F) * 4 if version != 4: raise ValueError("IPv4 패킷이 아닙니다.") if header_length < 20 or len(packet) < header_length: raise ValueError("IPv4 헤더 길이가 올바르지 않습니다.") if total_length < header_length or total_length > len(packet): raise ValueError("IPv4 전체 길이가 올바르지 않습니다.") header = packet[:header_length] return { "version": version, "header_length": header_length, "total_length": total_length, "identification": identification, "flags": flags_and_fragment_offset >> 13, "fragment_offset": flags_and_fragment_offset & 0x1FFF, "ttl": ttl, "protocol": protocol, "checksum": header_checksum, "checksum_valid": internet_checksum(header) == 0, "source": str(ipaddress.IPv4Address(source_bytes)), "destination": str(ipaddress.IPv4Address(destination_bytes)), "dscp_and_ecn": dscp_and_ecn, "payload": packet[header_length:total_length], } def main() -> None: packet = build_ipv4_packet( source="192.0.2.10", destination="198.51.100.20", payload=b"hello packet", ) parsed = parse_ipv4_packet(packet) print(f"패킷 크기: {len(packet)}바이트") print(f"출발지: {parsed['source']}") print(f"목적지: {parsed['destination']}") print(f"TTL: {parsed['ttl']}") print(f"프로토콜 번호: {parsed['protocol']}") print(f"체크섬 유효: {parsed['checksum_valid']}") print(f"페이로드: {parsed['payload']!r}") if __name__ == "__main__": main()

프로토콜 번호 17은 UDP를 뜻한다. TCP는 6, ICMP는 1을 사용한다.

IPv4 헤더 체크섬은 헤더만 검사한다. 라우터가 TTL을 변경하면 체크섬도 갱신해야 한다. IPv6 기본 헤더에는 헤더 체크섬이 없다.

5. 시간 복잡도 또는 성능 특성

패킷 처리 비용은 수행하는 작업에 따라 달라진다.

  • 길이 n인 데이터를 체크섬으로 검사: 시간복잡도 O(n)
  • 패킷 헤더의 고정 필드 읽기: O(1)
  • 일반적인 라우팅 테이블 조회: 자료구조와 구현에 따라 달라짐
  • 패킷 복사: 복사하는 바이트 수가 n이면 O(n)

작은 패킷은 헤더 비율이 커진다. IPv4와 TCP가 각각 옵션 없는 20바이트 헤더를 사용하면 응용 데이터 전에 최소 40바이트가 추가된다. 이더넷 MTU가 1,500바이트인 경로에서는 TCP 옵션이 없을 때 최대 세그먼트 크기(MSS, Maximum Segment Size)가 보통 1,460바이트다.

패킷이 지나치게 크면 단편화 가능성이 커진다. 지나치게 작으면 같은 데이터를 보내기 위해 처리해야 할 패킷 수와 헤더 오버헤드가 증가한다.

6. 실무 사용 사례

웹 요청 분석

브라우저의 HTTP 요청은 TCP 또는 QUIC을 통해 전송된다. TCP를 사용하는 경우 응용 데이터가 TCP 세그먼트와 IP 패킷으로 캡슐화된다. QUIC은 UDP 데이터그램 위에서 동작한다.

패킷 캡처 도구를 사용하면 다음 정보를 확인할 수 있다.

shell
sudo tcpdump -n -i any 'host 198.51.100.20'

-n은 주소와 포트를 이름으로 변환하지 않는다. 운영 환경에서는 캡처 파일에 인증 정보나 개인정보가 포함될 수 있으므로 저장과 공유 범위를 제한해야 한다.

경로 확인

traceroute는 TTL 또는 홉 제한을 점차 증가시키며 중간 라우터의 응답을 관찰한다.

shell
traceroute example.com

라우터나 방화벽이 관련 ICMP 응답을 차단하면 일부 홉이 표시되지 않을 수 있다.

MTU 문제 진단

터널, 가상 사설망(VPN), 오버레이 네트워크는 추가 헤더를 붙인다. 실제로 사용할 수 있는 페이로드 크기가 줄어들어 특정 크기 이상의 요청만 실패하는 현상이 발생할 수 있다.

리눅스에서는 IPv4 패킷에 단편화 금지를 설정하고 크기를 바꾸어 확인할 수 있다.

shell
ping -M do -s 1472 192.0.2.1

IPv4 헤더 20바이트와 ICMP 헤더 8바이트를 더하면 총 1,500바이트다. 경로와 운영체제에 따라 명령 옵션과 결과가 달라질 수 있다.

7. 주의할 점

패킷과 메시지를 일대일로 대응하지 않기

하나의 HTTP 요청이 하나의 IP 패킷에 들어간다는 보장은 없다. 큰 요청은 여러 TCP 세그먼트로 나뉠 수 있고, TCP 수신 측에서는 여러 전송 결과가 하나의 읽기 작업으로 합쳐질 수도 있다.

TCP는 메시지 경계를 보존하지 않는 바이트 스트림(Byte Stream)이다. 애플리케이션 프로토콜이 길이 필드나 구분자를 사용해 메시지 경계를 정의해야 한다.

IP 체크섬을 전체 데이터 검증으로 오해하지 않기

IPv4 헤더 체크섬은 페이로드를 보호하지 않는다. TCP와 UDP는 별도의 체크섬을 사용한다. 데이터 무결성과 위변조 방지가 필요하면 전송 계층 체크섬만 의존하지 말고 TLS 같은 암호화 프로토콜을 사용해야 한다.

단편화에 의존하지 않기

단편 하나가 손실되면 원래 IPv4 패킷 전체를 재조립할 수 없다. 방화벽과 NAT 장비가 단편을 다르게 처리할 수도 있다. 송신 측에서 경로 MTU에 맞는 크기를 사용하는 편이 안정적이다.

패킷 캡처 결과를 애플리케이션 전송 횟수로 해석하지 않기

체크섬 오프로딩(Checksum Offloading), TCP 세그먼테이션 오프로딩(TSO), 일반 수신 오프로딩(GRO) 때문에 호스트에서 캡처한 형태와 실제 링크에서 전송된 형태가 다를 수 있다.

8. 핵심 정리

  • 패킷은 네트워크 계층의 데이터 단위이며 IP 헤더와 페이로드로 구성된다.
  • 라우터는 목적지 IP 주소와 라우팅 테이블을 이용해 다음 홉을 선택한다.
  • IP는 도착 순서와 전달 성공을 보장하지 않는 최선형 전달 방식을 사용한다.
  • TTL 또는 홉 제한은 라우팅 루프로 인한 무한 전달을 막는다.
  • MTU를 넘는 패킷은 단편화되거나 폐기될 수 있으므로 경로 MTU를 고려해야 한다.
  • 패킷, TCP 세그먼트, UDP 데이터그램, 이더넷 프레임은 서로 다른 계층의 데이터 단위다.