트래픽이 정말 VPN을 지나가고 있을까? 확인하는 방법
경로 조회, 첫 홉, 물리 어댑터에서의 캡처, 외부에서의 주소 확인은 각각 질문의 다른 부분에 답합니다.
트레이 아이콘에서 보면 두 가지 장애가 똑같아 보입니다 — 터널로 들어가는 트래픽과 조용히 터널을 우회하는 트래픽입니다. 두 경우 모두 클라이언트는 Connected를 표시하는데, Connected는 터널 인터페이스가 존재하고 클라이언트가 원하는 경로를 설치했다는 뜻일 뿐입니다. 한 애플리케이션의 패킷 하나가 그 경로를 사용했는지는 별개의 질문이며 스택의 다른 지점에서 답합니다. 네 가지 측정이 대부분을 해결합니다 — 경로 조회, 첫 홉, 물리 어댑터에서의 캡처, 외부에서의 관찰 — 그리고 서버의 세션 보기가 나머지를 덮습니다.
패킷이 지나갈 경로부터 시작합니다
라우팅 테이블이 곧 결정이므로, 전체를 읽고 추측하는 대신 목적지 하나에 대해 물어봅니다. Linux에서는 ip route get가 커널이 고른 인터페이스와 원본 주소를 알려 줍니다:
ip route get 203.0.113.10
ip route get 10.20.5.7Windows에서는 Find-NetRoute -RemoteIPAddress 203.0.113.10가 route print -4보다 유용한 이유도 같습니다 — 이긴 경로를 보고하며 존재하는 모든 경로를 보고하지 않습니다. 그래도 0.0.0.0 행 때문에 표를 한 번 읽어 보십시오. 거기에 /1 쌍이 있으면 전체 터널 클라이언트가 기본 경로를 다시 쓴 것입니다. OpenVPN의 redirect-gateway def1 옵션은 터널을 통해 0.0.0.0/1과 128.0.0.0/1을 설치하며, 물리적 기본 경로를 삭제하지 않고 최장 접두사 일치로 기존 0.0.0.0/0을 이기므로, 두 기본 경로가 공존하면서도 모든 것이 여전히 터널로 들어갑니다.
두 가지 세부가 표를 거짓말로 만듭니다. 첫째, 기본 경로의 개수가 아니라 일치한 접두사를 보십시오 — 인터페이스 메트릭이나 /1 쌍이 승자를 결정하기 때문입니다. 둘째, 이미 존재하는 연결은 성립된 경로를 유지합니다 — 터널이 올라온 뒤에도 열린 세션은 재설정될 때까지 옛 경로를 계속 쓸 수 있어서, 재연결이 라우팅 문제를 고친 것처럼 보입니다. 실행 중인 클라이언트 — ps ax | grep openvpn — 는 프로세스가 시작되었다는 것만 증명합니다.
내부 이름 하나와 공개 이름 하나를 추적합니다
경로 조회는 의도를 보여 주고, 경로는 전달을 보여 줍니다. 연결된 상태에서 반드시 내부여야 하는 이름과 그래서는 안 되는 이름을 추적하고 첫 홉을 비교합니다:
traceroute intranet.corp.example
traceroute 1.1.1.1둘 모두 같은 첫 홉이면 내부 이름이 터널로 들어가지 않았고 장애는 그 위에 있으며, 보통 공개 주소로 해석되는 이름이거나 설치되지 않은 경로입니다. 내부 트래픽이 10.8.0.1로 나가고 공개 트래픽이 192.168.1.1로 나간다면 분할 터널링은 설정된 대로 동작하는 것입니다. VPN 게이트웨이까지 도달한 뒤 시간 초과만 돌려주는 추적은 보통 반환 경로나 서버 방화벽 문제이며, 빠진 전달 경로가 아닙니다.
필터링된 ICMP는 이 점검을 거짓으로 만들며, 두 추적 모두에 별표가 늘어서면 죽은 터널처럼 보이지만 보통 방화벽입니다. 가능한 곳에서 TCP 포트를 시험하고 — Linux에서는 traceroute -T -p 443 — 추적이 어느 리졸버가 응답했는지는 말하지 않는다는 점을 기억하십시오: 이름은 로컬에서 해석되면서도 터널을 통해 도달할 수 있습니다.
터널이 아니라 물리 어댑터에서 캡처합니다
네트워크가 실제로 무엇을 보는지 보여 주는 유일한 지점은 물리 링크입니다. 터널이 올라온 상태에서 사무실로 가는 세션은 VPN 서버로 향하는 암호화된 페이로드로 나타나야 하며, 10.20.5.7로 가는 연결로 나타나서는 안 됩니다:
sudo tcpdump -ni eth0 udp port 53
sudo tcpdump -ni eth0 host 10.20.0.5첫 번째 필터는 로컬로 나가는 이름 해석을 잡습니다 — 가장 흔하고 가장 조용한 누출로, 페이지는 그대로 로드되고 리졸버가 질의만 본 것입니다. 두 번째는 터널 안에만 있어야 할 내부 주소로 가는 평문 트래픽을 잡습니다. Windows에서는 pktmon가 드라이버 없이 같은 일을 합니다: pktmon filter add -p 53, 그다음 pktmon start --capture --pkt-size 128, 그다음 pktmon stop와 pktmon format pktmon.etl -o capture.txt입니다. 이 플래그는 Windows 빌드마다 조금 다르므로 구문을 믿기 전에 pktmon filter add /?을 확인하십시오.
누출을 찾으려면 물리 인터페이스에서 캡처합니다 — 터널에서의 캡처는 이미 있다고 믿는 패킷을 담고, 서버에서는 원본이 게이트웨이인 읽을 수 없는 외부 패킷을 담습니다. 어느 인터페이스의 바이트 카운터든 패킷을 설명할 뿐 프로그램을 설명하지 않습니다 — 트래픽을 프로세스에 귀속시키려면 소켓을 프로세스에 대응시키는 모니터가 필요합니다.
외부 서비스에 물은 뒤 애플리케이션별로 다시 묻습니다
출구를 증명하는 유일한 점검은 외부에서 온 응답, 즉 자기가 본 원본 주소를 돌려주는 서비스입니다. 인터페이스를 고를 수 있는 셸에서 실행하고 두 번 실행합니다:
sudo curl --interface tun0 https://example.net/ip
sudo curl --interface eth0 https://example.net/ip서로 다른 응답은 두 경로의 증거와 특정 애플리케이션을 시험할 방법을 줍니다 — 애플리케이션을 바인딩하거나 경로가 하나뿐인 네트워크 이름 공간에서 실행하고, 먼 쪽이 어느 주소를 기록하는지 보십시오. 같은 응답은 흥미로운 장애입니다: 바인딩이 효과가 없었거나, 호스트와 먼 쪽 사이의 무언가가 도착하기 전에 원본 주소를 다시 씁니다. Windows에서는 curl --interface가 어댑터의 이름이 아니라 주소를 요구합니다.
VPN 주소를 보고하는 브라우저는 그 브라우저를 증명합니다. 메일 클라이언트, 패키지 관리자, 백업 에이전트, 두 번째 브라우저 프로필은 각각 다른 경로를 탈 수 있습니다 — 특히 프록시 설정이나 분할 터널 경로가 그중 하나를 덮을 때 그렇습니다. 주소 변화 자체는 누출의 증거가 아닙니다 — 로드 밸런서는 본 출구에 따라 다르게 응답할 수 있으므로 페이지가 아니라 기록된 원본 주소를 비교하십시오.
피어를 확인하려면 서버 보기를 사용합니다
서버는 다른 질문에 답합니다 — 피어가 연결되어 있는지, 얼마나 옮겼는지, 세션이 다시 시작되었는지입니다. 서버에서 피어가 살아 있는데 클라이언트 카운터가 거의 움직이지 않는다면, 클라이언트 표가 무엇이라 했든 애플리케이션 트래픽은 터널로 들어가지 않고 있습니다. 아무것도 작동하지 않는다고 보고하는 사용자를 위해 많은 데이터를 옮기는 피어는 다른 조사입니다.
여기에 두 가지 함정이 있습니다. 마스커레이드된 터널은 모든 흐름을 게이트웨이에서 온 것처럼 보이게 하므로 로그와 클라이언트별 집계가 하나의 주소로 무너집니다. 그리고 클라이언트는 로컬 호스트로 취급되므로 누군가 VPN으로 파일을 복사하는지라는 질문에는 서버의 파일 공유 트래픽이나 서버의 호스트 감사가 답합니다 — 터널을 지켜보면 끝점과 용량을 알 수 있지만 파일 이름은 알 수 없습니다.
설계 결정과 누출을 구분합니다
터널 밖의 모든 것이 누출은 아닙니다. 링크 로컬과 브로드캐스트 트래픽은 설계상 로컬에 머물고, 멀티캐스트로 찾은 프린터는 LAN에서 계속 광고하며, DHCP는 로컬로 나가야 합니다. 특정 인터페이스에 바인딩된 애플리케이션은 의도적으로 라우팅 테이블을 우회하며, 방화벽 규칙이나 이름 공간, 정책 경로만이 그것을 바꿉니다.
IPv6는 점검 목록에서 자기 줄을 가질 자격이 있습니다. IPv4만 나르는 터널은 이중 스택 호스트에 두 번째로 쓸 수 있는 경로를 남기고, 이중 스택 목적지는 그것을 선호합니다 — 리졸버가 IPv6로 도달 가능하면 DNS도 포함합니다. IPv4 결과를 완전하다고 보기 전에 어댑터에서 IPv6를 끄거나 터널이 그것을 나르는지 확인하십시오.
점검들이 서로 어긋날 때, 그 어긋남이 보통 장애를 지목합니다:
| What you see | What it means | Next step |
|---|---|---|
| The route picks tun0 but the capture shows cleartext to 10.20.5.7 | Something bypassed the table: a bound app, a policy route or a session older than the tunnel | Reset the flow, check the app bind, check the ip rule list |
| The route picks eth0 for an internal name | The name resolves to a public address, or the route was never installed | Compare the resolved address with the routes the server pushes |
| The capture shows DNS to the LAN resolver | Resolver leak: data is tunneled, lookups are not | Point the resolver inside, or push it from the server |
| All four checks agree and one app still fails | Path selection is fine; suspect MTU, a per-app proxy or the far end | Test the same destination from a known-good client |
작동하는 순서는 이렇습니다 — 연결 전후로 라우팅 테이블을 저장하고, 내부 이름 하나와 공개 이름 하나를 추적하고, 물리 어댑터에서 DNS를 캡처하고, 특정 애플리케이션에 대해 셸에서 출구 주소를 돌려받고, 마지막으로 용량을 위해 서버를 읽습니다. 처음 네 가지가 일치하면 터널은 생각하는 것을 나르고 있고 남은 문제는 그 위에 있습니다. 둘이 어긋나면 어긋난 쌍이 장애이며 아이콘이 아닙니다.