📚 네트워크 · 패킷 분석 › 03. Wireshark 패킷 분석 — 115편
이전 글: 114. ARP 패킷 필드 분석 — opcode와 Sender·Target 필드로 IP-MAC 매핑 검증하기 · 다음 글: 116. IPv6 Packet 분석
참고(리눅스 시스템 기초): IP / TCP / UDP / Port — IP 주소와 포트의 기본 개념
방화벽·IDS 로그에 남는 IP 정보는 대부분 출발지·목적지 주소와 프로토콜 정도입니다. 그러나 pcap에는 IPv4 헤더의 모든 필드가 남아 있고, 이 필드들은 로그만으로는 답할 수 없는 질문에 단서를 줍니다.
ip.ttl)ip.flags.mf, ip.frag_offset, ip.id)TTL과 라우팅의 개념은 17. 게이트웨이와 라우팅 기초 — 다른 네트워크로 패킷은 어떻게 가는가에서, MTU와 단편화의 개념은 28. MTU와 IP 단편화 — 큰 패킷이 쪼개질 때에서 다룹니다. 이 글은 그 개념이 실제 패킷의 어떤 필드에 어떤 값으로 나타나는지에 집중합니다.

그림 1. IPv4 헤더 필드와 Wireshark 필드명
| 헤더 항목 | 크기 | Wireshark 필드 | 분석 시 의미 |
|---|---|---|---|
| Version | 4비트 | ip.version | 4 (IPv4) |
| IHL (헤더 길이) | 4비트 | ip.hdr_len | 바이트로 표시. 옵션이 없으면 20 |
| DS Field | 8비트 | ip.dsfield (ip.dsfield.dscp, ip.dsfield.ecn) | QoS 표시, 혼잡 알림 |
| Total Length | 16비트 | ip.len | IP 헤더 + 데이터 전체 길이 |
| Identification | 16비트 | ip.id | 같은 원본 패킷의 조각을 묶는 번호 |
| Flags | 3비트 | ip.flags (ip.flags.rb, ip.flags.df, ip.flags.mf) | 예약 비트 / 단편화 금지 / 뒤에 조각 더 있음 |
| Fragment Offset | 13비트 | ip.frag_offset | 원본 데이터에서 이 조각의 위치 |
| TTL | 8비트 | ip.ttl | 라우터를 지날 때마다 1씩 감소 |
| Protocol | 8비트 | ip.proto | 1 ICMP, 6 TCP, 17 UDP |
| Header Checksum | 16비트 | ip.checksum (ip.checksum.status) | 헤더 오류 검출값과 검증 결과 |
| Source / Destination | 각 32비트 | ip.src, ip.dst, ip.addr | 출발지·목적지 주소 |
| 상황 | ip.flags.df | ip.flags.mf | ip.frag_offset |
|---|---|---|---|
| 단편화되지 않은 일반 패킷 (DF 설정) | 1 | 0 | 0 |
| 단편화되지 않은 일반 패킷 (DF 미설정) | 0 | 0 | 0 |
| 첫 번째 조각 | 0 | 1 | 0 |
| 중간 조각 | 0 | 1 | 0보다 큼 |
| 마지막 조각 | 0 | 0 | 0보다 큼 |
ip.frag_offset에는 버전별 표시 차이가 있어 주의가 필요합니다. 헤더에 실제로 기록되는 값은 8바이트 단위입니다. Wireshark 4.2 기준으로 상세 트리에는 바이트로 환산한 값(예: Fragment Offset: 1480)이 표시되지만, 필터와 -T fields 출력에는 8바이트 단위 원시값(185)이 사용됩니다. 그래서 ip.frag_offset == 1480은 아무것도 찾지 못하고 ip.frag_offset == 185가 해당 조각을 찾습니다. 다른 버전에서는 표시 방식이 다를 수 있으므로, 필터를 쓰기 전에 -T fields 출력으로 단위를 먼저 확인하는 습관이 안전합니다.
TTL 초기값은 OS·장비마다 다르며, 일반적으로 Linux·macOS는 64, Windows는 128, 네트워크 장비는 255를 쓰는 경우가 많습니다. 수신 측에서 본 ip.ttl이 57이라면 "초기값 64에서 7홉을 거쳐 왔을 가능성"을 추정할 수 있습니다. 단, 초기값은 설정으로 바꿀 수 있으므로 OS를 확정하는 근거가 아니라 일관성을 비교하는 기준으로만 씁니다.
ip.checksum.status | 의미 | 상세 트리 표시 |
|---|---|---|
| 2 | 검증하지 않음 (기본값) | [validation disabled], Unverified |
| 1 | 검증 결과 정상 | Good |
| 0 | 검증 결과 불일치 | incorrect, should be 0x.... |
Wireshark는 기본적으로 IP 체크섬을 검증하지 않습니다(ip.check_checksum: FALSE). 이유는 3절의 체크섬 오프로딩 때문입니다.
원본: ICMP 데이터 3008바이트, MTU 1500 구간 통과
↓ (출발지 또는 라우터에서 단편화)
조각 1: ip.id=0x7777 mf=1 frag_offset=0 ip.len=1500 → "Fragmented IP protocol"
조각 2: ip.id=0x7777 mf=1 frag_offset=185 ip.len=1500 → "Fragmented IP protocol"
조각 3: ip.id=0x7777 mf=0 frag_offset=370 ip.len=68 → 마지막 조각
↓ (Wireshark: 같은 출발지·목적지·프로토콜·ip.id 조각을 모음)
조각 3 프레임에 [3 IPv4 Fragments (3008 bytes): #10(1480), #11(1480), #12(48)] 표시
↓
재조립된 ICMP가 조각 3 프레임에서 해석됨
그래서 단편화된 트래픽은 마지막 조각 프레임에서만 상위 프로토콜(ICMP, UDP 등)로 표시되고, 앞선 조각은 IPv4로만 보입니다. 앞 조각을 필터로 찾으려면 icmp가 아니라 ip.flags.mf == 1 || ip.frag_offset > 0을 써야 합니다.
[송신 호스트 내부]
애플리케이션 → 커널 TCP/IP 스택 → (캡처 지점: tshark/libpcap) → NIC 드라이버 → NIC 하드웨어
↑ ↑
여기서는 체크섬이 아직 비어 있을 수 있음 여기서 체크섬 계산 (오프로드)
NIC가 체크섬 계산을 대신하도록 설정되어 있으면, 호스트가 자신이 보내는 패킷을 캡처할 때 체크섬 칸이 비어 있거나 틀린 값으로 보입니다. Linux에서는 IPv4 헤더 체크섬을 커널이 직접 계산하는 경우가 많고 오프로드는 주로 TCP·UDP 체크섬에 적용되지만, OS와 NIC 설정(예: Windows NIC의 IPv4 체크섬 오프로드 옵션)에 따라 IP 헤더 체크섬도 영향을 받을 수 있습니다. 검증을 켜면 Wireshark도 may be caused by "IP checksum offload"?라는 안내를 함께 표시합니다.
결론적으로 송신 측 호스트에서 캡처한 자기 패킷의 체크섬 오류는 대부분 증거 가치가 없습니다. 미러링 포트나 다른 장비에서 캡처한 패킷에서 오류가 보일 때 의미가 있습니다.
실습 환경: 본인 소유 Linux VM (Rocky Linux 또는 Ubuntu). 인터페이스 이름은 ens33을 예시로 사용합니다. tshark 설치는 112편과 같습니다(Rocky: sudo dnf install -y wireshark-cli, Ubuntu: sudo apt install -y tshark).
# 터미널 1: ICMP만 캡처 (단편화 조각도 포함되도록 ip proto 기준)
sudo tshark -i ens33 -f "icmp" -w /tmp/pcap09-ip.pcapng
# 터미널 2
ping -c 3 <게이트웨이 IP> # 일반 패킷
ping -c 1 -s 3000 <게이트웨이 IP> # MTU보다 큰 데이터 → 단편화 발생
ping -c 1 -M do -s 1472 <게이트웨이 IP> # DF 설정 강제 (1472+8+20 = 1500)
Capture Filter icmp는 첫 조각 뒤의 조각도 IP 프로토콜 번호(1)로 매칭되므로 함께 캡처됩니다. ping -M do는 iputils ping의 옵션으로, DF 비트를 강제로 설정합니다.
📷 [실습 화면 삽입]
ping -s 3000실행 후 Wireshark 목록에 "Fragmented IP protocol"이 보이는 화면
F=/tmp/pcap09-ip.pcapng
# (1) 주요 IP 헤더 필드
tshark -r $F -T fields -E header=y -E occurrence=f \
-e frame.number -e ip.src -e ip.dst -e ip.hdr_len -e ip.len \
-e ip.id -e ip.flags.df -e ip.flags.mf -e ip.frag_offset -e ip.ttl -e ip.proto
# (2) 단편화 조각만
tshark -r $F -Y "ip.flags.mf == 1 || ip.frag_offset > 0" -T fields \
-e frame.number -e ip.id -e ip.flags.mf -e ip.frag_offset -e ip.len
# (3) 출발지별 TTL 분포 (같은 IP에 TTL이 여러 개인지)
tshark -r $F -T fields -E occurrence=f -e ip.src -e ip.ttl | sort | uniq -c
# (4) 체크섬 검증을 켜고 불일치 패킷 찾기
tshark -r $F -o ip.check_checksum:TRUE -Y "ip.checksum.status == 0" \
-T fields -e frame.number -e ip.src -e ip.checksum
# Rocky: sudo dnf install -y ethtool / Ubuntu: sudo apt install -y ethtool
ethtool -k ens33 | grep -i checksum
tx-checksumming: on 이면 송신 패킷의 일부 체크섬이 NIC에서 계산된다는 뜻입니다. 설정은 바꾸지 않고 해석할 때 참고만 합니다.
📷 [실습 화면 삽입] Wireshark 상세 창의 Internet Protocol Version 4 트리 (Flags, Fragment Offset, TTL, Header Checksum 부분)
아래는 형식 예시(값은 환경마다 다름) 입니다.
frame ip.src ip.dst hdr_len len ip.id df mf frag_offset ttl proto
1 192.168.10.20 192.168.10.1 20 84 0x1a2b True False 0 64 1
2 192.168.10.1 192.168.10.20 20 84 0x3c4d False False 0 64 1
5 192.168.10.20 192.168.10.1 20 1500 0x7777 False True 0 64 1
6 192.168.10.20 192.168.10.1 20 1500 0x7777 False True 185 64 1
7 192.168.10.20 192.168.10.1 20 68 0x7777 False False 370 64 1
ip.id(0x7777)를 공유하는 세 조각. 마지막 조각만 mf=Falsefrag_offset 185 × 8 = 1480바이트 위치에서 두 번째 조각이 시작확인 체크리스트
ip.hdr_len이 20인 것(옵션 없음)을 확인했다ip.id를 공유하는 것을 확인했다ip.flags.mf가 0인 것을 확인했다ip.frag_offset 값의 단위(8바이트)를 트리 표시와 비교해 확인했다ip.ttl이 일관적인지 확인했다| 관찰 | 정상일 수 있는 경우 | 의심해야 하는 경우 |
|---|---|---|
같은 출발지 IP의 ip.ttl이 두 가지 이상 | NAT 뒤의 여러 OS, 경로 변경, 로드밸런서 | 같은 세션 안에서 TTL이 튐 (위조·삽입 패킷 가능성) |
매우 작은 ip.ttl (예: 1~3) | traceroute, 멀리서 온 패킷 | 내부 서버로 향하는 일반 트래픽인데 TTL이 비정상적으로 작음 |
| 단편화 조각 존재 | 큰 UDP(DNS 응답 등), 터널 구간의 MTU 차이 | 매우 작은 조각, 겹치는 조각(ip.fragment.overlap), 끝나지 않는 조각 |
ip.flags.rb == 1 | (일반적으로 정상 트래픽에서 사용되지 않음) | 예약 비트가 설정된 패킷 → 조작된 패킷 가능성 |
ip.hdr_len > 20 (IP 옵션) | 일부 특수 프로토콜 | 일반 트래픽에서 IP 옵션 출현 |
ip.checksum.status == 0 | 송신 호스트 캡처의 오프로드 | 미러링 구간 캡처에서 반복되는 체크섬 오류 |
확인 명령 예시입니다.
# 같은 출발지에서 TTL 값이 2개 이상인 IP
tshark -r $F -T fields -E occurrence=f -e ip.src -e ip.ttl | sort -u \
| awk '{c[$1]++; t[$1]=t[$1]" "$2} END {for (i in c) if (c[i]>1) print i, t[i]}'
# 겹치는 조각, 예약 비트, IP 옵션
tshark -r $F -Y "ip.fragment.overlap || ip.flags.rb == 1 || ip.hdr_len > 20"
# TTL이 작은 패킷 (ip.ttl#1 = 바깥쪽 첫 번째 IP 헤더의 TTL)
tshark -r $F -Y "ip.ttl#1 < 5" -T fields -E occurrence=f \
-e frame.number -e ip.src -e ip.dst -e ip.ttl
ip.ttl < 5로만 쓰면 TTL 초과 ICMP 메시지 안에 들어 있는 원본 헤더의 TTL(1)까지 조건에 걸려 엉뚱한 패킷이 검색됩니다. #1은 몇 번째 계층의 필드인지 지정하는 레이어 연산자로, Wireshark 4.2 기준으로 지원됩니다. 구버전에서 지원되지 않으면 !icmp && ip.ttl < 5처럼 ICMP를 따로 분리해 확인합니다.
TTL을 비교할 때는 같은 방향·같은 세션끼리 비교해야 합니다. 서로 다른 서버에서 온 패킷의 TTL이 다른 것은 당연하기 때문입니다. 또한 ICMP 오류 메시지 안에는 원본 IP 헤더가 한 번 더 들어 있으므로(117편에서 다룸), -E occurrence=f로 바깥 헤더 값만 뽑는 것이 안전합니다.
흔적이 남는 곳
한계와 오탐 주의
ip.id 생성 방식은 OS마다 다르고(증가, 무작위, 0 고정 등), 버전에 따라 달라지기도 합니다. ID 패턴으로 결론을 내리지 않고, 조각을 묶는 용도로 주로 씁니다.ip.ttl, ip.id, ip.flags.df·ip.flags.mf, ip.frag_offset, ip.proto, ip.checksum입니다.ip.id를 공유하고, 마지막 조각만 mf=0이며, Wireshark는 마지막 조각 프레임에서 재조립 결과를 보여 줍니다.ip.frag_offset은 8바이트 단위 값이며, 트리 표시(바이트)와 필터 값의 단위가 다를 수 있으니 -T fields로 확인합니다.