Android 에뮬레이터와 호스트 VPN: DNS 스냅샷과 NAT 해설
게스트는 에뮬레이터 NAT 뒤에 있고 부팅 시 복사된 DNS 목록을 유지한다. 부팅 전에 터널을 연결한 뒤 게스트 내부에서 해석을 시험하라.
에뮬레이터가 호스트 브라우저에서는 잘 열리는 빌드 서버에 도달하지 못할 때, 유용한 질문은 VPN이 작동하는지가 아니다. 게스트에게 두 메커니즘 중 무엇이 빠져 있는지가 질문이다. 가상 기기는 에뮬레이터 NAT 뒤에서 자체 네트워크 스택을 실행하고, 그 리졸버 목록은 부팅 시점에 복사한 사본이므로 나중에 올라온 터널은 호스트 경로만 바꾸고 게스트가 묻는 대상은 바꾸지 않는다. 이 두 계층을 이해하면 이 문제의 거의 모든 변형이 설명된다.
게스트는 고정 NAT 뒤의 별도 스택이다
QEMU 사용자 모드 네트워킹은 게스트를 10.0.2.15에, 게이트웨이를 10.0.2.2에, DNS 서비스를 10.0.2.3부터 10.0.2.6까지에 둔다. 게스트의 아웃바운드 트래픽은 호스트 커널이 포워딩하지 않고, 게스트 연결마다 하나씩 호스트 측 소켓으로 변환되며 그 소켓은 호스트 주소 공간에서 나간다. 그래서 게스트 연결은 생성되는 순간 호스트의 라우팅 결정을 물려받고, 게스트의 라우트 테이블에는 당신의 터널을 가리키는 항목이 없다.
같은 메커니즘이 게스트에게 유용한 탈출구를 준다. 10.0.2.2는 호스트 루프백 인터페이스의 별칭이므로 노트북의 127.0.0.1에서 수신하는 서비스는 그 주소로 게스트에서 도달할 수 있다. 클라이언트가 루프백에서만 노출하는 것은 무엇이든 이 경로로 도달할 수 있고, 이것이 아래에서 설명하는 프록시 구성의 토대다.
게스트가 할 수 없는 것은 호스트 네트워크를 마치 자신의 네임스페이스인 것처럼 관찰하는 것이다. 브리지도, 공유 라우트 테이블도, 상속된 리졸버도 없고, 패킷을 받아 주는 NAT 서비스와 가상 기기가 시작될 때 런처가 찍은 스냅샷만 있을 뿐이다.
DNS는 스냅샷이지 실시간 뷰가 아니다
시작할 때 에뮬레이터는 호스트 리졸버 목록을 읽고 게스트에게 최대 네 개의 서버를 넘기며, 이는 10.0.2.3부터 10.0.2.6 주소로 노출된다. 그 시점에 터널이 내려가 있었다면 스냅샷에는 노트북이 쓰던 공용 리졸버가 들어 있고, 게스트는 평생 그것에 물어본다. 공용 이름은 해석되고 내부 이름은 실패하며, 모든 증상은 VPN을 가리키지만 실제로 낡은 값은 터널이 올라오기 몇 분 전에 채집된 리졸버 목록이다.
따라서 순서 교정은 속설이 아니다. 먼저 터널을 연결한 다음 에뮬레이터를 다시 시작하고, 스냅샷 복원보다 콜드 부팅을 선호하라. 퀵 부트 이미지는 이전에 저장한 리졸버와 라우트 상태로 돌아올 수 있기 때문이다. 게스트가 지금 무엇을 쓰는지 보려면 호스트가 아니라 기기에 물어보라:
adb shell dumpsys connectivity | grep -i -A2 dns
adb shell ping -c 1 build.internal.example에뮬레이터는 시작 시 명시적 리졸버도 받는다. 게스트가 어느 서버를 써야 하는지 정확히 알 때 유용하고, 그 서버가 공용일 때 위험하다:
emulator -avd Pixel_API_34 -dns-server 203.0.113.53공용 리졸버에는 내부 존의 사본이 없으므로 하나를 고정하면 순서 문제가 영구적 실패로 바뀐다. 프로파일이 사설 DNS를 밀어 준다면 터널이 그것을 제공하게 두고, 주소를 하드코딩하는 대신 게스트를 콜드 부팅하라.
라우팅은 연결 시점에 호스트를 따른다
게스트 연결이 호스트 소켓이 되므로 라우팅 결정은 소켓이 만들어질 때 내려진다. 요청 전에 터널이 올라와 있으면 요청은 프로파일 라우트에 따라 터널로 나간다. 선택한 프리픽스만 운반하는 분할 터널 프로파일은 당신의 사설 서브넷을 기꺼이 밖에 남겨 두므로, 호스트 도달 가능성은 게스트에 대한 가설이지 결과가 아니다. 기존 게스트 연결은 만들어진 경로를 유지하며, 그래서 다시 연결하는 클라이언트가 에뮬레이터 안에서 멈춘 다운로드를 항상 고치지는 않는다.
검증은 게스트 통계가 아니라 호스트 인터페이스에서 한다:
sudo tcpdump -ni tun0 'host 203.0.113.10'게스트 요청이 터널 인터페이스에 나타나면 라우팅이 작동하고 문제는 그 위에 있다. 물리 인터페이스에 나타나면 프로파일이 그 프리픽스를 덮지 않으며, 게스트 쪽 재구성으로는 바뀌지 않는다.
클라이언트가 루프백 프록시만 제공할 때
일부 상용 클라이언트는 시스템 터널을 아예 만들지 않는다. 127.0.0.1에서 HTTP 또는 SOCKS 리스너를 노출하고 애플리케이션이 그것을 쓰기를 기대한다. 게스트는 그 소켓을 직접 볼 수 없지만 에뮬레이터에게 쓰라고 할 수는 있다. 확장 컨트롤(Settings, 그다음 Proxy, 수동 모드, 호스트 127.0.0.1과 클라이언트가 출력한 포트)을 통해서거나 시작 시다:
emulator -avd Pixel_API_34 -http-proxy http://127.0.0.1:8080포트는 클라이언트 창에서 복사하라. 추측한 포트는 조용히 실패하고 고장 난 터널처럼 보인다. 이것이 무엇을 덮는지 기억하라. HTTP 프록시 설정은 프록시를 인식하는 애플리케이션이 존중하고, 원시 소켓은 완전히 무시한다. 작업 부하가 스스로 연결을 여는 네이티브 바이너리라면 프록시 경로는 터널과 동등하지 않으며 비교는 명시적으로 해야 한다.
결과는 게스트 내부에서 증명하라
같은 세션에서 두 가지 테스트로 해석과 라우팅을 분리하라. 숫자 주소는 응답하는데 이름이 실패하면 DNS 스냅샷 문제이고 해법은 순서나 명시적 리졸버다. 둘 다 실패하면 라우팅이나 정책을 가리키며, 다음 확인은 앱이 자체 네트워크 제한이 있는 작업 프로파일 안에서 실행되는지다.
마지막으로 어느 게스트에서 시험하는지 알아 두라. 아주 오래된 에뮬레이터 이미지는 게스트 측 VPN 클라이언트에 필요한 tun 지원 없이 출하되어 어떤 프로파일도 작동할 수 없었다. 현재 이미지에는 그것이 포함된다. 현재 이미지에서 실패하는 클라이언트는 클라이언트나 프로파일 문제이며, 호스트 터널을 다시 만들어도 결과는 바뀌지 않는다.