Получаем IPv6 адрес с помощью VPS.
В этом тексте рассматривается вариант получения IPv6 адреса, когда VPS провайдер выдает адреса поштучно в режиме on-link и фильтрует ndp трафик.
Сразу оговорюсь что я не являюсь экспертом по сети, и все знания были получены в ходе моего личного опыта, так что учтите этот момент. Возможно некоторые вещи вы можете сделать более правильно.
Для примера используется Debian 12 и WireGuard. Вы можете использовать любую другую ОС и туннель, адаптируя действия с этого текста под ваш стек. Текст предполагает, что вы уже знакомы с настройкой WireGuard и nftables, поэтому некоторые детали будут упущены.
Наша задача - получить полноценный IPv6 адрес.
И так, приступим.
Нам понадобится 2 или больше IPv6 адреса, один для самого VPS, и остальные для раздачи клиентам.
Будем считать что нам выданы 2 шт адреса:
2001:db8:10::1 - пусть это будет адрес VPS сервера
2001:db8:10::2 - а этот адрес опрокинем клиенту
А также держим в голове эти адреса и метки:
2001:db8:10::ff - адрес шлюза для выхода в интернет
fe80::ab10:1 - link-local адрес VPS
fe80::cd20:cd20:cd20:ff - link-local адрес маршрутизатора провайдера
ens3 - имя сетевого интерфейса VPS для выхода в интернет
wg0 - имя интерфейса туннеля WireGuard
Заметка: для простоты реальные IPv6 адреса заменены на вымышленные.
Давайте кратко пройдемся по режимам routed и on-link.
Режим routed - это когда в качестве шлюза на выданный вам префикс выступает адрес вашего VPS. Если с интернета пришел пакет с адресом назначения в пределах вашего префикса, то маршрутизатор провайдера просто пересылает его на вашу VPS. ndp запрос со стороны провайдера делается только для адреса VPS. Сам адрес VPS сервера может быть с отдельной подсети, отличной от выданной вам. Или это может быть link-local адрес. Или еще по другому может быть, зависит от настроек провайдера.
Режим on-link - это когда маршрутизатор провайдера полагает, что все выданные вам адреса находятся в одном сегменте L2, поэтому он пытается отправить пакет напрямую, делая ndp запросы на каждый адрес назначения.
Отдельный момент: в моем случае провайдер дал только данные об IP адресах и шлюзе. Вся настройка назначения адреса в интерфейс, указание адреса шлюза, DNS и маршрута лежал на пользователя, поэтому некоторые моменты у меня уже заранее настроены.
И тут у нас 2 проблемы:
1. Как вы уже догадались, клиент WireGuard находится не в одном сегменте L2 с маршрутизатором.
2. Провайдер почему то фильтрует ndp трафик, наверное для этого есть веская причина.
Фильтрация заключается в игнорировании ndp пакетов с link-local адреса.
Сразу включим функцию пересылки пакетов в ядре Linux.
sysctl -w net.ipv6.conf.all.forwarding=1Если у вас много интерфейсов, мы можете включать точечно по нужным интерфейсам.
Эта настройка сохранится до перезагрузки сервера. Если вы хотите её оставить после перезагрузки, то запишите это в конфиг файл sysctl. В Debian 12 это /etc/sysctl.conf
Так как у нас режим on-link, то нам придется сделать так, чтобы клиент логически оказался в одном сегменте с маршрутизатором.
Для этого в Linux есть система проксирования соседей IPv6 - NDP Proxy.
NDP (Neighbor Discovery Protocol) - это протокол, с помощью которого узлы узнают MAC адреса соседей по их IPv6 адресам. Близкий аналог ARP из IPv4.
Чтобы клиент оказался в одном сегменте с маршрутизатором, включим NDP Proxy и добавим адрес клиента в список проксирования.
sysctl -w net.ipv6.conf.ens3.proxy_ndp=1 ip -6 neigh add proxy 2001:db8::2 dev ens3 ip -6 neigh show proxyТеперь когда маршрутизатор опросит соседей у кого адрес 2001:db8::2, наш VPS будет отзываться, как будто этот адрес назначен ему.
Так мы решили проблему 1.
Теперь настроим WireGuard.
Полагается, что вы знакомы с настройкой WireGuard, поэтому часть настроек с ключами пропустим. В конфиге указаны только интересующие нас поля.
Сделаем такой конфиг на сервере:
[Interface] Address = fd20::1/64 [Peer] AllowedIPs = fd20::2/128, 2001:db8::2/128И такой конфиг для клиента:
[Interface] Address = fd20::2/64, 2001:db8::2/128 DNS = 2001:4860:4860::8888, 2001:4860:4860::8844 [Peer] Endpoint = server-ip:port AllowedIPs = fd20::1/128, 2000::/3 PersistentKeepalive = 20
DNS вы можете ставить любой другой.
Указание PersistentKeepalive обязательна, если вы за CG-NAT. У некоторых хостеров WireGuard может нормально работать только с PersistentKeepalive = 5-10.
Дополнительно можно указать MTU по IPv4 каналу между VPS и клиентом. В сети пишут, что накладные расходы WireGuard достигают до 80 байт и MTU по умолчанию стоит 1420. Если ваш интернет подключен по PPPoE с MTU 1492, то можно указать MTU в WireGuard в 1412, чтобы пакеты гарантированно влезли в размер канала. Но это необязательно делать.
systemctl start wg-quick@wg0Разрешаем пересылку пакетов в nftables. nftables по умолчанию может быть отключен или не установлен.
table inet gate {
chain forward {
type filter hook forward priority filter; policy drop;
iifname "wg0" oifname "ens3" accept
iifname "ens3" oifname "wg0" accept
}
}
Не забываем открыть входящий порт для WireGuard.В системах, где нет ndp фильтрации, по идеи настройка на этом закончена, но мы идем дальше.
Теперь самое важное.
Если из интернета приходит входящий трафик на адрес VPS, или с адреса VPS уходит исходящий трафик, то ndp трафик со стороны VPS всегда идет с глобального адреса 2001:db8:10::1. ndp трафик не блокируется и интернет у VPS работает.
А когда по VPS идет промежуточный трафик между интернетом и клиентом WireGuard, то ndp трафик со стороны VPS идет с link-local адреса. А как нам известно, ndp трафик с link-local адреса игнорируется, и интернет у клиента не работает.
Убедиться в этом можно запустив tcpdump по ICMPv6 сообщениям.
При входящем промежуточном пакете:
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on ens3, link-type EN10MB (Ethernet), snapshot length 262144 bytes 12:07:58.535783 IP6 fe80::cd20:cd20:cd20:ff > ff02::1:ff00:2: ICMP6, neighbor solicitation, who has 2001:db8:10::2, length 32 12:07:58.888522 IP6 fe80::ab10:1 > fe80::cd20:cd20:cd20:ff: ICMP6, neighbor advertisement, tgt is 2001:db8:10::2, length 32 12:07:59.545823 IP6 fe80::cd20:cd20:cd20:ff > ff02::1:ff00:2: ICMP6, neighbor solicitation, who has 2001:db8:10::2, length 32 12:07:59.652465 IP6 fe80::ab10:1 > fe80::cd20:cd20:cd20:ff: ICMP6, neighbor advertisement, tgt is 2001:db8:10::2, length 32 12:08:00.545227 IP6 fe80::cd20:cd20:cd20:ff > ff02::1:ff00:2: ICMP6, neighbor solicitation, who has 2001:db8:10::2, length 32 12:08:00.840472 IP6 fe80::ab10:1 > fe80::cd20:cd20:cd20:ff: ICMP6, neighbor advertisement, tgt is 2001:db8:10::2, length 32По логам видно что маршрутизатор отправляет ndp запросы для адреса клиента, VPS отвечает со своего link-local адреса, но маршрутизатор эти ответы не видит/игнорирует и отправляет запрос повторно.
При исходящем промежуточном пакете:
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on ens3, link-type EN10MB (Ethernet), snapshot length 262144 bytes 11:47:36.159995 IP6 fe80::ab10:1 > ff02::1:ff00:ff: ICMP6, neighbor solicitation, who has 2001:db8:10::ff, length 32 11:47:37.160461 IP6 fe80::ab10:1 > ff02::1:ff00:ff: ICMP6, neighbor solicitation, who has 2001:db8:10::ff, length 32 11:47:38.184495 IP6 fe80::ab10:1 > ff02::1:ff00:ff: ICMP6, neighbor solicitation, who has 2001:db8:10::ff, length 32А тут VPS хотел узнать MAC адрес шлюза, отправляя ndp запросы с link-local адреса, но маршрутизатор не отвечает.
А вот пример пинга с самого VPS до Google DNS:
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on ens3, link-type EN10MB (Ethernet), snapshot length 262144 bytes 03:25:30.757616 IP6 2001:db8:10::1 > ff02::1:ff00:ff: ICMP6, neighbor solicitation, who has 2001:db8:10::ff, length 32 03:25:30.760058 IP6 2001:db8:10::ff > 2001:db8:10::1: ICMP6, neighbor advertisement, tgt is 2001:db8:10::ff, length 32 03:25:30.760087 IP6 2001:db8:10::1 > 2001:4860:4860::8888: ICMP6, echo request, id 677, seq 1, length 64 03:25:30.782814 IP6 2001:4860:4860::8888 > 2001:db8:10::1: ICMP6, echo reply, id 677, seq 1, length 64 03:25:31.759407 IP6 2001:db8:10::1 > 2001:4860:4860::8888: ICMP6, echo request, id 677, seq 2, length 64 03:25:31.782166 IP6 2001:4860:4860::8888 > 2001:db8:10::1: ICMP6, echo reply, id 677, seq 2, length 64 03:25:32.761416 IP6 2001:db8:10::1 > 2001:4860:4860::8888: ICMP6, echo request, id 677, seq 3, length 64 03:25:32.784181 IP6 2001:4860:4860::8888 > 2001:db8:10::1: ICMP6, echo reply, id 677, seq 3, length 64Тут видим, что маршрутизатор сразу ответил после ndp запроса с глобального адреса и трафик пошел.
Поэтому нам нужно заставить Linux использовать глобальный адрес в ndp трафике.
Для этого мы можем просто удалить link-local адрес с интерфейса, чтобы VPS пришлось использовать глобальный адрес.
По правилам IPv6 удалить link-local адрес это грубое варварство, потому что каждый IPv6 узел обязан иметь такой адрес. Но в нашем конкретном случае это только плюс:
1. link-local адрес фактически бесполезен, так как ndp трафик с него игнорируется.
2. Удалив link-local адрес, мы заставим Linux использовать глобальный адрес вместо link-local где это возможно.
Удаляем link-local адрес(закройте глаза):
ip -6 addr del fe80::ab10:1/64 dev ens3Это не совсем правильно, но наша задача - получить полноценный IPv6 адрес, и это шаг к решению.
А вообще после удаления адреса мы заметим, что ничего не пострадало. IPv6 интернет у VPS работает как и раньше. А пересылка пакетов не работало так и не работает.
Теперь VPS на ndp запросы отвечает со своего глобального адреса.
Но есть и обратный эффект, без link-local адреса VPS больше не инициирует ndp запросы для пересылаемых пакетов. Так мы частично решили проблему 2.
Но что дальше делать с неработающими ndp запросами? Вообще ndp запрос со стороны VPS делается чтобы узнать MAC адрес шлюза. И мы можем сделать это самостоятельно.
Добавляем MAC адрес шлюза в таблицу ndp.
Нам нужно лишь узнать MAC адрес шлюза 2001:db8:10::ff. Давайте посмотрим текущее состояние соседей IPv6:
ip -6 neigh show fe80::cd20:cd20:cd20:ff dev ens3 FAILED 2001:db8:10::ff dev ens3 FAILEDВидим, что в списке есть наш шлюз, но без MAC адреса. Это кстати признак присутствия ndp фильтрации. Такая же картина будет и с link-local адресом.
Давайте просто попингуем Google DNS, чтобы обновить таблицу.
ping 2001:4860:4860::8888 PING 2001:4860:4860::8888(2001:4860:4860::8888) 56 data bytes 64 bytes from 2001:4860:4860::8888: icmp_seq=1 ttl=119 time=22.9 ms 64 bytes from 2001:4860:4860::8888: icmp_seq=2 ttl=119 time=22.9 msИ посмотрим еще раз:
ip -6 neigh show fe80::cd20:cd20:cd20:ff dev ens3 FAILED 2001:db8:10::ff dev ens3 lladdr 00:00:5e:00:53:01 REACHABLEТеперь у нас есть MAC адрес шлюза.
Пока в таблице есть эта запись, VPS больше не нужно делать ndp запросы. Следовательно пересылаемый трафик сразу начнет идти к шлюзу напрямую и IPv6 связь у клиента заработает. Но при тишине трафика эта запись устареет и связь у клиента пропадет.
Чтобы запись не устаревала мы можем зафиксировать его навсегда.
ip -6 neigh replace 2001:db8:10::ff lladdr 00:00:5e:00:53:01 dev ens3 nud permanentИмея постоянную запись MAC адреса шлюза и удалив link-local адрес мы добились пересылку трафика в обе стороны. Так мы решили проблему 2.
И все, с этого момента IPv6 связь между клиентом WireGuard и интернетом должно работать.
На этом настройка завершена.
Поздравляю, теперь у вас есть полноценный IPv6 адрес!
Заключение и альтернатива.
Но минус этого способа в том, что если у шлюза изменится MAC адрес, то связь IPv6 пропадет. И придется убрать постоянную запись MAC адрес шлюза, узнать новый MAC адрес и снова зафиксировать его. Но то это дело пары минут. При желании можно даже это автоматизировать. Для личного пользования вполне пойдет.
Если вас смущает постоянная запись MAC адреса, то как вариант можете его убрать и просто добавить постоянный фоновый трафик с адреса VPS, чтобы держать таблицу ndp всегда актуальной. Например пинговать адрес шлюза каждые N секунд/минут. Но мне не нравится идея генерации постоянного трафика, поэтому первый вариант для меня предпочтителен.
Но если вас такой вариант не устраивает, то в качестве альтернативы могу посоветовать вместо удаления link-local адреса и добавления постоянной записи MAC адреса шлюза, попробовать поиграться с nftables, создать таблицу netdev, создать цепочку с хуком egress, отловить ICMPv6 пакеты и менять адреса с link-local на глобальные.
Хотя не знаю как это отразится на ресурсы CPU.
Почему netdev/egress, а не postrouting? Потому что у меня ICMPv6 пакеты только там отлаливались, попытка сделать это в postrouting ничего не дало.
Если ваша цель получить только доступ к IPv6 сети, можно вообще обойтись без всего этого просто настроив NAT66, но это совсем не то что мы хотели изначально.
Не забывайте обязательно настроить фаервол/брандмауер, так как вы больше не за NAT-ом. А также учтите, что MTU IPv6 канала будет меньше стандартных 1492-1500.
На этом текст подходит к концу, надеюсь у вас все получилось и узнали что-то новое. Спасибо за внимание.
Всем добра и удачи!