«Включён ли VPN?» — на самом деле три вопроса в одном предложении. Какой из них нужен вашему коду — это первое решение, и именно его пропускает большинство реализаций. Есть ли на этом устройстве туннель, уходит ли трафик к этому адресату через него или дальний конец отвечает той личностью, которую вы ожидаете. Выберите свойство, затем сигнал, который его измеряет, и в конце запишите в журнале, какое именно вы измерили.

Три свойства, три сигнала

Сжатые до одной строки каждое, три вопроса имеют три разных ответа. Таблица ниже — это само решение, а не его краткое изложение.

PropertySignal that answers itWhat it still leaves open
ExistenceThe platform's own connection state: RAS on Windows, NetworkManager on D-Bus on Linux, scutil on macOSTunnels of a type the platform does not manage, which includes most third-party clients
PathA route lookup for the one destination, or a scoped proxy or interface key that names the tunnelWhether the far end is the peer you trust
IdentityAn authenticated response from the far end, or a source-address policy applied on the serverWhich local interface carried the packets

Прочитайте последнюю строку медленно. Отвечающий частный хост доказывает, что что-то ответило, а не то, что ответ пришёл через туннель, и он ничего не говорит о том, кто на другом конце. Если нужное свойство — это путь, измеряйте путь.

Существование: собственное состояние платформы

Windows отвечает на это для родных профилей через Remote Access Service API. RasEnumConnections перечисляет, что подключено, RasGetConnectStatus сообщает состояние одной записи, а RasGetLinkStatistics добавляет скорость, счётчики байтов и возраст соединения. Ограничения и есть суть: OpenVPN, WireGuard и клиенты производителей никогда не регистрируют соединение RAS, поэтому исправный туннель такого типа показывает пустой список, и наивный код сообщает, что он выключен.

Рабочие столы Linux предоставляют то же свойство через NetworkManager на D-Bus, где PropertiesChanged на объекте VPN-соединения превращает проверку в событие, а свойство State сообщает, когда соединение достигает состояния подключено. Из оболочки nmcli connection show --active читает ту же информацию, и именно это нужно при отладке. macOS хранит этот список в платформе System Configuration, а scutil — его представление из командной строки. Все три описывают соединение, которым управляет платформа, и все три молчат о туннеле, которым она не управляет.

Путь: куда на самом деле идёт пакет

Сопоставление имён — это кратчайший путь, к которому все прибегают первым: перечислите интерфейсы, проверьте строку на tun, tap, ppp или ipsec и считайте совпадение доказательством. Это эвристика, и набор интерфейсов может выглядеть одинаково независимо от того, идёт ли через него что-нибудь. Старые сборки iOS отвечали так, а более новые перестали, потому что видимые интерфейсы больше не различали эти два состояния.

То, что заменило это там, — системный словарь прокси. Пока VPN подключён, вызов настроек прокси возвращает ключи, ограниченные интерфейсами, связанными с туннелем, а когда ничего не подключено, словарь возвращается пустым. Это более слабое утверждение, чем кажется, потому что оно фиксирует системную веру в существование туннеля, а не то, какой адресат им пользуется.

У Android есть собственная версия этой ловушки: широковещательное сообщение vpn.connectivity в одних сборках доходит до приёмников, объявленных в манифесте, а в других — до приёмников, зарегистрированных во время выполнения. Относитесь к нему как к наилучшему усилию и держите за ним прямую проверку.

Когда важен именно адресат, спрашивайте таблицу маршрутизации, а не список адаптеров. Эвристики шлюза по умолчанию ломаются на машинах с несколькими сетями и на туннелях, которые устанавливают собственный шлюз.

Идентичность: проверьте дальний конец

Цикл, который каждые несколько секунд отправляет ICMP-эхо на дальний конец, — самая дешёвая проверка такого рода, и она отказывает в полезную сторону. Направьте его на адрес, который существует только внутри частной сети, потому что публичный отвечает через любой работающий вышестоящий канал и ничего не доказывает. Там, где ICMP фильтруется, короткое TCP-соединение с портом службы делает ту же работу.

Проверка на стороне сервера располагает ровно одним сигналом: адресом источника в соединении. Ведение инвентаря диапазонов — это работа по сопровождению, и она не может отделить туннель от любой другой сети, которая случайно использует тот же блок.

Грубая политика может с этим жить. Идентичность не может, поэтому аутентифицируйте клиента, а не полагайтесь на то, откуда, как кажется, приходят его пакеты.

Записывайте категорию, а не вердикт

Держите категории сбоев в журнале раздельно: интерфейса нет вовсе, интерфейс есть, но адресат недостижим, адресат достижим, но аутентификация отклонена. Это три разных владельца, и каждый открывает своё расследование.

Сведение их в одно «VPN упал» отправляет отчёт команде, которая не может это исправить. Категория — полезный результат; вердикт — это то, что делает его неверным.

Похожие статьи