Маршрутизация IPv6 между двумя площадками через существующий VPN
WAN только с IPv4 всё ещё может переносить внутренний IPv6. Проследите селекторы, маршруты ядра, пересылку и обратный префикс, который молча отбрасывает ответы.
Когда вы добавляете префикс IPv6 в туннель между площадками, пакет путешествует не так, как подсказывает схема. Локальный шлюз берёт весь пакет IPv6, шифрует его и ставит перед ним внешний заголовок IP, семейство адресов которого определяется адресами конечных точек, а не тем, что внутри. Удалённый шлюз снимает этот внешний заголовок и пересылает исходный пакет в свою локальную сеть, где он снова обычный пакет IPv6. Внешний туннель и внутренний трафик — два отдельных разговора, у которых общий ключ, пара конечных точек и больше ничего.
Два заголовка, два семейства адресов
В режиме туннеля IPsec внутренний пакет IPv6 становится полезной нагрузкой за заголовком ESP. Внешний заголовок, несущий этот пакет ESP, может быть IPv4 или IPv6 и следует local_addrs и remote_addrs. WireGuard делает то же самое с меньшей церемонией: внутренний пакет — полезная нагрузка датаграммы UDP к конечной точке партнёра. Оба оставляют внутренний заголовок нетронутым, поэтому удалённая сторона может маршрутизировать его после декапсуляции.
Путь между площадками видит только UDP 4500, когда IPsec использует прохождение NAT, или UDP 51820 для прослушивателя WireGuard по умолчанию. Любой маршрутизатор на пути может быть только IPv4, и трафик IPv6 всё равно проходит, потому что на этом пути он никогда не появляется как пакет IPv6. Это даёт чистое первое разделение: tcpdump -ni eth0 udp port 51820 показывает зашифрованный путь, tcpdump -ni wg0 ip6 показывает внутренний трафик. Занятая внешняя запись с молчаливой внутренней означает, что ядро не помещает IPv6 в туннель; это маршрутизация или политика, а не состояние канала.
Шлюз также уменьшает внутренний Hop Limit при пересылке, поэтому конечные точки считаются маршрутизаторами на пути IPv6. Внешнее число переходов и внутренний Hop Limit движутся независимо, и пакет, умирающий на удалённом шлюзе, умер при пересылке, а не при шифровании.
Селекторы и AllowedIPs — это список разрешений
IKEv2 согласовывает, какие внутренние префиксы может нести child SA. На strongSwan 5.9 и новее их явная запись в swanctl.conf выглядит так:
children {
net6 {
local_ts = 2001:db8:aa::/48
remote_ts = 2001:db8:bb::/48
mode = tunnel
esp_proposals = aes256gcm16-prfsha384-ecp384
start_action = trap
}
}
Если опустить local_ts и remote_ts, strongSwan выводит их из подсетей выбранного интерфейса. На узле с dual-stack этот короткий путь часто даёт пару только IPv4: SA поднимается, а IPv6 отвергается, не доходя до кода ESP. Явная запись обоих префиксов делает SA способным их нести, и start_action = trap поднимает его на первом подходящем пакете.
У WireGuard один список выполняет две задачи. В блоке партнёра AllowedIPs = 2001:db8:bb::/48, fd00:bb::/48 — это и маршрут, устанавливаемый при отправке, и фильтр, применяемый при приёме: расшифрованный пакет, внутренний источник которого выходит за пределы списка этого партнёра, молча отбрасывается. wg-quick добавляет маршрут для каждой записи, поэтому блоку партнёра ниже не нужна отдельная команда ip -6 route.
[Interface]
Address = 2001:db8:aa:ff::1/64, fd00:aa:ff::1/64
ListenPort = 51820
[Peer]
PublicKey = <site-b-public-key>
Endpoint = 203.0.113.20:51820
AllowedIPs = 2001:db8:bb::/48, fd00:bb::/48
Оба списка должны быть зеркальными: площадка B принимает префикс площадки A как удалённый селектор и маршрутизирует его обратно, и туннель работает только тогда, когда оба направления совпадают. Асимметричный список даёт симптом, который съедает больше всего времени, — здоровое рукопожатие, счётчики движутся с одной стороны, и ping из A в B, который проходит, тогда как ничего из B не достигает A.
Пересылка, маршруты и обратный префикс
Linux не пересылает IPv6, пока вы не скажете, и настройка действует на пространство имён, а не на интерфейс:
sysctl -w net.ipv6.conf.all.forwarding=1
ip -6 route add 2001:db8:bb::/48 dev wg0
ip -6 route get 2001:db8:bb::10 from 2001:db8:aa::10
Вторая строка — та, которую забывают в схемах на основе маршрутов. IPsec на основе политик устанавливает политику XFRM, соответствующую префиксам, но конфигурация на основе маршрутов с vti6 или WireGuard требует настоящего маршрута IPv6, указывающего на это устройство. Сохраните sysctl в /etc/sysctl.d/; иначе перезагрузка молча вернёт пересылку в выключенное состояние.
У пересылки есть побочный эффект, который стоит знать: ядро тогда игнорирует объявления маршрутизаторов, если net.ipv6.conf.<wan>.accept_ra не равен 2, поэтому шлюз, чей адрес WAN приходит по SLAAC, может потерять маршрут по умолчанию в тот момент, когда вы включаете пересылку. Задайте accept_ra = 2 на этом интерфейсе в том же файле, и маршрут выживет. Ядро площадки B также должно узнать 2001:db8:aa::/48 от шлюза туннеля — статическим маршрутом или протоколом маршрутизации поверх туннеля, иначе ответ умирает внутри собственной сети B.
Проверка в четыре шага, которая локализует поломку
Работайте в этом порядке и останавливайтесь на первом шаге, который не удался, потому что каждый следующий шаг зависит от предыдущих.
- Селекторы:
swanctl --list-sasдолжен показать пару префиксов IPv6 на child SA, илиwg show wg0 allowed-ipsудалённый префикс со свежимlatest-handshakes. - Маршрут ядра:
ip -6 route get 2001:db8:bb::10должен ответить устройством туннеля, а не интерфейсом WAN. - Внутренняя запись:
tcpdump -ni wg0 ip6во время ping между настоящими хостами локальной сети. Пакеты, которые уходят, но ни один не возвращается, указывают на маршрут или брандмауэр B; полное отсутствие пакетов указывает назад на шаг 1 или 2. - Назначение: на хосте B
ss -6 -ltnподтверждает, что служба слушает на IPv6, а локальные счётчики брандмауэра показывают, был пакет принят или отброшен.
MTU входит в тот же проход. Маршрутизаторы IPv6 не фрагментируют, поэтому слишком большой пакет отбрасывается и Packet Too Big возвращается отправителю. Если рукопожатия и маленькие ping работают, а передача файла застревает, уменьшите полезную нагрузку, пока она не пройдёт: ping -6 -s 1400 -c 3 2001:db8:bb::10. Интерфейсы WireGuard по умолчанию имеют MTU 1420 байт, а IPsec поверх IPv4 вычитает больше, поэтому задать внутренний MTU лучше, чем надеяться на обнаружение MTU пути через брандмауэр, который отбрасывает ICMPv6.
Режимы отказа, похожие на проблемы с брандмауэром
Самый частый — вовсе не брандмауэр: служба привязывается к 0.0.0.0 вместо ::, запись AAAA существует, и клиент получает быстрый отказ по IPv6, который браузер с dual-stack скрывает, откатываясь на IPv4. Если отказывают только некоторые приложения, проверьте семейство прослушивания, прежде чем трогать набор правил.
Второй — фильтрация ICMPv6. Neighbor Discovery и Packet Too Big — часть нормальной работы, а не диагностики, и политика, отбрасывающая весь ICMPv6, создаёт туннель, который соединяется, аутентифицируется и затем застревает на всём, что больше маленького пакета. Разрешите типы, от которых зависит путь, и оставьте счётчики включёнными, чтобы отбрасывание было видно.
Третий — префикс, добавленный в туннель, но никогда — в план адресов площадки, которой он принадлежит, поэтому ни один маршрутизатор его не объявляет и ни один хост не может ответить из него. Начните с одного префикса и одной пары хостов, докажите оба направления четырьмя шагами, затем добавьте остальное; сломанный префикс остаётся одной строкой в конфигурации вместо переделки. Единственная официальная ссылка, которую стоит держать открытой, — RFC 8200, которая определяет заголовок, который несёт туннель.