Как проверить состояние VPN без ping в Android, iOS и Windows
Ping доказывает лишь то, что один хост ответил, а не то, что туннель работает. Опросите API состояния Android, iOS и Windows, а затем проверьте маршрут, который действительно нужен вашему приложению.
Ping до 192.0.2.10 сообщает только то, что один адрес ответил на эхо ICMP, и ничего больше. В сети без туннеля он может всё равно пройти, потому что адрес может находиться в локальной подсети; при здоровом туннеле он может не пройти, потому что сервер отбрасывает ICMP, пока обслуживает HTTPS. Проверка, которая нужна вашему скрипту, — это запрос состояния платформы плюс тест маршрута к назначению, которое использует приложение. Рассматривайте их как два отдельных поля, никогда как одно логическое значение.
Что на самом деле доказывает ответ на эхо
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. Этот подход — соглашение об именовании, а не документированная гарантия, поэтому обновление системы, переименовывающее интерфейс, может его сломать. Он также склонен сообщать 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 или пакет подготовки. Этот командлет не является универсальным детектором туннелей.
Прочитайте поле для именованного профиля, добавив -AllUserConnection, когда профиль принадлежит всем пользователям:
Get-VpnConnection -Name "Fabrikam" | Select-Object Name, ConnectionStatus, ServerAddress
Сторонние клиенты часто остаются невидимыми для этого командлета даже при работающем туннеле, поэтому пустой результат означает «неизвестно», а не «отключено». Списки адаптеров вводят в заблуждение так же: адаптер поставщика может сохраняться, пока он неактивен, а некоторые адаптеры существуют только во время сеанса. Для профиля, которым владеет Windows, rasdial сообщает код выхода, по которому можно ветвиться. Всё это описывает локальное представление, а не достижимость пира.
Состояние плюс маршрут: проверка из двух частей
Подключённый профиль ничего не говорит о том, куда идут пакеты. Раздельный туннель может держать туннель поднятым, исключая нужное вам назначение. Профили always-on остаются подключёнными при мёртвом сервере, а kill switch намеренно блокирует трафик, когда туннель падает.
Проверьте оба уровня: подтвердите, что транспорт VPN или профиль активен, затем подтвердите, что назначение разрешается и отвечает по этому пути. В Linux спросите ядро, какой интерфейс оно использовало бы:
ip route get 192.0.2.10
resolvectl query intranet.example.test
Когда клиент вне вашего контроля, фиксируйте оба результата и позволяйте закономерности указать на уровень. Подключено и неудачный тест маршрута указывает на раздельный туннель, DNS или политику. Отключено и работающий тест маршрута означает, что трафик несёт другой путь.
Тест, который можно выполнить по порядку
Сначала зафиксируйте состояние платформы, прежде чем менять любое состояние сети. Разрешите имя хоста и отметьте семейство адресов, потому что запись AAAA может пойти иным путём, чем запись A. Откройте порт службы и требуйте ответа приложения, а не простого рукопожатия. Храните каждое наблюдение в своём поле журнала.
На машине с Linux, dig и curl три команды покрывают разрешение имени, маршрут и саму службу. Запустите их с поднятым туннелем и снова с опущенным:
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 платформы и тест маршрута отвечают на настоящий вопрос.