Полный туннель или маршрут до конечной точки: как оставить VPN-сервер доступным
Туннель идёт через сеть, которую он заменяет. Сравните исключение в виде маршрута к хосту с меткой сокета на уровне приложения и узнайте, когда отказывает каждый из них.
Добавьте маршрут по умолчанию через туннель, и собственные пакеты туннеля пойдут по нему. Таблица тогда содержит правило, которое само себя обесценивает. Зашифрованное соединение с сервером пытается пройти внутри соединения, которое оно пытается установить.
Два механизма предотвращают эту петлю. Один виден в таблице маршрутизации, другой живёт внутри приложения.
Внутренний и внешний трафик — разные решения
Пакет приложения попадает в виртуальный интерфейс и инкапсулируется. Результат — новый пакет, адресованный публичному адресу сервера. Этому пакету нужен маршрут в физической сети, потому что ничто в нём не должно попасть в туннель.
Оба — решения о маршрутизации, и только первое принадлежит туннелю. Когда клиент устанавливает маршрут по умолчанию, охватывающий и сервер, внешний пакет тоже перехватывается.
Симптомы мгновенные и полные. Рукопожатие завершается, и после этого ничего не течёт, либо соединение падает в тот момент, когда устанавливается маршрут. В обоих случаях keepalive больше не может выйти.
Первый вариант: маршрут к хосту для конечной точки
Прямой подход добавляет маршрут для адреса сервера через исходный шлюз. Это занимает одну строку. Он виден каждому, кто читает таблицу. Его также легко объяснить в записи об изменении.
Его слабость проявляется, когда меняется нижележащий путь. Сохранённый маршрут называет шлюз и интерфейс из старой сети. После смены Wi-Fi этого шлюза может не быть, а маршрут остаётся, выглядит правильным и ничего не делает.
# before the tunnel: which path reaches the endpoint?
ip route get 198.51.100.7
# pin it through the current gateway
ip route add 198.51.100.7/32 via 192.168.1.1 dev wlan0
# after the tunnel is up, confirm the pin survived
ip route get 198.51.100.7
Имя хоста усложняет дело. Если конечная точка — это имя, разрешающееся в несколько адресов, исключение должно покрывать адрес, который фактически используется. Более поздний запрос может вернуть то, что маршрут никогда не покрывал.
Второй вариант: защитить сокет
Другой подход никогда не трогает таблицу маршрутизации. Клиент помечает или защищает сокет, несущий туннель, поэтому операционная система по политике отправляет трафик этого сокета вне туннеля. Android предоставляет этот механизм VPN-приложениям напрямую. Настольные клиенты реализуют эквиваленты с помощью меток межсетевого экрана или вызовов привязки к устройству.
Преимущество в том, что исключение следует за соединением, а не за сохранённым шлюзом. Смена сети переписывает нижележащий путь, и защищённый сокет перемещается вместе с ним, потому что он никогда не был привязан к адресу. Таблица маршрутов остаётся чистой.
Цена — видимость. Ничто в таблице маршрутизации не показывает исключение, поэтому проверяющий, ищущий маршрут к хосту, ничего не находит и может заключить, что схема небезопасна. Защита живёт в коде клиента и в правилах политики, которые этот код устанавливает.
Выбор между ними
Выбирайте механизм уровня сокета, когда клиент его поддерживает, потому что он переживает смены сети, ломающие закреплённый маршрут. Выбирайте маршрут к хосту, когда исключение должно быть видно другим инструментам или когда клиент не предлагает ничего лучше.
Ни один из механизмов не отменяет необходимости в том, чтобы адрес сервера вообще был доступен. У частного шлюза есть публичный адрес прослушивания, и именно этот адрес должен оставаться вне туннеля. Внутренние сети за ним становятся доступны только после появления туннеля.
Что бы вы ни выбрали, держите это рядом с компонентом, которому принадлежит туннель. Заблудший маршрут к хосту, добавленный вручную, работает до следующей сети, а затем превращается в загадочный сбой завтрашнего дня.