Android 상시 연결 VPN: 엔드포인트 이름이 먼저 해석되는 방식
Android는 터널이 생기기 전에 트래픽을 차단하지만, 클라이언트는 여전히 자신의 엔드포인트를 해석해야 합니다. 그 순서와 막히는 지점을 설명합니다.
휴대폰이 재부팅되고 상시 연결이 켜져 있고 «VPN 없이 연결 차단»도 켜져 있는데 터널은 올라오지 않습니다. 잘못 설정된 것은 없습니다. 차단이 제 역할을 하고 있을 뿐이고, 클라이언트 자신이 엔드포인트에 도달하려는 시도도 그 차단의 대상입니다. 엔드포인트는 터널이 존재하기 전에, 다른 모든 앱은 허용되지 않는 동안 네트워크와 통신할 수 있는 클라이언트에 의해 해석되어야 합니다. 어떤 구성 요소가 그것을 하고 어떤 순서로 하는지 이해하면 이 수수께끼는 설정 문제가 됩니다.
VPN 빌더가 실제로 요구하는 것
Android VPN 앱은 플랫폼에 서버 주소를 넘기지 않습니다. 로컬 인터페이스를 만듭니다. 터널용 주소와 경로, 그 안에서 앱이 사용해야 할 리졸버를 지정하고, 그다음 establish()가 파일 디스크립터를 반환하면 앱이 이를 원시 IP 패킷으로 읽고 씁니다. 원격 엔드포인트는 그 빌더에 전혀 나타나지 않습니다.
이것이 핵심 구조적 사실입니다. 플랫폼은 tun 인터페이스를 만들고 그 위에 정책을 적용합니다. 앱은 사용자 공간에서 전송을 구현하므로, 엔드포인트 이름 해석과 암호화된 연결 개시도 앱 자신이 합니다. 따라서 호스트 이름 엔드포인트가 가능한 것은 플랫폼이 이름을 지원해서가 아니라, 플랫폼이 엔드포인트를 전혀 보지 않기 때문입니다.
빌더에서 주소를 받는 필드는 하나뿐입니다. DNS 서버입니다. 이는 숫자 리졸버 주소여야 하며, 보통 운영자가 터널을 통해 조회하려는 주소입니다. 이 필드는 터널이 올라온 뒤 앱이 쿼리를 어디로 보내는지를 설명합니다. 클라이언트가 엔드포인트를 어떻게 찾는지는 말하지 않으며, 이 둘을 같은 설정으로 취급하는 지점에서 혼란이 시작됩니다.
DNS 질문은 하나가 아니라 둘
첫 번째 질문은, 터널이 올라온 뒤 앱이 쿼리를 어디로 보내는가입니다. 답은 터널 내부의 리졸버 주소이며, tun 인터페이스를 통해 도달할 수 있고, 구조상 숫자입니다.
두 번째 질문은, 그 터널이 존재하기 전에 클라이언트가 자신의 엔드포인트를 어떻게 찾는가입니다. 그 조회는 터널 밖, 하위 네트워크에서, 클라이언트 프로세스 안에서 일어납니다. 이름을 쓸 수 있으며, VPN 없이 기기가 도달할 수 있는 리졸버를 써야 합니다.
| Stage | Who resolves | Path | Failure looks like |
|---|---|---|---|
| Endpoint lookup | The VPN app | Underlying network, protected socket | Never connects; no tunnel interface traffic |
| Peer authentication | Client and server | Tunnel transport | Connects then drops; certificate or key error |
| Private name lookup | Apps via the tunnel | Resolver inside the tun | Tunnel up, internal sites do not resolve |
세 단계, 세 가지 다른 증상. «연결할 수 없음»이라는 하나의 오류 메시지는 이들을 한데 뭉개며, 그래서 사람들은 그 필드가 참조되기도 전에 일어나는 문제를 고치려고 DNS 서버 필드를 편집하게 됩니다.
부트스트랩이 교착되는 이유
상시 연결에 차단이 켜져 있다는 것은 VPN 안에 있지 않은 트래픽이 버려진다는 뜻이며, VPN을 소유한 앱의 트래픽도 포함됩니다. 탈출구는 명시적입니다. 앱이 소켓에 protect()를 호출하면 소켓이 하위 네트워크에 묶여 터널을 우회하고 나갈 수 있게 됩니다. 모든 VPN 클라이언트는 자기 전송을 위해 이렇게 해야 하며, 그렇지 않으면 서버에 도달할 수 없습니다.
이제 엔드포인트 이름을 아는 유일한 리졸버가 터널 내부에 있는 배포를 생각해 봅시다. 클라이언트는 이름을 해석하기 위해 터널이 필요하고, 터널을 만들기 위해 이름이 필요합니다. 이는 진짜 순환 의존이며 어떤 클라이언트 설정으로도 풀리지 않습니다. 버그가 아니라 설계 오류입니다.
타이밍은 같은 의존을 더 온화한 형태로 드러냅니다. 부팅 시 첫 잠금 해제 전에는 기기에 아직 쓸 수 있는 네트워크가 없을 수 있습니다. 캡티브 포털에서는 네트워크가 있지만 포털 외의 모든 것을 차단합니다. Wi-Fi에서 셀룰러로 전환할 때는 보호된 소켓의 하위 네트워크가 바뀌므로 클라이언트가 이를 알아채고 다시 해석해야 합니다. 각각이 부트스트랩 사건이며, 새벽 3시에 발견하기보다 의도적으로 시험해 볼 가치가 있습니다.
답을 고정하는 것이 해결이 아닌 이유
유혹적인 우회는 이름을 한 번 해석해 나온 주소를 숫자 필드에 붙여 넣거나, 맞춤 빌드에 하드코딩하는 것입니다. 되는 것처럼 보이고, 이름을 스냅숏으로 바꿔 놓습니다. 운영자가 엔드포인트를 옮기면, 마이그레이션, 애니캐스트 변경, 새 지역 주소 같은 이유로 클라이언트는 계속 옛 주소로 접속하고 UI 어디에도 이유가 설명되지 않습니다.
DNS 레코드에 TTL이 있는 것은 바로 답이 바뀔 수 있기 때문입니다. 캐싱은 그 수명을 기준으로 정의되며 영원하지 않습니다. 답을 고정하면 이름을 주소보다 낫게 만드는 메커니즘을 버리면서, 이름을 썼기 때문에 생기는 운영 비용은 그대로 안고 갑니다.
고정은 대개 두 번째 나쁜 습관과 함께 다닙니다. 이제 연결이 다르게 실패한다는 이유로 신원 검사를 느슨하게 하는 것입니다. 서버 신원 검증은 부트스트랩 문제의 일부가 아니며, 이를 약화하면 DNS 불편을 가로채기 위험과 맞바꾸게 됩니다.
재부팅을 견디는 설계
오래 가는 구성은 화려하지 않습니다. 보호 소켓을 올바르게 구현하고 엔드포인트를 하위 네트워크에서 해석하는 클라이언트를 쓰고, 공개적으로 또는 터널 전에 도달 가능한 리졸버를 통해 해석되는 엔드포인트 이름을 주십시오. 차단은 켜 둡니다. DNS가 동작하게 하려고 이를 전면 비활성화하면 그 배포가 존재하는 이유인 속성을 없애게 됩니다.
엔드포인트를 비공개로 유지해야 하는 곳에서는 플랫폼이 쓸 수 있는 명시적 부트스트랩 경로를 마련하십시오. 터널 없이 도달 가능한 리졸버, 또는 운영자가 유지하는 관리 연결입니다. 그리고 그 예외는 좁게 유지하고 사용자 토글이 아니라 정책이 소유하게 하십시오. Android VpnService 문서는 보호와 수명 주기에 관해 플랫폼이 무엇을 보장하는지에 대한 참조이며, VpnService.Builder 참조는 DNS 필드가 숫자 주소를 받는다는 것을 확인해 줍니다.
그런 다음 재부팅 후 새 연결에서 세 단계를 순서대로 검증하십시오. 엔드포인트 이름이 해석되고, 터널이 수립되어 피어가 인증되며, 비공개 이름이 터널의 리졸버를 통해 해석되는지입니다. 첫 단계가 실패하면 이후는 아무것도 동작할 수 없고, 수정은 부트스트랩 경로에 속합니다. 결코 리졸버 필드가 아닙니다.
- 엔드포인트 해석은 앱 안에서, 터널 밖에서, 보호된 소켓 위에서 일어납니다.
- 빌더의 DNS 필드는 터널 안의 앱을 위한 것이지 클라이언트 자신의 엔드포인트를 위한 것이 아닙니다.
- 터널을 통해서만 도달할 수 있는 리졸버는 느린 시작이 아니라 교착입니다.
- 재부팅 후 엔드포인트, 인증, 비공개 조회를 각각 따로 시험하십시오.