중국에 도착하면 평소 쓰던 것들이 로컬 네트워크에서 열리지 않는다. 구글이 그렇고, Claude Code도 그렇다. 그래서 무엇보다 국제망으로 나가는 통로부터 마련해야 하는데, 가장 손쉬운 통로는 로밍이나 eSIM처럼 처음부터 해외 통신사를 거치는 회선이고, 속도만 잘 나와 주면 고민은 거기서 끝난다. 문제는 이런 회선이 느려질 때다. 그때는 중국 회선을 그대로 쓰면서 tailscale exit node로 국제망에 나가는 방법이 남는다. 한편 다중뎬핑이나 메이퇀, 샤오훙수 같은 중국 서비스는 사정이 정반대라, 중국 회선으로 접속해야 훨씬 잘 돈다. 결국 요령은 tailscale을 빠르게 만드는 것이 아니라 지금 하려는 일에 따라 나가는 길을 바꾸는 것이다. 상하이에서 며칠을 지내는 동안 숙소에서는 로밍이 자주 느려져 ‘중국 회선 + exit’ 경로에 가장 많이 기댔고, 그 경로를 실제로 쓸 만하게 다듬는 데 들인 시간이 이 글이 됐다.
왜 exit node인가: 중국 로컬망에선 Claude Code나 구글 같은 국제 서비스가 막힌다. 로밍이나 eSIM이 빠르면 그걸로 나가면 되고, 느리면 중국 회선을 exit node로 우회한다. 반대로 중국 서비스는 중국 회선이 낫다. 하나로 고정하지 말고 오간다.
이 글이 다루는 것: 로밍이 느린 숙소에서 ‘중국 회선 + exit’를 실제 작업에 쓸 만하게 만든 기록. 느린 것과 불안정한 것을 갈라, 고칠 수 있는 쪽을 고쳤다.
확인 방법: 체감이 아니라 tailscaled 로그와 tailscale ping·netcheck 실측으로. 아래 수치는 모두 상하이 현지에서 직접 잰 값이다.
중국에서 노트북을 열면 익숙한 것들이 하나둘 멈춰 있다. 구글이 안 되고, 깃허브도 일부가 막히고, 이번 작업의 중심인 Claude Code는 아예 닿지 않는다. 밖으로 나가는 통로 없이는 일을 시작할 수가 없다.
쓸 수 있는 통로는 크게 셋이었다. SKT 로밍, 싱가포르 기반 eSIM, 그리고 중국 회선을 tailscale exit node로 우회하는 구성이다. 앞의 둘은 처음부터 해외 통신사를 거쳐 나가는 회선이라, 속도만 잘 나오면 exit node를 꺼낼 이유가 없다. 실제로 SKT 로밍은 잘 터지는 곳에서 다운로드 57Mbps까지 나왔고, 그런 곳에서는 아무 고민 없이 로밍을 그대로 썼다.
exit node가 필요해지는 순간은 그 회선들이 느려질 때 온다. 이번 숙소가 그랬다. 방 안에서는 로밍도 eSIM도 속도가 나오지 않아서, 결국 방까지 들어와 있는 중국 유선 회선을 exit node로 우회해 국제망에 나가는 구성에 가장 오래 기댔다. 이 글의 대부분은 이 경로를 다듬은 이야기다.
반대 방향의 문제도 있다. 다중뎬핑(大众点评, 맛집 리뷰와 예약)이나 메이퇀(美团, 배달과 생활 서비스), 샤오훙수(小红书, 사진 중심 SNS) 같은 중국 서비스는 exit로 나가면 오히려 굼뜨거나 아예 안 열리고, 중국 회선으로 접속해야 제 속도가 난다. 그래서 exit를 늘 켜 두는 것도 답이 아니다. 국제 서비스를 쓸 때는 exit로 나갔다가 중국 서비스를 쓸 때는 중국 회선으로 돌아오는 식으로, 그때그때 바꿔야 한다. 이 대비는 뒤에서 속도계 두 개로 직접 확인한다.
중국에서 tailscale을 검색해 보면 사람들이 가장 걱정하는 것은 속도가 아니라 접속 자체다. 붙었다 끊긴다거나 로그인이 안 된다는 이야기가 많은데, 노드들을 조율하는 컨트롤 플레인(controlplane.tailscale.com)에 닿지 못할 때 나오는 증상이다. 이번 체류에서는 그런 일을 겪지 않았다. 다만 로그를 열어 보니, 방해가 아예 없었던 것은 아니다.
연결이 강제로 방해받은 흔적은 하루치뿐이었다. 그날 컨트롤 서버 연결에 2021년에 만료된 엉뚱한 TLS 인증서가 222번 끼어들었는데, 경로상의 차단이라기보다는 WiFi 자체 인증(캡티브 포털)을 통과하기 전에 접속을 시도하다 숙소 장비에 가로채인 것일 수도 있다. tailscaled의 캡티브 포털 탐지가 포털을 찾지 못한 것도(found=false) 당시 망 상태가 어중간했다는 정황이라, 원인은 단정하지 않는다. 그 밖에는 조용했다. 컨트롤 서버의 실제 IP(Tailscale이 2025년 7월부터 정적 대역으로 쓴다고 공식 문서에 밝힌 192.200.0.0/24, 소유는 whois로 확인)로 가는 연결이 리셋된 일은 나흘 동안 네 번이 전부였다.
이런 방해가 있었는데도 작업에는 지장이 없었다. 이유는 두 가지다.
첫째, tailscaled가 스스로 다른 길을 찾아 계속 붙었다. 다이얼 후보 IP 목록(DialPlan), DNS 재질의, DERP(tailscale의 공용 중계 서버)를 거친 부트스트랩 DNS까지 사다리를 타고 내려가며 재시도한다. 게다가 컨트롤 연결은 웹 인증서에 기대지 않는 키 고정 방식(Noise)이라, 가짜 인증서가 끼어들어도 속지 않고 즉시 실패한 뒤 다른 경로를 탄다.
둘째 이유가 더 중요하다. 실제 트래픽이 흐르는 데이터 플레인은 컨트롤 플레인과 따로 돈다. 노드 사이의 WireGuard 터널은 컨트롤이 나눠 준 키와 피어 목록(netmap)을 캐시해 두고 그것만으로 굴러간다. 그래서 컨트롤이 몇 분씩 죽어 있어도 이미 붙은 터널과 exit 경유 트래픽은 아무 일 없이 흐른다. 컨트롤이 꼭 필요한 순간은 새 노드 추가나 키 갱신, ACL 변경 같은 조율뿐이고, 그 요청이 실패해도 재시도가 흡수하니 사용자는 모른 채 지나간다.
사전에 미리 셋업해 둔 peer relay 덕인가 하면, 그건 아니다. peer relay는 두 노드가 직결에 실패했을 때 공용 중계 서버(DERP) 대신 같은 tailnet에 속한 내 노드가 UDP 중계를 받아 주는 기능이다. 여행 내내 켜 두긴 했지만, 이건 피어 사이의 데이터 경로를 받아 주는 데이터 플레인 기능이라 컨트롤 접속과는 관련이 없다. peer relay가 실제로 해 준 일은 따로 있었다. 집 노드로 가는 트래픽이 도쿄 DERP(208ms)로 떨어질 상황을, 집에 있는 다른 노드가 중계를 맡아 63ms로 지켜 준 성능 개선이다.
덧붙이면 방해 정도는 붙는 망마다 다르다. 대도시 대형 ISP에서는 컨트롤과 DERP가 대체로 닿는다는 보고가 여럿 있고, 이번 경험도 그와 비슷했다. 다만 이건 “중국에서 이제 문제없다”가 아니라 “전면 차단은 아니고 망마다 다르다”로 읽는 게 맞다.
정리하면, 접속 자체는 tailscale이 알아서 버틴다. 남는 문제는 품질, 그러니까 “느리고 멈칫한다” 쪽이다.
체류 중 실제로 해야 했던 일은 tailnet 안의 서버에 SSH로 붙어 Claude Code를 돌리는 원격 작업이었다. 원격 셸은 회선 상태를 고스란히 드러내는 작업이라, 접속 구성을 바꿀 때마다 체감이 계단처럼 달라졌다.
| 단계 | 구성 | 체감 |
|---|---|---|
| 1 | 호텔 무선 WiFi + 도쿄, 싱가포르, 홍콩 exit | SSH는 붙지만 몇 분이면 먹통. 이어서 하는 실작업은 거의 불가능 |
| 2 | 호텔 유선을 여행용 라우터로 받아 WiFi 분배 + 같은 exit | 동작하다, 먹통이 되다, 다시 살아나는 반복. 조금 나아졌지만 여전히 속도로 고생 |
| 3 | 같은 유선 + 서울 exit(집 회선과 클라우드) | 느리지만 안정. 첫 응답만 기다리면 세션이 끊기지 않음 |
속도는 끝까지 느렸다. 호텔 회선의 다운로드는 대체로 5Mbps 안팎, 좋아야 10~20Mbps였고 마지막 날도 다르지 않았다. 바뀐 것은 안정성이다. 무선을 유선으로 바꾸자 먹통이 간헐 동작으로 올라왔고, exit를 지리 감각 대신 실측대로 서울에 옮기자 같은 속도에서도 세션이 끊기지 않게 됐다. 느린 것과 불안정한 것은 다른 문제고, 고칠 수 있었던 쪽은 후자였다. 회선이 느려도 안정적이기만 하면, exit를 경유하는 원격 작업은 충분히 굴러간다.
첫 번째 계단이 시사하는 바가 크다. 1단계에서 2단계로 넘어갈 때 exit는 그대로 두고 접속 매체만 바꿨는데 체감이 한 계단 올라왔다. 느림과 불안정의 범인은 exit나 tailscale이 아니라 로컬 접속 매체일 때가 많다는 뜻이다. 숫자도 같은 이야기를 한다. 마지막 밤 유선 구성에서 다운로드는 평균 2.3Mbps로 여전히 느렸지만, 같은 순간 exit 터널의 지연은 86ms에 손실 0%로 깨끗했다. 국제구간이 막힌 것이라면 exit 핑에도 손실이나 지터가 보여야 하는데 그렇지 않았다. 이렇게 지연은 낮은데 처리량만 낮다면, 숙소 회선에서 업스트림으로 나가는 관이 좁다는 전형적인 신호다.
속도를 직접 재 보고 싶으면 exit를 켠 채로 이렇게 한다. 측정 도구가 없어도 Cloudflare 엔드포인트로 충분하고, 한 번만 재면 편차가 크니 다운로드는 세 번 평균을 낸다.
for n in 1 2 3; do
curl -s -o /dev/null -w "%{speed_download}\n" --max-time 40 \
"https://speed.cloudflare.com/__down?bytes=50000000"
done | awk '{printf "%.1f Mbps\n",$1*8/1e6; s+=$1*8/1e6} END{printf "평균 %.1f\n",s/NR}'
“불안정”을 한 덩어리로 보면 exit만 이리저리 바꿔 가며 헤매게 된다. 성격으로 갈라야 한다. 원인마다 고칠 수 있는지, 어디서 확인하는지가 다르기 때문이다.
| 원인 | 성격 | 어떻게 잡나 |
|---|---|---|
| 국제구간 지터와 손실 버스트 | 구조적. 못 없애고 우회만 가능 | 핑 지연이 출렁이고(80~350ms대), 수 초짜리 손실이 한 경로의 여러 흐름을 함께 무너뜨림. 저녁엔 여러 경로가 동시에 나빠지기도 |
| exit의 IPv6 블랙홀 | 고칠 수 있음 | conn-track 타임아웃을 v4/v6로 갈라 셌을 때 v6가 압도적이면 이것 |
| 로컬 WiFi/링크 플랩 | tailscale과 무관. 기기나 환경에 따라 | 무선 재연결 루프 로그. (이 노트북은 리눅스를 올린 Apple Silicon 맥이라 무선 칩에 알려진 문제가 있다) |
exit node는 보통 “나로 전부 보내라”는 뜻으로 기본 경로를 광고한다. IPv4의 0.0.0.0/0과 함께 IPv6의 ::/0도 광고한다. 문제는 exit가 ::/0을 받아 놓고 정작 자기 바깥으로 v6를 못 내보낼 때 생긴다.
열쇠는 이름 조회 순서다. 기본 설정에서 glibc의 getaddrinfo는 목적지의 v6 주소를 v4보다 먼저 돌려주고, 앱은 대개 받은 순서대로 v6부터 연결을 시도한다. Happy Eyeballs(v4와 v6를 거의 동시에 걸어 먼저 되는 쪽을 쓰는 로직)를 제대로 구현한 앱이라면 v6가 막혀도 250ms 안팎에 v4를 병렬로 걸어 금방 넘어간다. 문제는 그런 앱만 있는 게 아니라는 점이다. Happy Eyeballs가 없는 앱은 죽은 v6 주소로 먼저 connect를 걸고, 그 연결 타임아웃을 수 초에서 십수 초까지 기다린 뒤에야 v4로 물러선다. 사용자 눈에는 “눌렀는데 한참 멈칫했다가 뒤늦게 열리는” 그 느낌이다. 끊긴 게 아니라, 죽은 v6를 먼저 두드리고 있는 것이다. 그래서 뒤의 처방은 이 조회 순서 자체를 v4 우선으로 바꾼다. 앱이 Happy Eyeballs를 구현했든 안 했든, v4 주소를 먼저 받으니 죽은 v6를 두드릴 일이 사라진다.
체감으로는 못 가른다. 로그로 가른다. tailscaled는 연결을 못 연 시도를 open-conn-track: timeout으로 남기고, 그 줄에 출발지와 목적지 주소가 찍힌다. 목적지가 대괄호로 싸여 있으면([2606:4700:...]) v6다.
$ journalctl -u tailscaled --since "3 hours ago" \
| grep "open-conn-track: timeout"
# v6 시도가 exit를 통해 죽는 전형 (Cloudflare DNS의 v6로 나가려다 타임아웃)
open-conn-track: timeout opening (TCP [fd7a:…]:40130
=> [2606:4700:4700::1111]:443) to node [exit]; online=yes
이걸 v4/v6로 갈라 세면 블랙홀이 있는지 바로 보인다. 실제로 exit를 싱가포르 인스턴스에 두었을 때와, 서울 인스턴스로 옮기고 클라이언트를 v4 우선으로 고정한 뒤를 같은 방식으로 비교하면 이렇다.
| 구간 | 측정 창 | 총 타임아웃 | 그중 v6 | 시간당(총 / v6) |
|---|---|---|---|---|
| 싱가포르 exit (전) | 7/19 저녁 약 3.9시간 | 1,535건 | 981건(64%) | 약 400 / 253 |
| 서울 exit + v4 우선 (후) | 7/19 밤 23시~7/20 새벽 1시(활동 약 2.1시간) | 105건 | 24건(23%) | 약 50 / 11 |
v6 타임아웃이 시간당 253건에서 11건으로 떨어졌다(약 23분의 1). 블랙홀이 실제로 사라진 것이다. 남은 타임아웃은 이제 v4가 다수인데, 이건 블랙홀이 아니라 국제구간 버스트가 v4 연결을 때려서 나는 것이다. 원인을 갈랐더니, 고칠 수 있는 블랙홀은 사라지고 못 고치는 버스트만 남았다는 게 이 표의 요점이다.
exit가 얼마나 “새는지” 보려고 핑부터 때리는 사람이 많은데, 핑 손실률은 과장되기 쉽다. 노드는 부하가 걸리면 ICMP(핑) 응답을 실제 트래픽 뒤로 미룬다. 그래서 핑은 손실 20%로 나와도 같은 터널의 실제 TCP 경로는 멀쩡한 일이 흔하다(예전 저녁 버스트 때 exit 핑 손실 20%인데 TCP 연결은 0% 손실이었다).
마지막 밤의 측정은 반대 방향에서 같은 교훈을 준다. exit를 지나 공개 호스트로 핑을 20번 때려도 손실 0%, TCP 핸드셰이크도 20번 모두 성공, 지연도 90ms로 깨끗했다. 그런데 같은 순간 실제 다운로드 속도는 2.3Mbps였다. 핑은 완벽하다고 말하는데 회선은 굼떴던 것이다. 핑은 체감을 재는 도구가 아니다. 판단은 핑 수치가 아니라 conn-track 로그와 실사용(curl이나 ssh)으로 한다.
가장 크게 헛짚는 지점이 여기다. “중국에서 쓰니까 가까운 홍콩이나 싱가포르에 두자”가 대개 틀린다. 국제 트래픽은 지도상 거리가 아니라 통신사 사이의 피어링 접점을 따라 흐르기 때문이다. 그래서 “가까운 나라”가 “가까운 경로”와 자주 어긋난다.
아래는 상하이에서 tailscale ping과 netcheck로 후보 경로들을 실측한 값이다. 직결(exit 터널을 직접 뚫은 경우)과 릴레이(중계 서버를 거친 경우)를 갈라 보면 두 가지가 눈에 들어온다.
첫째, 직결이 릴레이를 압도한다. 홀펀칭(NAT를 넘어 노드끼리 직접 잇기)에 성공해 exit로 곧장 뚫으면 60~86ms인데, 중계 서버로 떨어지면 제일 가까운 것조차 149ms고 대부분 200ms를 넘는다. exit를 세울 땐 직결이 되는지부터 봐야 한다.
둘째, 지리가 순위를 안 정한다. 상하이에서 지도상 가장 가까운 축인 홍콩 릴레이가 220ms로, 태평양 건너 로스앤젤레스(149ms)보다 느리다. 릴레이 지연은 시간대에 따라 크게 출렁인다. 전날은 홍콩이 326ms였다. 그러니 절대값이 아니라 “직결이 이긴다, 가까운 게 빠른 게 아니다”라는 순위만 읽으면 된다. 후보는 머리로 고르지 말고 netcheck와 tailscale ping으로 재보고 정한다.
직결이 되는 두 후보, 즉 집에 상시 켜 둔 기기(주거 IP)와 클라우드 인스턴스(데이터센터 IP)는 성격이 갈린다. 실측 지연만 보면 집이 더 빠른데(같은 서울권인데 25ms 차이가 나는 것 자체가 “피어링”의 증거다), 실제 선택은 지연만으로 안 갈린다.
| 집 회선 exit (주거 IP) | 클라우드 exit (데이터센터 IP) | |
|---|---|---|
| 상하이발 지연 | 약 60ms | 약 86ms |
| IPv6 | 회선에 아예 없음(무해, 아래 참고) | 정상 동작 |
| Cloudflare “Just a moment…” 검문 | 안 걸림 | 걸림(데이터센터 IP라서) |
| 저녁 버스트 노출 | 집 업링크가 국제구간에 물려 더 출렁임 | 상대적으로 균일 |
| 잘 맞는 쓰임 | 한국 서비스 접근과 지연이 최우선 | v6 필요, 검문 회피 불필요, 상시 |
주거 IP의 큰 장점은 Cloudflare의 “Just a moment…” 검문에 안 걸린다는 점이다. 한국 서비스를 자주 쓰면 집 exit가 매끄럽다. 대신 실측에서 집 경로는 저녁 버스트에 더 노출됐다. 집의 상시 맥으로 5번 핑을 보내면 3번이 무응답이었는데, 같은 순간 서울 클라우드 exit는 0% 손실이었다. 같은 “서울”이라도 두 회선이 타는 국제 경로가 달라, 버스트가 한쪽만 때리는 것이다. 그래서 평소엔 안정적인 클라우드 exit를 상시로 두고, 한국 서비스가 중요할 때 집 exit로 바꾸는 쪽으로 정리했다.
표에서 집 회선의 “IPv6 없음”을 단점이 아니라 무해로 적은 데는 이유가 있다. 앞서 본 블랙홀은 exit가 ::/0을 광고해 놓고 못 나가서, 죽은 v6로 건 연결이 타임아웃까지 수 초씩 매달리는 경우다. 반면 집 회선처럼 v6 주소도 경로도 아예 없으면, 커널이 첫 시도에서 “갈 길 없음”(ENETUNREACH)을 즉시 돌려주고 앱은 곧바로 v4로 넘어간다. 똑같이 v6를 안 쓰는데, 하나는 긴 멈칫이고 하나는 걸림 없이 넘어간다. exit를 고를 때 “v6 됨/안 됨”보다 “안 되면 빨리 실패하나, 오래 매달리나”가 실제 체감을 가른다.
exit를 옮겨 다니다 보면 exit마다 v6 유무가 달라 매번 마찰이 생긴다. 클라이언트에서 v4를 우선으로 못 박아 두면 어느 exit에 붙든 v6를 먼저 두드리는 일이 없어진다(v6가 되는 exit에선 예비로만 남는다). 리눅스에서는 /etc/gai.conf로 한다.
여기 함정이 하나 있다. glibc는 precedence 줄이 하나라도 있으면 내장 기본 표를 통째로 버린다(gai.conf 매뉴얼에 그렇게 적혀 있다). v4 우선 한 줄만 적으면 나머지 규칙이 다 날아가 오히려 이상해진다. 그래서 기본 표 다섯 줄을 전부 옮겨 적고, 그중 v4를 담는 ::ffff:0:0/96(v4-mapped)의 우선순위만 v6 기본값(::/0)보다 높게 올린다. 100은 흔히 쓰는 값이다.
# IPv4 우선 (/etc/gai.conf)
# precedence 줄이 하나라도 있으면 기본 표 전체가 무시되므로 전부 명시
precedence ::1/128 50
precedence ::/0 40
precedence 2002::/16 30
precedence ::/96 20
precedence ::ffff:0:0/96 100 # v4-mapped를 최상위로 = v4 우선
적용됐는지는 이름 조회 순서로 바로 확인된다.
$ getent ahosts cloudflare.com | head -1
104.16.132.229 STREAM cloudflare.com # v6가 아니라 v4가 먼저 나오면 적용됨
되돌리기는 sudo rm /etc/gai.conf 한 줄이다. 원본은 전부 주석인 glibc 예시 파일이라 지워도 잃을 게 없다. 운영에서 한 가지만 주의하면 된다. v6 관련 실험을 하다가 tailscaled를 재시작하면 exit 선택이 저장된 기본값으로 되돌아간다. GUI나 CLI로 골라 둔 exit가 조용히 바뀔 수 있으니, 재시작 전후로 tailscale status를 확인한다.
앞서 말한 “유연하게 오간다”가 왜 필요한지는 속도계 두 개만 돌려 봐도 눈에 보인다. 넷플릭스가 운영하는 fast.com(글로벌)과 중국 로컬 속도계 speedtest.cn을, 브라우저를 서울 exit로 내보낸 상태와 exit 없이 중국 회선으로 내보낸 상태에서 각각 돌려 봤다.
| 속도계 | 서울 exit 경유 | 중국 회선 직접 |
|---|---|---|
fast.com (넷플릭스, 글로벌) | 다운 4.8Mbps로 정상 측정 | “Could not reach our servers”, 측정 서버에 붙지 못함 |
speedtest.cn (중국 로컬) | 페이지는 뜨지만 “내 네트워크 검색 중”에서 멈춰 측정 시작조차 안 됨 | 내 위치를 상하이 차이나텔레콤으로 잡고 다운 8.5 / 업 3.2Mbps 정상 측정 |
정확히 거울처럼 뒤집힌다. 넷플릭스 CDN은 중국 회선에서 직접 닿지 않으니 fast.com은 exit를 켜야 측정되고, 반대로 중국 로컬 속도계는 접속 IP가 중국 안이어야 자기 사용자로 인식하니 exit를 꺼야 측정된다. 이게 앞서 말한 다중뎬핑, 메이퇀, 샤오훙수 이야기와 같은 그림이다. 국제 서비스를 쓸 땐 exit로 나가고, 중국 서비스를 쓸 땐 중국 회선으로 돌아온다. 그러니 exit는 켜 두는 게 아니라 오가는 것이다.
참고로 이 측정을 한 밤의 다운로드는 무엇으로 재든 한 자릿수 Mbps 언저리였다(curl 2.3, fast.com 서울 4.8, speedtest.cn 현지 8.5). 값이 측정마다 갈리는 건 좁은 중국 업링크가 병목이라 그때그때 출렁이기 때문이지 exit나 사이트 탓이 아니다. 무엇을 재느냐가 아니라 어느 회선으로 나가느냐가 “되고 안 되고”를 가른다. (앱마다 나가는 경로를 통째로 바꾸지 않고 하나씩만 우회시키는 방법은 이 글 범위 밖이라 접어 둔다. 유저스페이스 프록시로 특정 앱만 exit로 보내는 방법을 따로 정리해 두었다.)
끝으로 못 고치는 것을 인정해야 한다. 저녁 시간대 국제구간의 손실 버스트는 구조적이라, 어느 경로가 언제 걸릴지 예측하기 어렵다. 같은 밤에도 서울 exit는 손실 0%인데 집 경로는 240~300ms로 출렁였다. exit를 바꿔 그날 밤 나아질 수는 있어도, 버스트 자체를 없앨 수는 없다. 진단이 하는 일은 이 구조적 손실을 exit나 tailscale 탓으로 오해하지 않게 해 주는 것까지다. 나머지는 감수하거나 시간대를 피하고, 대화형 연결의 버스트 내성이 급하면 mosh 같은 걸 얹는 정도가 차선이다.
이번에 겪은 것이 일반적인지 확인하려고, 2024~2026년의 공개 경험담을 영어권(Reddit, GitHub 이슈)과 중국어권(V2EX, 즈후, 기술 블로그) 중심으로 훑었다(일본어권은 1차 경험담이 거의 없었다). 훑은 글에서 자주 보인 문제부터 이번 여행과 대조하면 이렇다(빈도는 통계가 아니라 그 글들에서 받은 인상이다).
| 문제 | 보고 빈도 | 이번 여행에서는 |
|---|---|---|
| 직결이 안 되고 DERP로만 붙어 느림 | 가장 많음 | 겪지 않음. exit 직결이 유지됐고, 피어 경로가 DERP로 떨어질 상황은 peer relay가 받았다 |
| UDP 품질 저하, 시간대별 손실 | 많음 | 겪었고 못 고쳤다. 공개 경험담에도 시간대 회피 외의 해법이 없다 |
| ISP와 지역, 시간대에 따른 편차 | 많음 | 겪었다. “중국에서 되나”가 아니라 “어느 망에서 몇 시에”가 맞는 질문이다 |
| NAT 타입, 포트, MTU 문제 | 많음(중국어권) | 겪지 않음. 홀펀칭이 바로 됐다 |
| DNS가 꼬여 사이트가 안 열림 | 종종 | 겪지 않음(MagicDNS 사용) |
| 로그인이나 컨트롤 접속 불가 | 드묾 | 겪지 않음. 공개 표본에서도 주증상이 아니었다 |
대조에서 두 가지가 눈에 띈다. 첫째, 중국어권에서 가장 흔한 처방인 “본토에 자체 DERP를 세워라”는 릴레이 지연을 줄일 뿐 직결을 만들어 주지는 못하는데, 앞서 본 peer relay가 바로 그 역할(자기 노드로 중계)을 공식 기능으로 흡수했다. 2024년 4월 “중국에 있는 사용자에게 특히 큰 문제”라며 자체 DERP 없이 기존 노드를 릴레이로 쓰자는 기능 요청(tailscale/tailscale #11879)이 올라왔고 아직 열려 있는데, peer relay가 사실상 그 요청과 같은 일을 한다. 둘째, 이번에 고칠 수 있었던 exit의 v6 블랙홀은 공개 경험담에는 거의 안 보인다. 커뮤니티의 IPv6 이야기는 대개 “직결률을 높이려면 v6를 켜라” 쪽인데, 그 반대편, exit가 v6를 광고만 하고 못 내보낼 때의 멈칫은 조용히 묻혀 있는 함정에 가깝다.
netcheck와 tailscale ping으로 후보를 재본다. 직결 되는 걸 고르고, 한국 서비스 위주면 집 exit, v6나 안정 위주면 클라우드 exit./etc/gai.conf에 다섯 줄 전부, v4-mapped를 최상위로.conn-track 타임아웃을 v4/v6로 갈라 세서 v6 블랙홀부터 확인하고, 핑 손실률에 속지 않는다. 남는 저녁 버스트와 로컬 병목은 구조적이니 감수한다.숙소 회선이 별로일 때 카페로 나가는 건 중국에서 잘 안 통한다. 체인점은 대개 자체 인증 체계를 갖추고 있어 중국 전화번호(+86)가 없으면 로그인 단계를 못 넘는다. 스타벅스에서도 인증에 실패해 WiFi를 못 썼다. 개인 카페는 WiFi를 아예 안 주는 곳이 있고, 비밀번호를 알려 줘도 손님용 회선에 제한이 걸려 있어 몹시 느린 경우가 많다. 작업 공간이 필요하면 카페를 백업으로 계산에 넣지 말고, 회선이 검증된 숙소나 로밍을 준비하는 편이 낫다.
측정 기기는 노트북 한 대, 장소는 상하이 엑스포 부근의 호텔이다. 최종 구성은 호텔 유선을 여행용 라우터(GL.iNet Mudi 7)가 받아 WiFi로 나눠 주고, 노트북의 전 트래픽을 exit 경유로 내보내는 형태다. 상류는 차이나텔레콤이다. 아래 값은 모두 이 최종 구성에서 7/19 밤에서 7/20 새벽 사이에 다시 잰 것이다.
journalctl -u tailscaled --since "3 hours ago" | grep "open-conn-track: timeout" 뒤, 연결 튜플이 (TCP [로 시작하면 v6로 집계.tailscale ping <peer>(직결/릴레이/peer-relay와 지연을 함께 보여줌), tailscale netcheck(릴레이 지도).ping -c 20(ICMP 손실)과 curl 반복(TCP 성공률)을 나란히.__down/__up, 다운로드는 3회 평균.journalctl -u tailscaled | grep -E "PollNetMap|failed dialing"을 원인별로 분리해 시간별로 집계. 차단 신호는 connection reset과 TLS cert verificication ... failed 경고만 센다. 철자는 tailscaled 로그 원문 그대로다(network is unreachable은 오프라인이나 v6 부재의 소음이라 제외).