Android 앱이 VPN 밖에서 이름을 확인하는 경우: 바인딩 점검 목록
VPN이 켜져 있어도 Android는 기본 네트워크로 이름을 확인할 수 있습니다. 프로세스를 VPN 네트워크에 바인딩하고 앱이 실제로 어떤 리졸버를 사용했는지 확인하세요.
VPN 클라이언트 자체의 브라우저는 내부 이름에 도달하는데 Android 앱만 이름을 확인하지 못하고, 같은 빌드가 다른 휴대폰에서는 동작한다. URL은 변수가 아니다. 변수는 앱 프로세스가 이름 확인에 사용하도록 허용된 네트워크이며, Android는 활성 VPN이 소켓이 사용하는 네트워크임을 보장하지 않는다. 다음 점검을 순서대로 수행하고 리졸버 스냅숏이 가정과 어긋나는 지점에서 멈춘다.
1단계: 앱이 어느 네트워크에 있는지 증명한다
상태 표시줄이 아니라 앱 자체의 뷰를 읽는다. ConnectivityManager에서 활성 네트워크, 그 LinkProperties, DNS 서버 목록, 비공개 DNS 설정을 가져온다. 목록에 VPN의 리졸버가 있으면 앱에 그 경로가 있다. Wi-Fi의 리졸버가 있으면 그 프로세스에서 터널은 기본 네트워크가 아니다.
VPN 아이콘은 터널이 존재한다는 것만 말한다. 앱 프로세스가 그 터널에 바인딩되었는지는 말하지 않으며, Android는 항상 활성으로 보이는 네트워크로 이름 확인을 라우팅하지 않는다. 다른 기기로 전달하는 로컬 VPN 앱, 인터넷이 없는 Wi-Fi 네트워크, 벤더 빌드는 모두 앱이 터널 서버 대신 기본 리졸버에 질의하도록 남길 수 있다.
2단계: VPN 전송을 요청하고 프로세스를 바인딩한다
앱이 터널의 리졸버를 반드시 써야 한다면 VPN 전송을 가진 네트워크를 요청하고 콜백에서 프로세스를 바인딩한다. 그러면 코드가 네트워크가 언제 나타나는지 추측하지 않아도 된다:
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.build()
connectivityManager.requestNetwork(request, object : NetworkCallback() {
override fun onAvailable(network: Network) {
val bound = connectivityManager.bindProcessToNetwork(network)
Log.i(TAG, "bound to VPN network: " + bound)
}
})
이 호출은 프로세스 전체의 기본 네트워크를 바꾸므로 그 뒤에 생성되는 모든 소켓이 그것을 사용한다. 이미 존재하는 소켓은 생성된 네트워크를 유지하므로, 바인딩 전에 열린 연결 풀이 예전 경로로 계속 이름을 확인할 수 있다. 반환 값은 boolean이고 false는 바인딩이 일어나지 않았다는 뜻이다. 짧은 지연을 두고 다시 시도하고 두 시도를 모두 기록한다.
3단계: 바인딩을 필요한 범위로 좁힌다
프로세스 전체 바인딩은 거칠다. 텔레메트리, 업데이트 확인, 서드파티 SDK도 터널로 옮겨 가고, 터널에 인터넷이 없으면 그것들이 실패하기 시작한다. 단일 요청에는 네트워크의 소켓 팩토리를 쓰고 나머지는 기기의 기본값으로 둔다. 연결마다 좁은 바인딩을 할지, VPN이 사라질 때 명시적으로 해제하는 프로세스 바인딩을 할지 의도적으로 하나를 고른다.
리졸버의 두 기능은 오류 없이 바인딩을 무력화할 수 있다. 엄격 모드의 비공개 DNS는 쿼리를 네트워크 리졸버 대신 이름 있는 DoT 서버로 보내고, 자체 DNS-over-HTTPS 클라이언트를 포함한 앱은 시스템 리졸버를 완전히 우회한다. 바인딩이 실패했다고 결론 내리기 전에 둘 다 확인한다. VPN의 DNS 서버에 결코 도달하지 않는 쿼리는 터널 쪽 로그 어디에도 나타나지 않는다.
4단계: CONNECTED 제약을 신뢰하지 않는다
WorkManager의 NetworkType.CONNECTED은 네트워크가 연결되었다는 뜻이고, VPN 전송은 그 아래의 Wi-Fi나 셀룰러 링크가 사라져도 이를 충족한다. 작업은 실행되고 모든 요청이 실패하며, 재시도 정책이 그것을 스케줄링 버그처럼 보이는 폭풍으로 만든다. 터널은 네트워크다. 그 제약은 인터넷 확인이 아니었다.
작업이 실제로 필요로 하는 기능, 예컨대 검증된 인터넷 접근을 확인하고 백오프가 있는 제한된 재시도 정책을 유지한다. 작업에 비공개 이름이 필요하면 작업을 수행하기 전에 선택한 네트워크에서 그 이름을 확인하고, 성공할 수 없는 요청을 재시도하는 대신 명확한 오류로 빠르게 실패한다.
5단계: 증거를 기록하고 범위를 다시 테스트한다
선택한 네트워크의 전송 유형, LinkProperties에서 가져온 DNS 서버, 바인딩이 true를 반환했는지, 첫 요청의 정확한 실패를 기록한다. 그다음 같은 바인딩 아래에서 비공개 이름과 공개 이름을 실행한다. 그 쌍은 앱이 터널 리졸버를 썼는지 아니면 조용히 기기 기본값으로 되돌아갔는지를 보여 주며, 그것이 사용자가 경험하는 차이다.
지원하는 가장 오래된 Android 버전과 현재 릴리스 하나에서 반복한다. 기본 경로를 올바르게 보이게 만드는 리졸버 동작은 기기와 버전이 갈리는 바로 그 지점이며, 최신 테스트 기기에서만 통과하는 빌드는 출시하는 범위에 대해 검증되지 않은 것이다. adb shell dumpsys connectivity는 실패를 재현하는 동안 기기가 어떤 네트워크와 기능을 보고하는지 빠르게 확인하는 방법이다.