Клиент подключается к серверу OpenVPN на виртуальной машине Compute Engine, окно состояния сообщает подключено, а ping до хоста внутри VPC по-прежнему никуда не доходит. Первый порыв — добавить ещё одну строку push, и это никогда не помогает. Compute Engine решает, куда идут пакеты, по своей собственной таблице маршрутов и по адресам, которые интерфейс VM действительно держит, поэтому пул туннеля, которым VM не владеет, — это пул, в который никто не доставляет.

Идите за падающим пакетом, а не за файлом конфигурации, и недостающие части проявятся по порядку.

Начните с умирающего пакета

SYN покидает клиента, попадает в туннель и достигает VM как инкапсулированный пакет к адресату внутри VPC. OpenVPN распаковывает его и пытается передать через eth0, где fabric задаёт один вопрос прежде всего остального: владеет ли этот инстанс адресом назначения. Пул туннеля, вырезанный из чужой подсети, не проходит этот вопрос, и пакет отбрасывается ещё до того, как будет проверен хоть один маршрут или правило firewall.

Значит, у направления от клиента к VPC два отдельных требования, и ни одно из них не является директивой push. VM нужен диапазон alias, покрывающий пул туннеля, и именно это делает её законным владельцем этих адресов. VPC нужен маршрут, который называет VM следующим переходом для каждого защищённого префикса. Не хватает одного — пакет умирает в другом месте, а клиент видит то же молчание.

Читайте сбой в этом порядке, потому что со стороны клиента оба условия выглядят одинаково. Туннель, который сообщает о подключении, когда никто не отвечает, — признак проблемы топологии, а не плохого шифра.

Владеет ли инстанс пулом туннеля

Дайте VM вторичный диапазон внутри её собственной подсети, а затем заставьте OpenVPN выдавать адреса ровно из этого диапазона. На инстансе, чей основной адрес — 10.156.0.10/20, alias /28 отделяет пул от адресов, которые уже держат другие инстансы:

gcloud compute instances network-interfaces update openvpn-vm \
  --zone=europe-west1-b \
  --aliases=eth0:10.156.1.0/28

Тогда директива server должна описывать тот же блок. Её первый адрес становится шлюзом туннеля, а остальные адреса раздаются клиентам. Здесь два числа описывают один блок, сеть и маску, и это тот синтаксис пула, которого OpenVPN по-прежнему ждёт.

server 10.156.1.0 255.255.255.240

Убедитесь, что адрес действительно попал на интерфейс, а не только в конфигурацию OpenVPN. Fabric пересылает то, что сообщает NIC, а не то, во что верит демон. Одна команда это решает:

ip -4 addr show dev eth0

Вторичный адрес, который так и не доходит до eth0, или пул, пересекающийся с другой подсетью, позже проявляется как периодические потери, а не как ошибка запуска. Демон стартует, клиенты подключаются, и только часть назначений ведёт себя неправильно. Держите диапазон alias небольшим, чтобы пул легко узнавался в таблице маршрутов. Никогда не вырезайте его из подсети, которую уже использует другой инстанс.

Дайте обратному трафику следующий переход

Ответ от хоста в VPC адресован клиентскому адресу из пула туннеля, который не принадлежит ни одной известной VPC подсети. Без маршрута к этому пулу ответ останавливается на первом переходе без совпадения, а запрос вполне мог успешно выполниться на дальнем конце.

gcloud compute routes create openvpn-return \
  --network=prod-vpc \
  --destination-range=192.168.127.0/24 \
  --next-hop-instance=openvpn-vm \
  --next-hop-instance-zone=europe-west1-b \
  --priority=800

Тот же маршрут нужен для любого префикса, который находится за клиентом, а не внутри VPC. Этим префиксам также нужна запись iroute в ccd-файле этого клиента. Эта запись — учёт на стороне сервера: она говорит серверу, какой клиент владеет префиксом, и ничего не устанавливает на самом клиенте.

Оставьте список push для клиентской стороны разговора, потому что он лишь добавляет маршруты на подключающуюся машину. Строка push для подсети VPC и маршрут VPC обратно к пулу — две половины одного пути, и любая половина сама по себе даёт туннель, который несёт запросы, но не ответы.

Firewall, пересылка и промежуточные переходы

Firewall VPC оценивает распакованный пакет, поэтому правило ingress должно пропускать пул туннеля и любой префикс площадки, который доходит до VM этим путём. Позиция default-deny тихо проваливается здесь: пакет отбрасывается после распаковки, а журнал OpenVPN показывает здоровую сессию.

На любом хосте Linux, который пересылает, а не завершает, проверьте, включён ли net.ipv4.ip_forward. Правило владения действует и там: машина, которая пересылает для подсети, которой у неё нет, отбрасывает пакет так же, как это сделала бы VM. Прочитайте значение, прежде чем винить туннель.

Когда не работает ровно один префикс

Частичный сбой читается легче всего, потому что всё остальное на пути уже доказано. Начните со списка маршрутов и прочитайте диапазон назначения и следующий переход записи, которая должна покрывать префикс, затем прочитайте описание инстанса и сравните его диапазон alias с пулом, который раздаёт сервер.

gcloud compute routes list --filter=destination-range~10.164.0.0
 gcloud compute instances describe openvpn-vm --format=get(networkInterfaces[0].aliasIpRanges)

Отсутствующий маршрут проявляется как отсутствие следующего перехода для префикса, а отсутствующий alias — как пакет, отброшенный на интерфейсе; со стороны клиента эти две вещи похожи, а в этих двух выводах совершенно различны. Если есть и то и другое, оставшийся подозреваемый — iroute в ccd клиента, который заявляет префикс.

Запишите четыре значения рядом с туннелем: диапазон alias, пул сервера, запушенные префиксы, маршруты VPC. Устаревший alias ломает каждый адрес пула, устаревший маршрут ломает одно направление, отсутствующий push скрывает префикс от клиента, а неверный iroute отправляет префикс не тому peer. Следующему человеку, который добавит подсеть, нужны все четыре в одном месте, и эта заметка дешевле вечера.

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