아니요, VpnService 앱이 휴대폰 VPN을 켤 수는 없습니다
앱이 가진 것은 하나의 터널과 그 수명 주기이며, 휴대폰의 VPN 스위치가 아닙니다. 동의는 철회될 수 있고, 한 번에 하나의 VPN만 실행되며, 패킷은 직접 파싱해야 합니다.
잘못된 전제는 VpnService 앱이 시스템 스위치를 뒤집으면 그때부터 휴대폰이 보호된다는 것인데, 실제로 일어나는 일은 더 좁습니다. 앱은 동의를 요청하고, 인터페이스를 만들고, 파일 디스크립터를 통해 패킷을 읽고 쓰며, 중지할 때 인터페이스를 돌려줍니다. 그런 터널은 한 번에 하나만 살아 있을 수 있고, 시스템은 묻지 않고 가져갈 수 있습니다. 사람들이 프로토콜 탓으로 돌리는 혼란스러운 동작의 대부분은 이 소유 모델에서 나옵니다.
모든 수명 주기 전환을 예외 상황이 아니라 설계의 일부로 다루십시오.
속설: 앱이 휴대폰 VPN을 켠다
흐름은 VpnService.prepare() 에서 시작되며 사용자가 수락해야 하는 Intent를 반환하고, establish() 로 이어지지만 이 역시 이후에 앱이 통제하지 못하는 이유로 실패할 수 있습니다. 두 번째 터널을 설정하면 첫 번째 인터페이스가 비활성화되고 시스템 알림이 교체됩니다. 그 순서 어디에도 앱이 기기 전체의 스위치를 소유하지는 않으며, 앱이 소유한 것은 부여받은 디스크립터와 그 위로 도착하는 패킷입니다. 상시 알림조차 앱의 설계가 아니라 시스템에 속합니다.
인터페이스는 빼앗는 것이 아니라 부여되는 것이므로 다른 클라이언트가 언제든 자리를 차지할 수 있습니다. 앱은 이를 반환값이 아니라 콜백으로 알게 됩니다.
앱을 강제 종료한 것이 알림 패널에 보이는 것도 같은 이유로, 시스템은 자기가 무엇을 부여했는지 알릴 뿐 앱이 무엇을 보여주기로 했는지는 말하지 않습니다.
속설: 동의는 한 번만 받는다
시스템이 터널을 다른 클라이언트에게 넘길 때, 베이스 클래스가 onBind() 에서 반환하는 바인더로 취소 트랜잭션을 보냅니다. 그 바인더를 자체 바인더로 교체하는 앱은 취소 트랜잭션을 직접 전달해야 하며, 그렇지 않으면 VpnService.onRevoke() 이 결코 실행되지 않고 서비스는 더 이상 존재하지 않는 디스크립터에 계속 씁니다. 사용자가 승인을 철회할 수도 있고, 시스템은 재시작이나 항상 켜짐 재연결 중에 서비스를 중지할 수 있습니다.
이를 다루는 일은 로그 한 줄이 아니라 구체적인 작업으로, 디스크립터 읽기를 멈추고, 닫고, 연결 플래그를 지우고, 사용자에게 어떤 상태인지 알려야 합니다. 그다음 항상 켜짐 구성에서는 시스템이 서비스를 스스로 되살릴 수 있으므로, 앱이 다시 설정할지, 다시 물을지, 기다릴지 결정합니다.
취소를 상태가 아니라 예외로 다루는 앱은 자신이 쓰는 모든 패킷이 버려지는 동안에도 연결됨을 보고할 것이며, 이는 프라이버시 도구에 가능한 최악의 실패 모드입니다.
속설: 인터페이스가 트래픽을 대신 걸러준다
도착하는 것은 원시 IP입니다. 캡처하거나 필터링한다는 것은 IP, TCP, UDP, IPv6 헤더를 파싱하고 흐름을 추적하며 응답을 디스크립터로 되돌려 쓰는 일이며, 이는 캡처 앱을 시스템 기능이 아니라 작은 네트워크 스택으로 만듭니다. protect() 은 앱 자체의 전송 소켓이 스스로를 캡처하는 대신 자기 터널을 우회하기 위해 존재하며, 터널이 올라온 뒤 생성되는 모든 소켓에서 확인할 가치가 있는 불리언을 반환합니다.
빌더의 허용 및 금지 애플리케이션 목록은 누구의 트래픽이 인터페이스로 들어오는지 결정합니다. 그것들은 인터페이스가 나르는 IP 패킷 너머의 페이로드에 접근을 부여하지 않으며, 어떤 API도 다른 클라이언트의 터널을 노출하지 않고 시스템의 VPN 프로필을 나열하는 API도 없습니다. 네트워크 기능은 어떤 네트워크가 VPN 전송이라고 말할 수 있지만 그것이 누구의 터널인지는 말할 수 없습니다.
다른 앱에 대한 상태 질문이 모순된 답을 받는 것은 그 정보가 그저 여러분의 프로세스에 노출되지 않기 때문입니다.
수명 주기가 실제로 요구하는 것
여러분이 통제하는 상태, 즉 준비됨, 설정됨, 취소됨, 중지됨을 추적하십시오. 재연결은 해체한 뒤 설정하는 것이어야 하며, 설정을 두고 경쟁하는 두 구성 요소가 하나뿐인 빈자리를 다투므로 디스크립터와 전송 소켓의 소유자는 하나여야 합니다. 최신 Android에서는 백그라운드 시작이 제한되어 부팅 브로드캐스트가 보장이 아니므로, API 수준이 허용하는 곳에서는 알림이 있는 포그라운드 서비스를 사용하십시오.
최종 상태만이 아니라 전환을 기록하고, 모든 경로를 시험할 수 있을 만큼 상태 기계를 작게 유지하십시오. 시험에서 도달할 수 없는 상태는 로그 없이 현장에서 만나게 될 상태입니다.
기기에서 수명 주기를 시험하십시오
에뮬레이터는 연결을 속일 수 있지만 동의와 취소 경로는 설득력 있게 속일 수 없으므로, 자리를 빼앗을 두 번째 VPN 클라이언트를 설치한 실제 기기에서 이것들을 실행하십시오.
adb shell dumpsys connectivity | grep -i -A3 vpn
adb shell dumpsys activity services | grep -i -A2 vpnservice
adb logcat -s VpnService:V AndroidRuntime:E
adb shell am force-stop com.example.vpnapp
터널을 시작하고 알림과 인터페이스를 확인한 다음 다른 클라이언트를 연결해 취소 콜백이 도는지, 상태가 지워지는지 보십시오. 앱을 강제 종료하고 아직도 살아 있는 디스크립터를 보고하는 것이 있는지 확인하십시오.
항상 켜짐 재시작 뒤에 같은 순서를 반복하십시오. 그 경로는 사용자가 시작한 연결과 다르므로, 찾아야 할 것은 초록 아이콘이 아니라 앱이 더 이상 소유하지 않은 디스크립터 사용을 멈췄다는 증거입니다.