Иногда стандартная схема site-to-site VPN перестаёт быть стандартной уже на первом шаге. Требовалось объединить несколько удалённых площадок в единую маршрутизируемую сеть — но прямой WireGuard между частью узлов работал нестабильно или блокировался на сетевом пути.
Дополнительные требования заметно усложняли задачу: один из узлов работал на достаточно старом Linux, отдельные внутренние серверы должны были быть доступны по специально выделенным виртуальным IP-адресам, а исходящий интернет-трафик одного конкретного сервера требовалось направлять через другой офис. При этом старые входящие подключения через публичный IP Linux-шлюза должны были продолжать работать без изменения адреса источника выхода.
В результате получилась многоуровневая схема, где транспортный, криптографический и прикладной уровни почти не зависят друг от друга.
Все названия, публичные адреса и внутренние сети заменены. Для публичных IP используются документальные диапазоны RFC 5737, которые не маршрутизируются в реальном интернете.
Исходные условия
| Узел | Публичный IP | Локальная сеть | VPN IP |
|---|---|---|---|
| Router-A | 192.0.2.10 | 10.20.10.0/24 | 172.20.180.1 |
| Router-B | 198.51.100.20 | 10.20.20.0/24 | 172.20.180.2 |
| Router-C | 203.0.113.30 | 10.20.30.0/2410.20.31.0/24 | 172.20.180.3 |
| Linux-GW | 192.0.2.200 | 10.20.40.0/24 | 172.20.180.4 |
Linux-шлюз имеет WAN 192.0.2.200 и LAN 10.20.40.1. Внутри его сети находятся серверы 10.20.40.3 и 10.20.40.4. Задача — предоставить к ним доступ из других офисов по виртуальным адресам, а сервер 10.20.40.4 вывести в интернет через Router-A.
Internet
│
┌─────────────┼─────────────┐
Router-A Router-B Router-C
└───── WireGuard mesh ──────┘
│
IPIP underlay
│
Linux-GW
│
Internal LAN
Почему не просто WireGuard
В обычной ситуации достаточно построить WireGuard между всеми площадками. Но если UDP WireGuard фильтруется или распознаётся на промежуточном участке сети, handshake может вообще не происходить.
Для связи с Linux-шлюзом был выбран другой подход: WireGuard использует в качестве endpoint не публичный IP удалённого устройства, а адрес внутри IPIP-туннеля. Снаружи получается IP protocol 4 — IP-in-IP. Уже внутри него находится UDP-трафик WireGuard.
WireGuard кладётся внутрь IPIP. На физическом WAN виден только IP-in-IP (protocol 4), а UDP/51820 работает уровнем ниже — там, где его уже никто не фильтрует.
IPIP как транспортный слой
Для каждого маршрутизатора был создан отдельный IPIP до Linux-GW. Использовались служебные сети 172.31.200.0/30, 172.31.200.4/30, 172.31.200.8/30.
/interface ipip
add name=ipip-linux \
local-address=192.0.2.10 \
remote-address=192.0.2.200 \
clamp-tcp-mss=yes \
keepalive=10s,10
/ip address
add address=172.31.200.1/30 interface=ipip-linux
ip tunnel add ipip-router-a mode ipip \
local 192.0.2.200 \
remote 192.0.2.10 \
ttl 64
ip addr add 172.31.200.2/30 dev ipip-router-a
ip link set ipip-router-a up
ping 172.31.200.1 — пока IPIP не работает стабильно, переходить к WireGuard бессмысленно.
WireGuard внутри IPIP
Основная WireGuard-сеть — 172.20.180.0/24. Важное отличие от обычной конфигурации — endpoint. Linux-GW обращается к Router-A не по 192.0.2.10:51820, а по 172.31.200.1:51820. То есть пакет WireGuard сначала маршрутизируется в IPIP.
[Interface]
Address = 172.20.180.4/24
ListenPort = 51820
PrivateKey = <PRIVATE_KEY>
MTU = 1380
[Peer]
PublicKey = <ROUTER_A_PUBLIC_KEY>
Endpoint = 172.31.200.1:51820
AllowedIPs = 172.20.180.1/32,10.20.10.0/24
PersistentKeepalive = 25
/interface wireguard peers
add interface=wg-mesh \
public-key="<LINUX_PUBLIC_KEY>" \
endpoint-address=172.31.200.2 \
endpoint-port=51820 \
allowed-address=172.20.180.4/32 \
persistent-keepalive=25
WireGuard UDP/51820
↓
172.31.200.x
↓
IPIP
↓
public network
Полный mesh между офисами
Три RouterOS-площадки могут иметь прямой WireGuard mesh и общаться напрямую. Соединения Linux-GW с каждым из них идут через отдельный IPIP. Так отказ одной площадки не превращает её в обязательный транзитный узел для остальных.
Router-A
172.20.180.1
/ \
/ \
Router-B ───────────── Router-C
172.20.180.2 172.20.180.3
\ /
\ IPIP + WG /
\ /
Linux-GW
172.20.180.4
Виртуальные адреса серверов
Следующей задачей было скрыть реальные адреса серверов Linux-площадки. Виртуальные адреса назначаются Linux-GW:
ip addr add 10.254.100.254/32 dev lo
ip addr add 10.254.100.251/32 dev lo
На удалённых маршрутизаторах эти адреса относятся к peer Linux-GW, и к ним создаются маршруты:
allowed-address=172.20.180.4/32,10.254.100.254/32,10.254.100.251/32
/ip route
add dst-address=10.254.100.254/32 gateway=wg-mesh
add dst-address=10.254.100.251/32 gateway=wg-mesh
Теперь офисный компьютер обращается к 10.254.100.254 вместо реального 10.20.40.3 — знать настоящий адрес сервера ему не требуется.
DNAT виртуального RDP
На Linux-GW трафик к виртуальным адресам разворачивается на реальные серверы:
iptables -t nat -A PREROUTING -i wg0 \
-d 10.254.100.254 -p tcp --dport 3389 \
-j DNAT --to-destination 10.20.40.3:3389
iptables -t nat -A PREROUTING -i wg0 \
-d 10.254.100.251 -p tcp --dport 3389 \
-j DNAT --to-destination 10.20.40.4:3389
# для RDP имеет смысл пропустить и UDP 3389
iptables -t nat -A PREROUTING -i wg0 \
-d 10.254.100.251 -p udp --dport 3389 \
-j DNAT --to-destination 10.20.40.4:3389
В итоге пользователь просто вводит 10.254.100.251, а реальный адрес сервера остаётся скрыт.
Отдельный интернет-выход для одного сервера
Самая интересная часть. Сервер 10.20.40.4 должен выходить в интернет через Router-A, но весь Linux-GW и остальные серверы должны продолжать использовать обычный WAN. То есть нельзя просто заменить default route.
Для этого создан второй WireGuard — wg-exit — со своей сетью 172.20.181.0/30. Он тоже идёт внутри уже существующего IPIP Router-A ↔ Linux-GW. Отдельный интерфейс чётко разделяет функции: wg0 — межофисная связь, wg-exit — только интернет-трафик выбранного сервера.
[Interface]
Address = 172.20.181.2/30
PrivateKey = <PRIVATE_KEY>
ListenPort = 51821
MTU = 1380
Table = off
[Peer]
PublicKey = <ROUTER_A_EXIT_PUBLIC_KEY>
Endpoint = 172.31.200.1:51821
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Без этого параметра wg-quick с AllowedIPs = 0.0.0.0/0 автоматически перехватит основной default route Linux-GW и уронит весь остальной трафик шлюза.
Policy routing
Для специального маршрута создаётся отдельная таблица. В /etc/iproute2/rt_tables добавляется 251 exit-a, внутри неё — default route через выходной туннель, а трафик сервера направляется по источнику:
ip route add default dev wg-exit table exit-a
ip rule add priority 200 \
from 10.20.40.4/32 \
lookup exit-a
Для внешних ресурсов трафик сервера 10.20.40.4 теперь выглядит как исходящий с публичного IP Router-A.
Проблема асимметрии
У Linux-GW уже существовал старый внешний DNAT 192.0.2.200:11514 → 10.20.40.4:3389. Клиент подключается на публичный IP Linux-GW, DNAT разворачивает пакет на сервер. Но ответный пакет имеет source 10.20.40.4, а policy rule говорит «from 10.20.40.4 → exit-a». Поэтому SYN-ACK уходит через Router-A, с другого публичного IP — и TCP-соединение не устанавливается.
Запрос: client ──→ 192.0.2.200 (Linux-GW WAN)
│ DNAT
▼
10.20.40.4
Ответ: 10.20.40.4 ──→ Router-A ──→ другой public IP ✗
Решение: connection mark
Входящие соединения через физический WAN Linux-GW нужно отличать от обычного исходящего трафика сервера. При входе помечаем connection, для ответов восстанавливаем mark и даём ему более приоритетное правило маршрутизации:
# помечаем входящее соединение через WAN
iptables -t mangle -I PREROUTING 1 -i eth-wan \
-d 192.0.2.200 -p tcp --dport 11514 \
-m conntrack --ctstate NEW \
-j CONNMARK --set-mark 0x1
# восстанавливаем mark на ответных пакетах
iptables -t mangle -I PREROUTING 2 -i br0 \
-s 10.20.40.4 \
-m conntrack --ctstate ESTABLISHED,RELATED \
-j CONNMARK --restore-mark
# помеченный трафик идёт в main (физический WAN)
ip rule add priority 50 fwmark 0x1 lookup main
Теперь обычный новый трафик 10.20.40.4 идёт в exit-a, а ответ на соединение, пришедшее через WAN Linux-GW, — в main, то есть обратно через физический WAN.
Порядок правил policy routing
Важно также гарантировать, что межофисный трафик не уйдёт через exit-туннель. Полная последовательность правил, от высшего приоритета к низшему:
40 from 10.20.40.4 to 198.51.100.100 → main # админ-исключение
50 fwmark 0x1 → main # ответы DNAT
100 to 10.20.10.0/24 → main # офисные сети
101 to 10.20.20.0/24 → main
102 to 10.20.30.0/24 → main
103 to 10.20.31.0/24 → main
105 to 172.20.180.0/24 → main # WG mesh
106 to 172.31.200.0/24 → main # IPIP
107 to 10.254.100.0/24 → main # виртуальные IP
200 from 10.20.40.4 → exit-a # интернет-выход
Такая последовательность одновременно сохраняет старые входящие сервисы и меняет обычный исходящий интернет-маршрут сервера.
rp_filter и forwarding
При policy routing и потенциально асимметричных путях Linux Reverse Path Filtering может начать уничтожать вполне корректные пакеты. Поэтому его отключаем, а forwarding — включаем:
net.ipv4.conf.all.rp_filter=0
net.ipv4.conf.default.rp_filter=0
net.ipv4.ip_forward=1
NAT на выходном маршрутизаторе
Router-A получает через wg-exit пакет src=10.20.40.4, dst=8.8.8.8. Обычный masquerade на WAN превращает source в 192.0.2.10, поэтому внешний сервис видит публичный IP Router-A:
/ip firewall nat
add chain=srcnat action=masquerade out-interface-list=WAN
Проверка и диагностика
Для forwarded-трафика удобно проверять реальный путь пакета, а не только handshake:
# должен уйти через wg-exit:
ip route get 8.8.8.8 from 10.20.40.4 iif br0
# → dev wg-exit, table exit-a
# офисный адрес — через wg0:
ip route get 10.20.10.1 from 10.20.40.4 iif br0
# → dev wg0
# прямого WireGuard на WAN быть НЕ должно:
tcpdump -ni eth-wan udp port 51820
# зато виден IPIP:
tcpdump -ni eth-wan 'ip proto 4'
# а внутри IPIP — уже WireGuard:
tcpdump -ni ipip-router-a udp port 51820
Восстановление после reboot
Настроить всё командами ip и iptables недостаточно: после перезагрузки большая часть конфигурации исчезнет. Постоянными нужно сделать IPIP-интерфейсы, оба WireGuard, виртуальные /32-адреса, ip_forward, rp_filter, правила iptables, отдельную routing table, ip rule и keepalive.
Для WireGuard удобно использовать сервисы wg-quick@wg0.service и wg-quick@wg-exit.service, а policy routing поднимать через хуки — чтобы правила появлялись только после появления соответствующего интерфейса:
PostUp = /usr/local/sbin/exit-policy-up.sh
PreDown = /usr/local/sbin/exit-policy-down.sh
Что получилось в итоге
┌──── Router-A ────┐
│ WG mesh │
Router-B ─┼──────────── Router-C
│
│ WireGuard внутри IPIP
▼
Linux-GW
│
┌──────┴──────┐
10.20.40.3 10.20.40.4
│ │
│ └─ wg-exit → Router-A → Internet
│
└─ virtual IP (10.254.100.x)
Пользователи работают с виртуальными адресами 10.254.100.254 и 10.254.100.251 и не зависят от реальной адресации серверной площадки. Сервер 10.20.40.4 использует Router-A как интернет-шлюз, но старые подключения через публичный IP Linux-GW продолжают возвращаться через Linux-GW.
Основные выводы
- IPIP можно использовать как underlay для WireGuard, если прямой UDP WireGuard не проходит.
- WireGuard endpoint при этом указывается как внутренний адрес IPIP, а не публичный IP.
- Для нескольких площадок удобно сохранять полноценный WireGuard mesh, а IPIP применять только там, где он действительно необходим.
- Виртуальные /32-адреса отделяют адрес, используемый пользователями, от реального адреса сервера.
- Для отдельного интернет-выхода одного хоста лучше использовать отдельный WireGuard-интерфейс и policy routing.
Table = offне отдаётwg-quickуправление основным default route.CONNMARK+ip rule fwmarkрешают проблему возврата DNAT-соединений при source-based policy routing.- При сложной маршрутизации необходимо учитывать
rp_filter. - Проверять нужно не только handshake WireGuard, но и реальный путь пакета через
tcpdump,ip rule,ip route getи счётчики iptables. - После настройки обязательно проверить восстановление всей схемы после reboot.
Главная ценность такой архитектуры в том, что уровни практически не зависят друг от друга. IPIP решает задачу доставки WireGuard-трафика. WireGuard формирует защищённую маршрутизируемую сеть. Виртуальные IP скрывают реальную адресацию сервисов. Policy routing управляет тем, через какую площадку конкретный сервер выходит в интернет. В результате сложная физическая сеть для пользователей и серверов выглядит как единая обычная IP-сеть.

Комментариев пока нет
Будьте первым — оставьте свой.