VPN-клиент на LAN-сервере: почему ответы уходят не тем путём
Хост, подключающийся к VPN, получает маршрут по умолчанию, поэтому ответы LAN-клиентам уходят через туннель. Добавьте более узкий обратный маршрут и проверьте его.
Симптомы следуют одной схеме: хост работает, затем подключается к VPN как клиент, и теперь LAN-клиенты до него доходят, а его ответы пропадают. Перехват на хосте показывает, что ответы уходят через туннельное устройство, а не через LAN-интерфейс. Сам туннель не сломан, и исправление — это маршрут, а не переподключение. Выполните четыре проверки ниже по порядку и остановитесь на первой неудачной.
Ответ — это новый пакет, а не разворот пути
Полезно отказаться от мысли, что ответ возвращается тем же путём, которым ушёл запрос. Маршрутизация решается для каждого пакета и каждого направления на отправляющем хосте. Запрос, пришедший из 10.64.0.23 внутри туннеля, не создаёт обратного пути; когда хост отвечает, этот ответ сопоставляется с таблицей как новый пакет и уходит по тому маршруту, который побеждает.
Поэтому клиент с полным туннелем на машине, которая также обслуживает LAN, даёт такой запутанный отказ. Клиент устанавливает маршрут по умолчанию или два половинчатых маршрута по умолчанию, которые предпочитают некоторые клиенты, и ответ LAN-клиенту теперь совпадает с туннелем. Он уходит провайдеру, у которого нет причин его пересылать, а LAN-клиент видит тайм-аут, хотя хост работает и отвечает локально. Перехват делает это очевидным, а таблица маршрутизации объясняет.
Держите в голове две подсети, читая остальное. Одна — пул адресов VPN, откуда приходят удалённые клиенты, в этом примере 10.64.0.0/24. Другая — LAN, который должен продолжать работать, 172.16.24.0/24 с маршрутизатором на 172.16.24.1. Ошибка в том, что трафик между ними решает маршрут по умолчанию.
Докажите это перехватом и запросом
Прежде чем что-то менять, подтвердите, какое устройство будет использовать ответ. Запрос быстрее перехвата и отвечает ровно на тот вопрос, на который ответит ядро при сборке пакета.
ip route get 10.64.0.23 from 172.16.24.10
tcpdump -ni any -c 6 icmp
Читайте ответ в двух частях. Если он называет dev tun0, хост инкапсулирует ответ и передаст его провайдеру, а LAN-клиент его никогда не увидит. Если он называет dev eth0, но клиент всё равно получает тайм-аут, маршрут уже верен и пакет отбрасывается позже, что является другой проблемой с другим исправлением.
Перехват на том же шаге снимает сомнение, которое ping снять не может. Эхо-ответ, доходящий до LAN-клиента, доказывает, что какой-то путь работает, а не какой интерфейс его нёс, поэтому выполняйте перехват на самом хосте и смотрите, откуда уходят кадры. Принтер в той же подсети продолжает отвечать, потому что его прямой маршрут конкретнее маршрута по умолчанию туннеля. Проброшенный порт отказывает точно так же, как любой другой поток, потому что интерфейс выбирает таблица, а не соединение.
Установите самый узкий маршрут, который перекрывает туннель
Решает длина префикса, а не метрика, поэтому маршрут для пула клиентов через LAN-маршрутизатор побеждает любой маршрут по умолчанию, который установит VPN-клиент. Хирургия метрики не нужна, и маршрут по умолчанию может остаться ровно там, где его поставил клиент.
ip route replace 10.64.0.0/24 via 172.16.24.1 dev eth0
ip route get 10.64.0.23 from 172.16.24.10
Используйте replace, а не add, потому что клиент, который переподключится, уже установит свою запись, а второй add отказывает с ошибкой о существующем маршруте, которая читается как проблема с правами. На хосте Windows эквивалент — route add 10.64.0.0 mask 255.255.255.0 172.16.24.1 -p, где -p сохраняет запись между перезагрузками, так как клиент может перестроить таблицу, когда туннель снова поднимется. На современном хосте Windows поддерживаемый эквивалент — New-NetRoute -DestinationPrefix 10.64.0.0/24 -NextHop 172.16.24.1 -InterfaceAlias Ethernet -PolicyStore PersistentStore, а старая команда route остаётся как устаревшая форма.
Одна деталь решает, правильно это или только правдоподобно: следующий переход должен быть маршрутизатором, который действительно владеет путём назад к клиентам. Направление маршрута на адрес, до которого сам доходишь через туннель, создаёт петлю, проявляющуюся как периодические потери, а не как чистый отказ. Подтвердите, что следующий переход находится в LAN-подсети, прежде чем фиксировать строку.
Сохраните ответ после переподключения
Маршрут, добавленный вручную, живёт в лучшем случае до следующего переподключения. Если пул клиентов динамический, закрепите его в конфигурации сервера, чтобы префикс, который вы маршрутизировали, остался префиксом, который получают клиенты; иначе запись указывает на пустой диапазон, и симптом возвращается спустя недели без явной причины. Записать маршрут в собственный up-скрипт клиента — другой надёжный вариант, потому что он выполняется после того, как клиент перезаписал таблицу.
Межсетевой экран с отслеживанием состояний всё равно может победить правильный маршрут. Если перехват теперь показывает, что ответ уходит через eth0, а клиент всё равно получает тайм-аут, проверьте, не отбрасывает ли хост пакет до отправки. Строгая фильтрация обратного пути и net.ipv4.conf.all.rp_filter, установленный в 1, отбрасывают пакеты, источник которых не был бы маршрутизирован обратно через тот интерфейс, через который они пришли, и это ровно тот асимметричный случай, который вы только что создали и затем исправили, а состояние conntrack рассказывает ту же историю для проброшенных портов.
Последняя проверка — та, которую люди пропускают: повторите запрос после того, как туннель опускался и снова поднимался. Четыре значения должны совпасть с тем, что вы записали: назначение, следующий переход, устройство и выбранный источник. Если что-то из этого сместилось, клиент перезаписал таблицу, и исправление относится к его хукам, а не к истории оболочки.