Три хоста Linux, одна схема: публично доступный концентратор OpenVPN, потребительский ноутбук и выходная машина за обычным домашним маршрутизатором, который ничего не пересылает внутрь. Выход сам подключается к концентратору. Затем интернет-трафик потребителя входит в туннель на ноутбуке и выходит через собственный восходящий канал выхода. Всё удивительное в этой схеме следует из одной детали: соединение инициировал выход, поэтому путь внутрь уже существует и для его создания не нужна переадресация порта.

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

Два транспортных сеанса, один внутренний путь

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

outer: consumer --TLS--> hub
outer: exit     --TLS--> hub
inner: consumer --tun--> hub --internal--> exit --tun--> uplink

Поскольку оба внешних соединения исходят со стороны клиента, маршрутизатор выхода никогда не видит входящий SYN и никогда не нуждается в правиле NAT назначения. Ему нужно только поддерживать установленный транспорт живым, что он уже делает для любого долгого соединения. Ничто в публичном интернете не должно сначала достичь выхода, поэтому динамический адрес на этом восходящем канале не проблема. Маршрутизатор всё равно должен разрешать исходящий сеанс, но это то же разрешение, которое уже использует браузер.

Что iroute на самом деле устанавливает внутри сервера

Концентратор — единственное устройство, которое решает, какому партнёру принадлежит какое назначение, и принимает это решение в собственной таблице маршрутизации пространства пользователя, а не в таблице ядра. Запись client-config-dir для выхода с именем exit-node может запросить там префикс по умолчанию:

# /etc/openvpn/server/ccd/exit-node
iroute 0.0.0.0 0.0.0.0

Эта строка говорит процессу сервера «внутренние пакеты с любым назначением принадлежат этому клиенту». Она не добавляет маршрут по умолчанию в операционную систему концентратора и не превращает концентратор в интернет-шлюз. Справочник OpenVPN 2.6 держит внутреннюю маршрутизацию, специфичную для клиента, и маршрутизацию ядра как отдельные механизмы, и именно это разделение — причина, по которой схема работает, не затрагивая маршрут по умолчанию концентратора.

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

# /etc/openvpn/server/ccd/consumer-01
push "redirect-gateway def1"

Пересылку выполняет ядро выхода

Внутри выхода пакет от потребителя приходит на интерфейс tun, адресованный какому-то интернет-хосту. Ядро смотрит в свою таблицу маршрутизации, находит реальный восходящий канал как лучшее совпадение и затем применяет две независимые политики: можно ли этот пакет пересылать вообще и может ли он выйти через этот интерфейс с этим адресом источника. Пересылка — это sysctl, и во многих дистрибутивах она выключена по умолчанию.

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

Обратный путь покупается трансляцией источника

У интернет-хоста нет маршрута к адресу 10.90.0.x, поэтому ответы были бы отброшены ещё до того, как достигнут выхода. Перезапись источника при выходе пакета из восходящего канала решает это, а отслеживание соединений разворачивает перезапись на обратном пути.

sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -s 10.90.0.0/24 -o enp1s0 -j MASQUERADE
ip -4 route get 10.90.0.9   # run on the exit: must point at the tun

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

Где концентратор может применять правила, а где нет

С client-to-client и традиционным путём данных в пространстве пользователя концентратор передаёт внутренние пакеты между партнёрами внутри процесса сервера. Они никогда не проходят через цепочку FORWARD ядра концентратора, поэтому правило в ядре там не может ограничить прохождение между партнёрами, как бы тщательно оно ни было написано. Применение должно жить в собственной политике сервера: кому разрешено подключаться, какой файл CCD он получает и выдаётся ли вообще владение выходом по умолчанию.

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

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

Безопасно отказать, когда выход исчезает

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

# on the consumer, with the exit up and then down
ip -4 route get 1.1.1.1
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com

Если трафик потребителя всё ещё достигает интернета после исчезновения выхода, его несёт что-то другое, и задуманной границы не существует. К DNS и IPv6 тоже относитесь как к отдельным решениям: маршрут IPv4 плюс маскировка ничего не говорят о трафике AAAA, а резолвер, продвинутый концентратором, ответит оттуда, откуда запрос действительно выходит.

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