iPad만 빈 화면? WebRTC 디버깅 끝에 찾은 Tailscale IPv6 조각 패킷 드롭
The iPad was on Tailscale: a WebRTC debugging story
TL;DR Highlight
iPad만 빈 페이지였던 원인은 webrtc-rs MTU 상수와 Tailscale의 IPv6 조각 패킷 폐기였다
Who Should Read
WebRTC 데이터 채널이나 UDP 기반 P2P 기능을 만들다 특정 기기에서만 응답이 멈추는 문제를 만난 백엔드·웹 개발자
Core Mechanics
- iPad에서만 첫 GET이 영원히 대기
- 데이터 채널은 open 상태였고 박스 에이전트는 응답을 보냈다고 봤다.
- 브라우저에는 220바이트 헤더만 도착하고 본문은 오지 않았다.
- WebKit 탓이 아니라 패킷이 안 왔다
- getStats()에서 iPad의 candidate pair가 2,144바이트·18패킷에서 멈춰 있었다.
- 재전송이 계속되는데 전송 카운터가 안 움직였으니 패킷이 도착하지 않은 것이다.
- webrtc-rs의 INITIAL_MTU가 1228로 고정
- 설정할 수 없고 갱신도 안 되며 암호화 계층까지 붙으면 와이어상 1,265바이트가 된다.
- IPv4 헤더를 더하면 1,293바이트, IPv6면 1,313바이트로 Tailscale 터널 최대치 1,280을 넘는다.
- Tailscale은 IPv6 조각 패킷을 버린다
- 파서가 IPv6 Fragment 헤더 패킷을 unknown protocol로 분류해 기본 거부가 걸린다.
- tailscaled_inbound_dropped_packets_total{reason="acl"} 카운터가 올라갔다.
- ping 두 줄로 WebRTC 없이 재현된다
- ping -s 1400을 IPv6로 보내면 100% 손실, IPv4는 정상이었다.
- 확인한 모든 Tailscale 기기에서 같은 손실이 났다.
- 800바이트 제한은 되고 1,200바이트는 실패
- 800바이트는 조각 자체가 생기지 않아 통과했다.
- webrtc-rs가 첫 패킷을 1,228까지 패딩해서 1,200바이트 메시지도 한계를 넘었다.
Community Reactions
- MTU 블랙홀이 최악이라는 의견
- 헬스체크용 패킷은 전부 작아서 살아남기 때문이라는 설명이다.
- Tailscale이 UDP 조각을 통째로 버리는 게 이해가 안 된다는 의견
- 조각에서는 포트 번호를 못 읽어 ACL이 거부했을 것이라는 추정이 나왔다.
- 전역 on/off 설정이나 포트가 있는 첫 조각만 차단하는 방식을 대안으로 제시했다.
- iOS 시뮬레이터에서 비슷한 일을 겪었다는 경험
- HTTP3·QUIC가 1506바이트 UDP 패킷을 보내 인증이 실패했다고 한다.
- 방화벽에서 UDP 80/443을 막아 우회했다고 한다.
- 저자가 webrtc-rs 쪽 수정 PR(#807)을 올렸다는 정보
- Tailscale 쪽은 이슈 #20083으로 접수된 상태다.
How to Apply
- 양쪽 기기에서 큰 ping으로 경로 확인하기
- 특정 기기만 느리거나 멈추면 오늘 ping -s 100과 ping -s 1400을 IPv4·IPv6 주소에 각각 실행하라.
- 1400만 손실이면 조각 패킷 문제로 보고 WebRTC 코드를 건드리기 전에 경로부터 분리하라.
- 데이터 채널 메시지를 800바이트 이하로 제한하기
- webrtc-rs를 쓰는 송신 측에서 청크 최대 크기를 800바이트로 낮춰라.
- 1,200바이트는 첫 패킷 패딩 때문에 실패했으니 쓰지 마라.
- 수신 측 카운터로 디버깅하기
- 송신 측 tcpdump만으로 정상 판정하지 말고 수신 기기의 getStats() 바이트 수를 함께 기록하라.
- 재전송 중인데 수신 바이트가 멈춰 있으면 네트워크 문제로 판단하라.
- 한 기기만 문제면 그 기기의 경로부터 묻기
- 문제 기기와 정상 기기가 VPN·IPv6·ICE 후보 쌍 중 무엇이 다른지 표로 비교하라.
- 선택된 candidate pair를 로그에 남겨라.
Try It
# 셸에 붙여넣기: Tailscale 기기에서 IPv6 조각 패킷 손실 확인 (원문의 2줄 재현)
TS6={{상대 기기의 Tailscale IPv6 주소}}
ping -c 3 -s 100 $TS6
ping -c 3 -s 1400 $TS6
# 두 번째만 100% loss면 IPv6 조각 패킷이 버려지는 것
tailscale metrics print | grep dropped_packetsTerminology
관련 논문
LLM으로 대규모 formal verification이 가능해졌다
Claude로 오픈소스 라이브러리 123개의 no-crash 증명을 만들고 버그도 찾았다
PHP2Shell: AI가 찾아낸 PHP 코어 RCE 취약점
Wiz가 AI로 PHP 코어의 10년 묵은 RCE를 찾아 익스플로잇까지 만들었다
LLM에게 confidence 점수를 물어보면 안 되는 이유
LLM이 말한 confidence 숫자는 측정값이 아니라 그럴듯한 텍스트일 뿐이다
The Judge Can Debias Itself: LLM-as-a-Judge가 스스로 편향을 고치게 하는 법
심사 프롬프트에 편향 경고를 넣거나 스스로 비판시켰더니 21개 중 20개 조합에서 정확도가 올랐다
미국 노동시간의 90%는 AI 벤치마크가 제대로 다루지 못하는 일이다
벤치마크 29개를 직업 과제에 매핑했더니 노동시간 90%가 커버 밖
Postgres 19, AI 코드 리뷰가 버그를 너무 많이 찾아 출시 연기
LLM 코드 리뷰가 버그를 쏟아내 Postgres 19가 11월로 밀리고 기능 8~10개가 빠졌다
StarSkirmish Bench v0.2: LLM이 1시간 만에 짜는 StarCraft 봇 대결
LLM에게 1시간 동안 StarCraft 봇을 짜게 했더니 GPT-6 Astra가 1위
그럴듯한 설명 하나면 멀티모달 LLM 심사위원도 속는다 (JUPE 벤치마크)
정답은 그대로 두고 설명만 붙였더니 MLLM 심사위원의 오답 거절률이 12.7~13.8%p 떨어졌다