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
Show HN: Mindwalk – Replay coding-agent sessions on a 3D map of your codebase
Claude Code나 Codex 같은 AI 코딩 에이전트가 세션 중 코드베이스의 어떤 파일을 탐색하고 수정했는지를 3D 지도 형태로 시각화해서 재생해주는 로컬 도구다. 에이전트가 작업을 어떻게 이해했는지 한눈에 파악할 수 있다.
Ghost Font: A font that humans can read but AI cannot
움직임(모션)을 이용해 글자를 표현해서 AI 모델이 정적 이미지 분석으로는 메시지를 해독하지 못하게 막는 실험적 프로젝트인데, 커뮤니티에서는 이미 GPT-5.6, Claude Opus 등으로 해독에 성공한 사례가 속출해 실효성 논쟁이 뜨겁다.
GPT-5.6, Grok 4.5, Claude, and Muse Spark build the same 4 apps
12개 LLM 모델에게 레이캐스터 미로, 루빅스 큐브, 계산기, Game of Life 앱을 각각 5번씩 만들게 해서 성공률·비용·속도를 비교한 실전 벤치마크다. GPT-5.6 Sol이 전반적으로 가장 일관된 결과를 냈고, Grok 4.5는 가성비 면에서 눈에 띄었다.
Benchmarking coding agents on Databricks' multi-million line codebase
Databricks가 자사 실제 코드베이스를 기반으로 여러 AI 코딩 에이전트의 성능과 비용을 직접 측정했고, 모델 토큰 가격과 실제 태스크 비용이 전혀 다르다는 점, 그리고 오픈소스 모델이 이제 최상위 수준에 도달했다는 점을 확인했다.
Estimating Uncertainty from Reasoning: A Large-Scale Study of Multi- and Crosslingual MCQA Performance in LLMs
LLM이 저자원 언어 질문을 받을 때 영어로 추론하게 하면 불확실성 추정 성능이 고자원 언어 수준으로 올라온다.
LLM-as-a-Verifier: A General-Purpose Verification Framework
LLM의 토큰 확률 분포를 활용해 discrete 점수 대신 continuous 점수를 뽑아내면, 추가 학습 없이 코딩·로봇·의료 에이전트 평가 정확도를 SOTA로 끌어올릴 수 있다.