Два шлюза, один VPN: почему OpenVPN выбирает неверный восходящий канал
Туннель в порядке; сокет выходит через неправильный WAN. Прочитайте активный адрес пира, закрепите маршрут к хосту и сначала проверьте правила политики.
Два восходящих канала, один туннель, и трафик выходит через тот, который вы не выбирали. Частные подсети доступны, журнал сообщает об успешном подключении, и именно измеряемый канал его несёт. Ничто в выводе OpenVPN не упоминает шлюз, потому что клиент никогда его не выбирал. Он открыл сокет к удалённому адресу и позволил ядру решить, как добраться до этого адреса.
Адрес туннеля — не конечная точка
В каждой сессии существуют два разных пути. Внешний путь несёт зашифрованный транспорт к публичному адресу сервера. Внутренний путь несёт всё, что сервер проталкивает в туннель, и правила маршрутизации одного не влияют на другой.
Запись remote в профиле задаёт внешнее назначение, поэтому именно этот адрес нужно закрепить. Это также значение, которое меняется, когда имя хоста разрешается иначе или профиль перечисляет несколько remote.
Найдите адрес, к которому клиент действительно обратился
Читайте живой сокет, а не файл конфигурации. Таблица сокетов показывает пира, к которому процесс действительно подключился, и это значение, которому должно соответствовать исключение. Профиль можно редактировать после начала сессии, а сокет — единственное доказательство того, к чему обращались:
ss -tunp | grep -i openvpn
journalctl -u openvpn-client@work --no-pager | grep -i "Peer Connection Initiated"
Строка журнала указывает адрес и порт пира на момент подключения. Если она расходится с профилем, что-то перед клиентом переписывает назначение до начала соединения.
Проверьте и семейство адресов. Профиль с двумя стеками может подключаться по IPv6, тогда как ваше исключение покрывает только IPv4, и тогда трафик следует другому правилу, чем вы написали. То же касается прокси, отвечающего от имени конечной точки. В обоих случаях получается туннель, который работает, пока ваш маршрут ничего не делает.
Закрепите конечную точку и прочитайте порядок политики
Маршрут к хосту конкретнее любого маршрута по умолчанию. Поэтому /32 для адреса пира побеждает в таблице, которая его содержит. Обычная форма явно называет шлюз и устройство:
ip route add 203.0.113.40/32 via 192.168.10.1 dev eth0
Этот маршрут помогает, только если поиск доходит до этой таблицы. Правила политики оцениваются по порядку, и первое совпадение решает, поэтому правило, отправляющее всё во вторую таблицу, обходит ваш маршрут к хосту в main. Прочитайте правила, прежде чем считать маршрут авторитетным:
ip rule show
ip route get 203.0.113.40
Спросите вторую команду с тем же исходным адресом или меткой, которые использует клиент, когда политика зависит от них. Она сообщает маршрут, который выбрало бы ядро, а это доказательство о поиске, а не доказательство того, что живой сокет использует этот путь.
Когда конечная точка перемещается
Имя хоста, разрешающееся в несколько адресов, делает постоянный /32 недействительным, как только ответ меняется. Профиль с несколькими строками remote имеет ту же проблему, потому что клиент может приземлиться на любой из них. Обновляйте маршрут из собственных хуков жизненного цикла клиента, а не добавляйте вручную. Скрипт up, который читает согласованного пира и устанавливает маршрут к хосту, держит исключение в соответствии с реальностью, а соответствующий скрипт down удаляет его.
Записывайте обновление там же, где находится остальная конфигурация маршрутизации хоста. Маршрут, существующий только до следующей перезагрузки, — это тест, а не исправление.
Держите внутреннюю политику отдельно
Перемещение внешнего пути не даёт доступа к новой частной подсети и не меняет, какие назначения сервер проталкивает в туннель. Маршрут нижнего уровня выбирает провод; протолкнутые маршруты выбирают, что входит в туннель. Именно разделение этих двух решений делает разделение предсказуемым.
Если цель — только держать зашифрованный транспорт подальше от измеряемого канала, оставьте маршрут по умолчанию в покое и добавьте единственное исключение для хоста. Замена общесистемного маршрута по умолчанию, чтобы переместить одно VPN-соединение, ломает любое другое приложение на хосте.
Подтвердите результат двумя независимыми наблюдениями: локальным адресом и устройством сокета и маршрутом, в который разрешается частное назначение. Когда оба соответствуют вашему замыслу, разделение реально. Руководство OpenVPN 2.6 описывает параметры remote и локальной привязки, которые определяют, к какому адресу обращается клиент.