The iPad was on Tailscale: a WebRTC debugging story
TL;DR Highlight
WebRTC 데이터 채널에서 iPad만 응답을 못 받는 희귀 버그를 추적한 결과, webrtc-rs의 하드코딩된 MTU 상수와 Tailscale의 IPv6 Fragment 패킷 드롭이 동시에 작용한 복합 버그였다는 2주간의 디버깅 실화.
Who Should Read
WebRTC, VPN, 네트워크 레이어가 얽힌 환경에서 P2P 통신 기능을 개발하거나 운영 중인 백엔드/풀스택 개발자. 특히 특정 기기나 네트워크 조건에서만 재현되는 간헐적 버그를 디버깅해본 경험이 있는 개발자라면 공감 포인트가 많다.
Core Mechanics
- iPad에서만 WebRTC 데이터 채널이 열렸는데도 응답이 오지 않는 현상이 발생했다. 같은 WiFi, 같은 브라우저 엔진 조건에서도 Mac, Linux, Android폰은 정상 동작했는데 iPad만 빈 페이지였다.
- Heisenbug 특성이 있어서 새로고침을 계속 하면 가끔 로딩되는 경우도 있었고, 콘솔 에러도 없었으며, WebRTC 핸드셰이크도 정상 완료, dc.readyState도 'open'이었다. 즉 모든 지표가 정상인데 데이터만 안 왔다.
- 양쪽 로그를 타임스탬프 기준으로 맞춰보니 서버(box agent)는 분명히 220byte 헤더 + 7,874byte 본문 + 199byte 꼬리 총 3개 청크를 보냈지만, iPad는 220byte 헤더 하나만 수신하고 이후 데이터는 전혀 못 받았다.
- 처음 의심했던 WebRTC maxMessageSize(최대 단일 청크 크기) 제한은 iPad가 64kb로 Mac과 동일했고, 실제로 보내는 7~8kb보다 훨씬 커서 범인이 아니었다. 이 결론이 오히려 나중에 진짜 원인을 찾기 어렵게 만들었다.
- iPad에 Tailscale(VPN)이 켜져 있다는 사실을 발견했다. VPN은 패킷에 오버헤드 레이어를 추가해서 각 패킷에 실을 수 있는 데이터량(MTU)을 줄이는데, 그러면 큰 메시지가 더 많은 조각(fragment)으로 분할된다.
- 진짜 원인은 두 가지 버그의 조합이었다. 첫째, webrtc-rs가 INITIAL_MTU를 1228로 하드코딩해놓고 경로 탐색(path probing)도 없이 재전송도 항상 같은 크기로 했다. 둘째, Tailscale의 패킷 필터가 IPv6 Fragment 헤더를 가진 패킷을 'unknown protocol'로 분류해서 기본 deny 규칙으로 드롭했다.
- 두 버그가 합쳐지면서 '완벽한 무음 장애'가 발생했다. 헬스체크용 작은 패킷은 fragment 없이 통과하니 모든 모니터링 지표가 정상, 오직 실제 페이로드만 fragment로 분할되어 Tailscale에서 조용히 버려졌다.
- 재현 방법은 간단하다: Tailscale IPv6 주소로 `ping -s 100`은 통과하지만 `ping -s 1400`은 100% 패킷 손실. webrtc-rs에 PR(#807)을 올렸고 Tailscale에도 이슈(#20083)를 제보했다. 임시 해결책으로 메시지 크기를 800byte로 제한해서 패킷이 fragment되지 않도록 했다.
Evidence
- MTU 블랙홀(MTU black hole: 큰 패킷은 조용히 버려지는데 헬스체크용 작은 패킷은 통과해서 장애를 감지할 수 없는 상황)은 디버깅하기 가장 끔찍한 문제라는 공감대가 형성됐다. 헬스체크가 다 초록불이어서 뭔가 잘못됐다는 단서를 찾기가 극도로 어렵기 때문이다.
- Tailscale이 UDP fragment 패킷을 아예 드롭하는 설계 결정에 대해 비판적인 댓글이 있었다. TCP는 PMTUD(경로 최대 전송 단위 탐색)로 자동 조정하니 문제가 잘 안 드러났지만, 분할된 UDP 패킷은 정상적인 네트워크 동작의 일부인데 포트 번호를 파싱할 수 없다는 이유로 전부 드롭하는 건 문제가 있다는 지적이었다.
- Pion SCTP 이슈 #12를 언급하며 이 MTU 하드코딩 문제가 webrtc-rs뿐 아니라 여러 WebRTC 구현체에 걸쳐 수년째 미해결 상태로 남아있다는 아쉬움이 공유됐다.
- PPPoE 시대(1990년대)에도 MTU가 1500byte보다 약간 작은 환경에서 일부 상용 UNIX TCP/IP 스택이 더 작은 MTU를 무시하거나 지원하지 않아 대용량 전송이 망가졌던 경험을 공유한 댓글이 있었다. MTU 관련 문제는 수십 년 된 고질병임을 상기시켰다.
- iOS 앱 출시 직전에 실제 기기는 정상인데 시뮬레이터에서만 인증이 깨지는 버그를 경험한 사례가 공유됐다. Wireshark로 보니 iOS 시뮬레이터 스택이 MTU를 잘못 추적해서 1506byte UDP 패킷을 보내려 했고, 결국 방화벽에서 UDP 80/443 포트를 막는 것으로 해결했다는 아이러니한 픽스였다.
How to Apply
- WebRTC 데이터 채널을 사용하는 앱에서 특정 기기나 VPN 환경에서만 데이터 수신이 안 되는 현상을 겪고 있다면, 먼저 `ping -s 1400` vs `ping -s 100`으로 MTU 블랙홀 여부를 빠르게 확인하라. 큰 패킷만 손실된다면 MTU/fragment 문제다.
- webrtc-rs를 사용 중이라면 INITIAL_MTU가 1228로 하드코딩되어 있고 경로 탐색을 하지 않는다는 점을 인지해야 한다. 메시지를 800byte 이하로 분할해서 보내는 것으로 임시 회피할 수 있고, 공식 PR #807을 주시하며 패치를 기다리는 것이 좋다.
- Tailscale 등 VPN 환경에서 WebRTC나 UDP 기반 P2P 통신을 운영한다면 IPv6 + Fragment 조합이 Tailscale ACL에서 기본 deny된다는 것을 알아야 한다. Tailscale 이슈 #20083을 추적하거나, 재현 및 패킷 캡처가 포함된 github.com/phact/mtu-webrtc-bug 레포를 참고해 자신의 환경에서 테스트해볼 수 있다.
- VPN이 개입된 환경에서 디버깅할 때는 양쪽 로그를 타임스탬프 기준으로 정렬하고 '실제로 전송된 바이트 수'와 '상대방이 받은 바이트 수'를 나란히 비교하는 계측(instrumentation)을 초기에 추가하라. 이 글의 경우 이 방법으로 헤더만 도착하고 본문이 버려진다는 사실을 발견할 수 있었다.
Code Example
# MTU 블랙홀 빠른 진단법 (Tailscale IPv6 주소 대상)
ping -s 100 <tailscale-ipv6-addr> # 통과해야 정상
ping -s 1400 <tailscale-ipv6-addr> # 100% 손실이면 MTU/fragment 문제
# webrtc-rs 임시 회피: 메시지를 800byte 이하로 청킹
// Rust 예시 (개념)
const SAFE_CHUNK_SIZE: usize = 800;
for chunk in data.chunks(SAFE_CHUNK_SIZE) {
data_channel.send(chunk).await?;
}Terminology
Related Papers
ALIGNBEAM : Inference-Time Alignment Transfer via Cross-Vocabulary Logit Mixing
도메인 파인튜닝으로 망가진 LLM 안전성을, 재학습 없이 추론 시점에 작은 안전 모델에서 빌려와 복구하는 방법.
Can LLMs Beat Classical Hyperparameter Optimization Algorithms?
LLM 기반 하이퍼파라미터 최적화 에이전트와 CMA-ES, TPE 같은 고전 알고리즘을 직접 비교한 연구로, LLM 단독으로는 고전 방법을 이기지 못하지만 두 방법을 합친 하이브리드 'Centaur'가 최고 성능을 낸다는 결론이 나왔다.
What the Eyes See, the LLMs Miss: Exploiting Human Perception for Adversarial Text Attacks
Bold, 하이라이트, 공백 배치 같은 타이포그래피 트릭으로 GPT-4o, Llama Guard 등 10개 콘텐츠 모더레이션 시스템을 99% 이상 우회할 수 있다.
Did Claude increase bugs in rsync?
rsync 프로젝트에 Claude AI가 도입된 이후 버그가 늘었다는 소셜 미디어 주장을 실제 데이터와 통계 분석으로 검증한 글로, 결론적으로 Claude 도입 후 릴리즈가 역사적 분포에서 유독 버그가 많다는 통계적 근거는 없었다.
I built a vulnerable app and spent $1,500 seeing if LLMs could hack it
Firebase 취약점을 가진 앱을 직접 제작하고 GPT-5.5, Claude, Deepseek 등 주요 LLM이 자율적으로 해킹할 수 있는지 실험한 결과, GPT-5.5가 70% 성공률로 압도적이었고 Claude는 보안 거부 정책 때문에 능력과 무관하게 낮은 점수를 기록했다.
Clustered Self-Assessment: A Simple yet Effective Method for Uncertainty Quantification in Large Language Models
LLM이 여러 답변을 의미 단위로 묶어 객관식으로 만들고 스스로 채점해서 '이 답 얼마나 확신해?'를 수치로 뽑아내는 기법.