Android, iOS, Windows에서 ping 없이 VPN 상태를 확인하는 방법
ping은 호스트 하나가 응답했다는 것만 증명할 뿐, 터널이 살아 있다는 뜻은 아닙니다. Android, iOS, Windows의 상태 API에 물어보고, 앱이 실제로 필요로 하는 경로를 검증하세요.
192.0.2.10에 ping을 보내면 한 주소가 ICMP 에코에 응답했다는 것만 알 수 있고, 그 이상은 알 수 없습니다. 터널이 없는 네트워크에서도 성공할 수 있습니다. 주소가 로컬 서브넷에 있을 수 있기 때문입니다. 반대로 건강한 터널에서도 실패할 수 있습니다. 서버가 HTTPS를 제공하면서 ICMP를 버리기 때문입니다. 스크립트에 필요한 검사는 플랫폼 상태 조회와 앱이 사용하는 목적지에 대한 경로 테스트입니다. 이 둘은 별개의 필드로 다루고, 절대 하나의 불리언으로 합치지 마세요.
에코 응답이 실제로 증명하는 것
ICMP는 인터페이스나 터널의 개념이 없는 도달 가능성 프로토콜입니다. 응답이 도착하면 커널은 어떤 호스트가 응답했다고 보고하지만, 요청을 실어간 경로나 응답을 실어온 경로는 보고하지 않습니다. 필터링은 신호를 더 시끄럽게 만듭니다. 에코 요청을 막는 방화벽은 그 뒤의 포트에 대해 아무것도 말해주지 않기 때문입니다. 또한 분할 터널 프로필은 다른 모든 목적지가 동작하더라도 테스트한 주소를 의도적으로 제외할 수 있습니다.
정말로 알고 싶은 것은 특정 서비스가 앱이 지나는 경로에서 쓸 수 있는지입니다. 앱이 호출하는 호스트와 포트를 고르고, 애플리케이션 계층에서 응답을 요구하세요.
도달 가능성과 터널 상태는 서로 다른 사실이며, 하나가 참인 동안 다른 하나는 거짓일 수 있습니다. 둘을 분리해 두면 장애가 누구 몫인지 알 수 있습니다. 둘을 하나의 검사로 합치면 아무도 라우팅할 수 없는 티켓이 만들어집니다.
Android에는 ping이 아니라 전송 방식을 물어보세요
Android는 ConnectivityManager를 통해 상태 질문에 직접 답합니다. 활성 네트워크의 기능에는 VPN 전송 플래그가 있으며, 트래픽이 VPN 인터페이스로 라우팅될 때 설정됩니다. 이 호출은 로컬이고 값싸며, 원격 호스트가 응답하는지와 무관합니다.
이 Java 버전은 활성 네트워크가 없을 때 예외를 던지지 않고 false를 반환합니다. 활성 네트워크를 읽기 때문에 사용자가 설정에서 고른 프로필을 반영합니다.
ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
NetworkCapabilities caps = cm.getNetworkCapabilities(cm.getActiveNetwork());
boolean tunnel = caps != null && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
변경 알림에는 타이머로 폴링하는 대신 NetworkCallback을 등록하세요. 이것을 VpnService.prepare와 혼동하지 마세요. 후자는 사용자가 VPN 권한을 허용했는지만 알려주고 패킷이 흐르는지는 알려주지 않습니다. 권한 취소는 VpnService.onRevoke로 전달되지만, 설정에서 프로필을 잊어버리면 브로드캐스트가 전혀 발생하지 않습니다.
iOS: 인터페이스 이름은 경험칙이지 계약이 아닙니다
많은 iOS 코드 조각이 프록시 설정을 검사하고 __SCOPED__ 사전을 찾은 뒤 tap, tun, utun2, ppp, ipsec 같은 인터페이스 키를 찾습니다. 이 접근은 명명 관례일 뿐 문서화된 보장이 아니므로, 인터페이스 이름을 바꾸는 OS 업데이트가 이를 깨뜨릴 수 있습니다. 또한 터널보다 오래 살아남는 오래된 항목에 true를 보고하는 경향이 있습니다.
앱이 구성을 소유한다면 대신 NEVPNManager가 실제 상태를 줍니다. 앱이 시작한 터널을 설명하며, 앱별 VPN에서는 다른 앱이 그 터널 완전히 밖에 있을 수 있습니다.
상태를 읽기 전에 환경설정을 다시 로드하세요. 터널이 올라오고 내려갈 때 값이 바뀌기 때문입니다.
let manager = NEVPNManager.shared()
manager.loadFromPreferences { error in
let up = manager.connection.status == .connected
print(up)
}
그래도 플랫폼 상태 플래그는 트래픽이 피어에 도달한다는 것을 증명하지 않습니다. 그 틈을 메우는 것이 경로 테스트이며, 그래서 지원에는 하나의 불리언으로 충분하지 않습니다.
Windows 프로필은 실제 상태 필드를 노출합니다
Windows는 VPN 프로필을 어댑터와 별도로 추적하며, Get-VpnConnection은 알고 있는 각 프로필의 ConnectionStatus를 노출합니다. Windows 클라이언트나 프로비저닝 패키지로 만든 프로필이 여기에 나타납니다. 이 cmdlet은 범용 터널 탐지기가 아닙니다.
이름이 있는 프로필의 필드를 읽고, 프로필이 모든 사용자에게 속하면 -AllUserConnection을 추가하세요.
Get-VpnConnection -Name "Fabrikam" | Select-Object Name, ConnectionStatus, ServerAddress
타사 클라이언트는 터널이 살아 있어도 이 cmdlet에 보이지 않는 경우가 많으므로 빈 결과는 알 수 없음을 뜻하며 연결 끊김이 아닙니다. 어댑터 목록도 똑같이 오해를 부릅니다. 공급업체 어댑터는 유휴 상태로 남을 수 있고, 일부 어댑터는 세션 중에만 존재합니다. Windows가 소유한 프로필이라면 rasdial이 분기할 수 있는 종료 코드를 보고합니다. 이 모든 것은 로컬의 믿음을 설명할 뿐 피어 도달 가능성은 아닙니다.
상태 더하기 경로: 두 단계 검사
연결된 프로필은 패킷이 어디로 가는지에 대해 아무것도 말하지 않습니다. 분할 터널링은 터널을 살려 두면서 관심 있는 목적지를 제외할 수 있습니다. always-on 프로필은 죽은 서버와도 연결된 채로 남고, kill switch는 터널이 끊기면 의도적으로 트래픽을 막습니다.
두 계층을 모두 테스트하세요. VPN 전송이나 프로필이 활성인지 확인한 다음, 목적지가 그 경로로 해석되고 응답하는지 확인하세요. Linux에서는 커널에 어느 인터페이스를 쓸지 물어보세요.
ip route get 192.0.2.10
resolvectl query intranet.example.test
클라이언트를 통제할 수 없다면 두 결과를 모두 기록하고 패턴이 계층을 가리키게 하세요. 연결됨에 경로 테스트 실패는 분할 터널링, DNS, 정책을 시사합니다. 연결 끊김에 경로 테스트 성공은 다른 경로가 트래픽을 나르고 있다는 뜻입니다.
순서대로 실행할 수 있는 테스트
네트워크 상태를 바꾸기 전에 먼저 플랫폼 상태를 기록하세요. 호스트 이름을 해석하고 주소 체계를 적어 두세요. AAAA 레코드가 A 레코드와 다른 경로를 탈 수 있기 때문입니다. 서비스 포트를 열고 단순한 핸드셰이크가 아니라 애플리케이션 응답을 요구하세요. 각 관찰은 별도의 로그 필드에 남기세요.
dig와 curl이 있는 Linux 머신이라면 세 개의 명령으로 이름 해석, 경로, 서비스 자체를 다룰 수 있습니다. 터널을 올린 상태와 내린 상태에서 각각 실행하세요.
dig +short intranet.example.test
ip route get 198.51.100.20
curl -sS -o /dev/null -w "%{http_code}" --max-time 8 https://intranet.example.test/health
네 개의 별도 필드가 하나의 불리언보다 낫습니다. 상태, DNS, 경로, 정책은 각기 독립적으로 실패하며 서로 다른 팀에 떨어지기 때문입니다. 터미널의 ping은 여전히 수동 점검으로 훌륭합니다. 자동화에는 형편없는 연결성 정의이며, 거기서는 플랫폼 API와 경로 테스트가 실제 질문에 답합니다.