Android에 VPN이 연결되어 있는지 물어볼 수는 없습니다. VpnService에는 연결 상태를 반환하는 getter가 없고, 상태 표시줄의 열쇠 아이콘은 시스템이 그리는 것이지 앱이 읽을 수 있는 무엇도 아닙니다. 할 수 있는 일은 연결 스택에 더 좁은 질문을 던지는 것입니다. 이 앱의 트래픽은 어느 네트워크를 쓰는지, 그 네트워크가 VPN 전송을 나르는지. 이 글은 오탐을 피하는 순서로 점검을 안내합니다. 기기의 셸 명령에서 앱에 남겨 두는 콜백까지입니다.

앱 코드를 건드리기 전에 셸에서 기기를 확인하십시오

먼저 기준 상태를 잡으십시오. adb shell dumpsys connectivity은 시스템이 아는 모든 네트워크를 출력하고, 각 블록은 자기 전송 목록을 나열하므로 VPN 네트워크는 그 목록에 VPN과 함께 나타납니다. 무엇보다 먼저 기본 네트워크 블록을 읽고, 그다음 같은 전송을 나르는 다른 네트워크를 찾으십시오.

두 출력은 서로 다른 질문에 답합니다. VPN 네트워크가 존재하는지와 앱이 그것을 쓰는지는 같지 않으며, 업무 프로필이나 두 번째 사용자가 있는 기기에서는 프로세스가 인터넷과 직접 통신하는 동안 둘 다 동시에 참일 수 있습니다. 소켓의 동작을 결정하는 것은 활성 네트워크 줄입니다.

앱이 사용하는 네트워크를 물어보십시오

코드에서 기능 검사는 네 줄입니다. 매니페스트에서 ACCESS_NETWORK_STATE을 요청한 다음 활성 네트워크의 기능을 VPN 전송과 비교하십시오:

ConnectivityManager cm = (ConnectivityManager)
    context.getSystemService(Context.CONNECTIVITY_SERVICE);
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean vpnInUse = caps != null
    && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);

답은 의도적으로 좁습니다. 앱 기본 네트워크가 지금 VPN인가. 어떤 VPN인지, 내 것인지, 터널이 저쪽 끝까지 닿는지는 말하지 않습니다. true을 “트래픽이 어떤 터널로 나간다”로 해석하고 자체 기록은 따로 두십시오. 세 가지를 조용히 동시에 뜻하는 불리언은 사용자가 VPN으로 탐색하는 중에 설정 화면이 VPN을 꺼짐으로 보고하게 되는 이유입니다.

반대로 어떤 VPN 네트워크든 존재하는지 알아야 할 때는 cm.getAllNetworks()을 순회하며 각 기능 집합을 검사하십시오. 두 답은 다른 앱은 덮지만 내 앱은 덮지 않는 앱별 VPN이 도는 기기에서 갈라지며, 그 갈라짐이 분명히 켜진 VPN을 앱이 못 본다는 버그 보고의 대부분의 원인입니다.

폴링 대신 변화를 감시하십시오

타이머 폴링은 이 질문에 맞지 않는 형태입니다. 상태 변화는 이벤트로 도착하기 때문입니다. VPN 전송에 맞는 요청을 등록하고, 일치하는 네트워크가 나타나거나 사라지거나 바뀔 때 프레임워크가 알려 주게 하십시오:

NetworkRequest request = new NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();
cm.registerNetworkCallback(request, callback);

VPN 네트워크는 NET_CAPABILITY_NOT_VPN을 절대 나르지 않으므로 명시적 제거가 의도를 문서화합니다. 없어도 요청은 일치하지만, 다음 사람이 코드를 읽을 때 스스로 알아내야 합니다.

등록은 관찰이 필요한 범위에 속하며 보통 화면이나 포그라운드 서비스입니다. onStop이나 onDestroy에서 해제하고, 이미 사라진 액티비티에 콜백을 붙여 두지 마십시오. 보이는 컴포넌트가 없는 앱은 상시 관찰자의 좋은 주인이 아니며, 잊은 콜백이야말로 구성 변경 뒤에 충돌하는 것입니다.

세 가지 질문을 분리하십시오

이 영역의 혼란 대부분은 서로 다른 세 검사를 하나로 취급하는 데서 옵니다. 서로 다른 API를 쓰며, 하나가 참인 동안 나머지는 거짓일 수 있습니다.

QuestionHow you answer itWhat a yes means
Did the user grant VPN consent to my app?VpnService.prepare() returns nullConsent exists; the tunnel may still be down
Is the device sending default traffic through a VPN?TRANSPORT_VPN on the active networkSomething is tunneling, not necessarily your app
Did this request arrive through the VPN?Source address seen by the serverProof for that connection, nothing wider

셋째 줄은 사람들이 클라이언트에서 답하려는 것이지만 거기서는 답할 수 없습니다. VPN 클라이언트에 전용 주소 풀을 주고 서버에서 출발지 주소를 읽으십시오. 세션 도중 사용자가 터널을 바꿔도 살아남는 유일한 증거입니다.

오탐을 배제하는 검증 순서

아직 유통되는 오래된 예제 코드는 getNetworkInfo(ConnectivityManager.TYPE_VPN)을 호출합니다. 그 호출은 폐기되었고, 여러 릴리스에서 VPN이 연결된 동안에도 null을 반환했습니다. 복사된 코드 조각이 잘 동작하는 기기에서 고장 나 보이는 이유입니다. 기능 검사는 그 함정을 피하고 Android 5.0 이후 동일하게 동작합니다. 아직 그 호출을 쓰는 코드베이스를 유지한다면 폴백으로 감싸지 말고 호출 자체를 교체하십시오. 잘못된 답을 만든 것이 바로 그 폴백이기 때문입니다.

한 가지 지름길에는 경고가 필요합니다. 외부 서비스가 보고하는 공인 주소와 VPN 출구 주소를 비교하는 것은 경로에 대해 무언가를 증명하지만, 검사마다 서드파티 요청을 더하고, 그 서비스가 계속 살아 있어야 하며, 캐시되거나 차단될 수 있습니다. 수동 진단으로만 두고 앱 안의 상태 검사로는 절대 쓰지 마십시오.

이 순서로 검사를 실행하고 사용자 보고와 어긋나는 첫 지점에서 멈추십시오. 셸 전송 목록, 활성 네트워크 기능, 터널을 켜고 끌 때의 콜백 이벤트, 그다음 서버 측 출발지 주소입니다. 넷이 모두 맞는데도 앱이 오작동한다면 버그는 Android의 네트워크 인식이 아니라 여러분의 상태 기계에 있습니다.

관련 글