페이지는 열리고 ping은 성공하는데 파일 복사는 매번 같은 지점에서 멈춘다. 이 패턴은 대역폭이나 자격 증명이 아니라 패킷 크기를 가리킨다. 캡슐화는 모든 패킷에 바이트를 더하므로, 원래 경로에 들어갔던 페이로드도 터널을 지나면 한도를 넘을 수 있다. 이 대화에서 서로 다른 세 숫자가 MTU라고 불리며, 이를 혼동하면 한 애플리케이션은 고치고 다른 하나는 망가뜨리는 설정이 된다.

사람들이 MTU라고 부르는 세 숫자

인터페이스 MTU는 그 링크에서 패킷에 설정된 한도다. 경로 MTU는 경로를 따라 가장 작은 한도이며, 설정하는 것이 아니라 발견하는 것이다. TCP MSS는 상대가 받을 수 있다고 알리는 페이로드 크기이며, 나머지 둘에서 유도되지 별도의 물리적 한도가 아니다.

터널에는 안쪽과 바깥쪽이라는 두 가지 관점이 있다. 바깥 패킷은 캡슐화된 패킷에 프로토콜이 필요로 하는 헤더를 더해 나르므로, 쓸 수 있는 안쪽 크기는 바깥 경로가 허용하는 것보다 작다. VPN 서버까지의 경로도 서버에서 목적지까지의 경로와 다르다.

경로 MTU 탐색은 송신자가 동작하는 크기를 알게 해 주지만, 피드백이 도착하는지에 달려 있다. 중간 장비가 해당 ICMP 메시지를 걸러 내면 송신자는 끝나지 않는 패킷을 계속 보내게 되고, 이것이 전형적인 블랙홀이다. 증상은 알고 나면 뚜렷한데, 핸드셰이크는 성공하고 처음의 최대 크기 페이로드가 사라지며 결국 연결이 재설정되거나 멈춘다. 애플리케이션 로그에는 네트워크 문제처럼 보이는 것이 없다.

직접 통제하는 페이로드로 경로를 시험한다

시험은 하나의 경로와 하나의 크기에 대한 증거이지 권장 설정이 아니다. 응답하는 호스트를 쓰고, 페이로드 크기만 바꾼다.

ping -M do -s 1372 -c 2 203.0.113.20
ping -4 -f -l 1372 vpn.example

첫 명령은 단편화를 끄고 1372바이트 페이로드로 리눅스 시험을 보내고, 둘째는 페이로드를 -l로 지정하는 윈도우에서 같은 일을 한다. 응답이 오면 그 크기는 경로를 통과한 것이다. 단편화가 필요하다는 메시지는 정보를 주지만, 타임아웃은 호스트나 중간 장비가 ICMP를 그냥 버릴 수 있어 모호하다.

바이트 단위 이진 탐색 대신 몇 단계로 크기를 낮춰 본다. 크기 상한이 있는지 확인하려는 것이지, 모든 네트워크를 위해 숫자를 공표하려는 것이 아니다. 찾은 숫자는 이 경로, 이 프로토콜, 이 주소 체계에 적용된다.

IPv4와 IPv6는 단편화 방식이 다르므로 한쪽 결과가 다른 쪽에 대해 말해 주는 것은 없다. 애플리케이션이 실제로 쓰는 쪽을 시험한다.

MSS 클램핑은 TCP에만 도움이 된다

TCP에서는 핸드셰이크 중에 실효 페이로드가 협상되고, 경로 위의 라우터가 그 값을 아래로 다시 써서 양 끝이 더 작은 세그먼트를 보내게 할 수 있다. MSS 클램핑이 하는 일이 그것이며, 클라이언트를 전혀 건드리지 않고 동작한다.

iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
ip link show tun0

첫 규칙은 전달되는 TCP 핸드셰이크에 대해 알려진 MSS를 경로 MTU로 클램프하고, 둘째는 터널 인터페이스 자체의 MTU를 보여 주므로 경로가 허용하는 값과 비교할 수 있다. 핸드셰이크 뒤에 멈추는 TCP 애플리케이션에는 클램핑이 가장 덜 침습적인 수정이다.

UDP에는 아무것도 하지 않는다. 큰 데이터그램을 보내는 음성이나 게임 클라이언트는 다시 쓸 핸드셰이크가 없으므로 MSS 설정을 완전히 무시한다. 실패하는 애플리케이션이 UDP라면 대신 클라이언트 자체의 패킷 크기 옵션을 살펴본다. 두 프로토콜은 실패 방식도 고치는 도구도 다르므로, 티켓 첫 문장에 프로토콜을 적어야 한다.

OpenVPN은 tun-mtu, mssfix, 단편화 관련 설정 같은 옵션을 제공하며, 그 동작은 버전과 데이터 채널이 AEAD인지에 달려 있다. 사용 중인 버전의 설명서를 읽고, 한 번에 옵션 하나만 바꾼다.

지원되는 설정 하나를 바꾸고 두 실험을 모두 반복한다

원래 프로필을 두고 지원되는 값 하나만 바꾼 뒤, 성공했던 작은 요청과 실패했던 큰 전송을 모두 반복한다. 큰 쪽만 바뀌면 크기를 쫓고 있었던 것이다. 둘 다 바뀌면 다른 무언가가 함께 움직인 것이다.

값을 낮추면 증상은 줄지만 오버헤드가 늘어나므로, 가능한 가장 작은 숫자가 목표는 아니다. 목표는 경로가 가장 적은 낭비로 안정적으로 나르는 크기다. 같은 숫자를 다른 곳에 적용하는 것은 추측일 뿐이므로 값과 경로, 클라이언트 버전을 티켓에 적는다.

같은 프로필이 다른 신뢰할 수 있는 네트워크에서 동작한다면, 클라이언트가 고쳐졌다고 선언하는 대신 그 차이를 진단에 남긴다. 바뀐 변수는 경로이며, 시험이 측정한 것도 경로다.

관련 글