- 출처 / 참고: Linux man-pages (ip-link(8), ping(8)), RFC 1191 (Path MTU Discovery), RFC 4459 (MTU and Fragmentation Issues with In-the-Network Tunneling)
명령어:
ip link,ping,tracepath,iptables,tcpdump,ss
키워드: MTU Mismatch, Packet Drop, PMTUD, Black Hole Router, MSS Clamping, DF Flag, Fragmentation
사용처: VPN(OpenVPN, WireGuard, IPsec) 또는 GRE/IPIP 터널 연결 후 대용량 데이터 전송 지연 및 연결 멈춤 현상(Stall) 해결
실행예제
VPN 또는 터널 인터페이스 환경에서 소형 패킷(ping, ssh 로그인)은 정상 작동하지만, 대용량 파일 전송(scp), 웹 브라우징(HTTPS), 패키지 다운로드(apt/yum) 시 통신이 멈추는(Stall) 현상을 점검합니다.
# 1. 시스템의 모든 인터페이스 MTU 값 확인 (물리 인터페이스 vs 터널 인터페이스 비교)
$ ip link show
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
# 5: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
# 8: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN mode DEFAULT group default qlen 500
# -> tun0이 물리 인터페이스(1500)와 동일한 1500으로 잡혀 있다면 헤더 오버헤드로 인해 패킷 드롭 발생 가능성 높음
# 2. DF(Don't Fragment) 플래그를 설정하여 터널 경로상 최대 허용 패킷 크기(MTU) 측정
# 1472 bytes (데이터) + 20 bytes (IP 헤더) + 8 bytes (ICMP 헤더) = 1500 MTU 테스트
$ ping -M do -s 1472 10.8.0.1
PING 10.8.0.1 (10.8.0.1) 1472(1500) bytes of data.
ping: local error: Message too long, mtu=1420
# 3. Path MTU 탐색(PMTUD) 도구로 경로상 MTU 병목 지점 추적
$ tracepath 10.8.0.1
1?: [LOCALHOST] pmtu 1500
1: 10.8.0.1 15.210ms pmtu 1420
TooHashTable...
Resume: pmtu 1420
# 4. TCP 3-Way Handshake 시 협상되는 MSS 값 덤프 확인
$ sudo tcpdump -nn -i any "tcp[tcpflags] & (tcp-syn) != 0" and host 10.8.0.1
# [출력 예시] MSS 1460이 확인되는 경우 터널 헤더 오버헤드를 반영하지 못한 상태임
스크립트
터널 경로상에서 단편화(Fragmentation) 없이 통과할 수 있는 최적의 MTU 크기를 이진 탐색(Binary Search) 방식으로 자동 측정하고, 임시 또는 영구 적용 방안을 제시하는 쉘 스크립트입니다.
#!/bin/bash
# test_tunnel_mtu.sh - Find optimal MTU size through VPN/Tunnel and verify MSS clamping
TARGET_IP=${1:-"10.8.0.1"}
DEV_NAME=${2:-""}
echo "=========================================="
echo " [MTU 병목 진단 시작] 대상 IP: ${TARGET_IP}"
echo "=========================================="
# 1. 대상 IP 도달 가능 여부 기본 검사
if ! ping -c 1 -W 2 "$TARGET_IP" &>/dev/null; then
echo " [오류] 대상 IP($TARGET_IP)와 기본 ICMP 통신이 불가능합니다."
exit 1
fi
echo -e "\n[1] 단편화 금지(DF 플래그) 최적 패킷 크기 탐색 중..."
LOW=1200
HIGH=1500
OPTIMAL_PAYLOAD=1200
while [ "$LOW" -le "$HIGH" ]; do
MID=$(( (LOW + HIGH) / 2 ))
# ICMP 패킷 전송 (단편화 방지: -M do)
if ping -c 1 -W 1 -M do -s "$MID" "$TARGET_IP" &>/dev/null; then
OPTIMAL_PAYLOAD=$MID
LOW=$(( MID + 1 ))
else
HIGH=$(( MID - 1 ))
fi
done
TOTAL_MTU=$(( OPTIMAL_PAYLOAD + 28 ))
echo " -> 단편화 없이 도달 가능한 최대 ICMP 페이로드: ${OPTIMAL_PAYLOAD} bytes"
echo " -> 권장 경로 MTU (Payload + 28 byte 헤더): ${TOTAL_MTU} bytes"
# 2. 현재 인터페이스 설정값과 비교
if [ -n "$DEV_NAME" ]; then
echo -e "\n[2] 인터페이스 '${DEV_NAME}' 상태 비교:"
CURRENT_MTU=$(ip -o link show dev "$DEV_NAME" | awk '{print $5}')
echo " - 현재 인터페이스 MTU: ${CURRENT_MTU}"
echo " - 추천 설정 MTU : ${TOTAL_MTU}"
if [ "$CURRENT_MTU" -gt "$TOTAL_MTU" ]; then
echo " [경고] 인터페이스 MTU가 경로 허용치보다 큽니다! 패킷 드롭 발생 위험."
echo " -> 수정 명령어: sudo ip link set dev ${DEV_NAME} mtu ${TOTAL_MTU}"
else
echo " [정상] 인터페이스 MTU가 안전 범위 내에 설정되어 있습니다."
fi
fi
# 3. TCP MSS Clamping 규칙 적용 여부 검사
echo -e "\n[3] 방화벽 TCP-MSS 클램핑 규칙 검사:"
if command -v iptables &>/dev/null; then
MSS_RULES=$(sudo iptables -t mangle -S | grep -E "TCPMSS.*clamp-mss-to-pmtu")
if [ -n "$MSS_RULES" ]; then
echo " [정상] iptables mangle 테이블에 TCPMSS 클램핑 규칙이 활성화되어 있습니다:"
echo " $MSS_RULES"
else
echo " [주의] TCPMSS 클램핑 규칙이 발견되지 않았습니다."
echo " -> VPN 라우터 또는 방화벽에 아래 규칙 추가 권장:"
echo " iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu"
fi
fi
echo -e "\n=========================================="
echo " [진단 완료]"
echo "=========================================="
해설
VPN(OpenVPN, WireGuard, IPsec)이나 터널(GRE, VXLAN, IPIP) 인터페이스를 사용할 때 흔히 겪는 “SSH 텍스트는 잘 쳐지는데 대용량 파일(scp)이나 웹 접속(curl, 브라우저)만 시도하면 멈추는” 현상의 주원인은 MTU 불일치(MTU Mismatch)와 블랙홀 라우터(Black Hole Router) 문제입니다.
1. MTU 불일치 및 패킷 드롭 메커니즘
- 터널 오버헤드(Capsulation Overhead):
- 표준 이더넷의 기본 MTU는
1500 bytes입니다. - 패킷이 터널을 통과할 때 터널 프로토콜의 추가 헤더(IP 헤더, UDP 헤더, 암호화 헤더 등)가 덧붙여집니다.
- WireGuard: 60 ~ 80 bytes (권장 MTU:
1420) - OpenVPN: 40 ~ 100 bytes (권장 MTU:
1400~1450) - IPsec / GRE: 50 ~ 90 bytes (권장 MTU:
1400이하)
- WireGuard: 60 ~ 80 bytes (권장 MTU:
- 표준 이더넷의 기본 MTU는
- DF(Don’t Fragment) 플래그와 PMTUD 실패:
- 대부분의 현대 TCP 스택은 성능 향상을 위해 IP 패킷에
DF=1(단편화 금지) 플래그를 설정합니다. - 1500바이트 크기의 TCP 패킷이 터널 인터페이스로 들어오면 헤더가 추가되어 1500바이트를 초과(
1560 bytes)하게 됩니다. - 라우터는 패킷을 버리고(Drop), 송신자에게 ICMP Type 3, Code 4(Destination Unreachable, Fragmentation Needed and DF set) 패킷을 되돌려주어야 합니다(Path MTU Discovery).
- 그러나 중간 방화벽이나 클라우드 보안 그룹이 ICMP를 차단해버리면 송신자는 패킷이 왜 버려졌는지 알지 못한 채 계속 재전송만 시도하다가 타임아웃에 빠집니다(ICMP Black Hole).
- 대부분의 현대 TCP 스택은 성능 향상을 위해 IP 패킷에
2. 해결 방법
방법 1: 터널 인터페이스의 MTU 크기 하향 조정
터널 어댑터 자체의 MTU를 헤더 오버헤드를 감안하여 안전한 크기로 줄입니다.
# WireGuard 예시 (/etc/wireguard/wg0.conf)
[Interface]
MTU = 1420
# 수동 즉시 변경
sudo ip link set dev tun0 mtu 1400
방법 2: TCP MSS Clamping 적용 (가장 강력하고 권장되는 방법)
터널 게이트웨이 또는 서버에서 TCP 3-Way Handshake 과정의 SYN 패킷을 감시하여, MTU 크기에 맞게 MSS(Maximum Segment Size) 값을 자동으로 강제 축소(Clamping)합니다.
- iptables 사용 시:
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu - nftables 사용 시:
sudo nft add rule inet filter forward tcp flags syn tcp option maxseg size set rt mtu
주의사항
- ICMP Type 3(Fragmentation Needed) 차단 금지:
- “보안 강화” 목적으로 방화벽에서 모든 ICMP 패킷을 일괄
DROP하는 정책을 적용하면 PMTUD(경로 MTU 탐색)가 완전히 동작하지 않게 됩니다. ICMP Type 3(Destination Unreachable) 패킷은 네트워크 정상 동작을 위해 반드시 허용해야 합니다.
- “보안 강화” 목적으로 방화벽에서 모든 ICMP 패킷을 일괄
- UDP 기반 트래픽(DNS, VoIP, 영상 스트리밍) 주의:
- TCP MSS Clamping은 TCP 트래픽에만 적용됩니다. UDP 트래픽은 핸드셰이크가 없어 MSS를 조절할 수 없으므로, UDP 기반 대용량 통신(예: DNS EDNS0, QUIC, 비디오 스트림) 장애를 예방하려면 반드시 인터페이스 자체의 MTU(
ip link set mtu)를 적정값으로 낮춰야 합니다.
- TCP MSS Clamping은 TCP 트래픽에만 적용됩니다. UDP 트래픽은 핸드셰이크가 없어 MSS를 조절할 수 없으므로, UDP 기반 대용량 통신(예: DNS EDNS0, QUIC, 비디오 스트림) 장애를 예방하려면 반드시 인터페이스 자체의 MTU(
- 클라우드(AWS/GCP/Azure)의 점보 프레임(Jumbo Frame) 환경:
- VPC 내부 통신은 점보 프레임(
MTU 9001)을 지원하지만, 인터넷 게이트웨이(IGW)나 온프레미스 연동 VPN/DirectConnect 구간으로 나갈 때는 즉시1500또는 그 이하로 줄어듭니다. VPC 내부에서 생성된 대형 패킷이 터널을 타는 순간 드롭되지 않도록 주의해야 합니다.
- VPC 내부 통신은 점보 프레임(