Здравствуйте. Задумался о нормальной настройке ipv6 в России, но столкнулся с полным отсутствием хоть какой-то поддержки даже у хостеров. Расскажите пожалуйста о своём опыте. Знаете ли вы хоть одного хостера кто делегирует ipv6 префикс в России в routed, не on-link. aeza делает это в on-link, что является безумием ещё более высокого уровня, чем выделение куцых копеешных /64. Или расскажите другие способы, как вы решаете эти проблемы в РФ.
Я и иностранных таких не встречал.
Вариант раз, костыльный: явно прописать все нужные адреса на интерфейс (если их не слишком много и нет намерения отдавать их дальше куда-нибудь в VPN)
Вариант два, правильный: иcпользовать NDP proxy (ndppd)
Неправильно прочитал вопрос.
Дублирует вопрос:
Выбор VPS сервиса для России - #61 by anon27947102
На аезе /48 маршрутизируемый из коробки. На вдсине /64 с фильтрацией NAM, необходимо в поддержку написать тикет для отключения фильтрации.
Спасибо за ответ. Мне сегодня лично админ aeza в телеграмме написал, что, конечно, /48 не маршрутизируемый, а on-link. /48 как целый префикс это безумно жирно. Но, как выяснилось, они не маршрутизируют ничего. /64 просто не нужен.
Если можете, то тогда объясните мне несколько ньюансов. /48 on-link значит, что они дают еёо. Они раздают её всем, как общую на какую-то большую часть vpsов за их vlan. Но это разве не вызывает широковещательный шторм из-за ndp и прочие радотости безумия? Разве по стандарту вообще не запрещается делать on-link короче /64?
Я бы не сказал, что он правильный, он скорее тоже костыльный. Ни чем объяснить такое поведение хостеров нельзя.
В tcpdump не вижу шторма.
Тоже по стандарту RFC работает. А как ещё правильно, анонсировать целые префиксы, гоняя ненужный трафик?
UPD:
Я, кажется, понял, вы про режим static
? Да, он не рекомендуется для больших подсетей, и я его не использую. В режиме auto
всё нормально.
UPD2:
Какую задачу нужно решить, раздать клиентам через тунель IPv6 GUA? Я себе сделал два варианта: с перенаправлением широковещательных NDP через ndppd и анонсом через скрипты OpenVPN где широковещательные пакеты не ходят.
Я не понимаю вопроса. Какой ненужный трафик?
Они просто должны анонсировать префикс, а не лепить общую площадку для всех. Решения хостеров выглядят очень странным. Насколько я понимаю, по RFC 4291 on-link префиксы для сетей с конечными устройствами (а по факту с кучей VPS) должны быть /64, чтобы SLAAC не ломался. Делать префикс короче для такой сети — это прямое нарушение лучших практик. Любой префикс меньше /64 должен означать, что они делегируют его мне полностью, а не заставляют использовать прокси костыли от таких провайдеров.
Какую задачу нужно решить, раздать клиентам через тунель IPv6 GUA?
Да, я хотел бы раздать многим конечным устройствам GUA. Но это не клиенты, это мои конечные устройства, поэтому было бы более чем достаточно нормального /56.
В режиме
auto
всё нормально.
Вы не могли бы уточнить, что это за режим.
По поводу длины, согласен, RIPE рекомендует выдавать /64 на одного абонента ISP.
В этом режиме ndppd будет перенаправлять NAM пакеты между тунелем и сетью сервера на вышестоящий роутер. Приходящие пакеты на неанонсированные адреса просто отсекутся этим роутером и до сервера не дойдут. Разумеется, тунель должен поддерживать мультикаст передачу на адреса fe80::/10 и ff00::/8 (TAP, например).
Ваш ход с ВПНом мне нравится, это всё переносит на L3. Не могли бы вы раскрыть его более подробно. В этом случае я должен буду на уровне ОС захватывать статически /64, чтобы отправить её в ВПН туннель на роутер?
Вы пишите, что используете два варианта. В чём смысл работать сразу двумя?
Не одновременно, а на разных впс по разному, т.к. использую разные протоколы (несколько резервных тунелей в разные локации).
Необходимо
- настроить тунель так, чтобы оправленные с клиента мультикаст пакеты fe80::/10 и ff00::/8 приходили на виртуальный интерфейс сервера (проверяется в tcpdump)
- выданный префикс разделить на части: одну для хоста, вторую для раздачи клиентов, прописав хостовую часть в `/etc/network/interfaces` а клиентскую в конфигурацию тунеля
Пример
Выдан префикс
2001:db8:dead::/48
Поделим его на два:2001:db8:dead:0::/64
и2001:db8:dead:ffff::/64
В/etc/network/interfaces
надо изменитьiface eth0 inet6 static address 2001:db8:dead:0::1 netmask 64
А
2001:db8:dead:ffff::1/64
использовать для tun - настроить ndppd, например
ndppd.conf
route-ttl 30000 proxy eth0 { router yes timeout 500 ttl 30000 rule 2001:db8:dead:ffff::/64 { auto } }
-
включить форвардинг
sysctl -w net.ipv6.conf.all.forwarding=1
или что-то в этом роде
- Настроить политики брандмауэра
- ?????
- PROFIT
firewalld
Создание внешней зоны
firewall-cmd --permanent --new-zone="my_external"
firewall-cmd --permanent --zone="my_external" --set-target=DROP
firewall-cmd --permanent --zone="my_external" --add-interface=eth0
firewall-cmd --permanent --zone="my_external" --add-masquerade
Создание зоны тунеля
firewall-cmd --permanent --new-zone="my_tun"
firewall-cmd --permanent --zone="my_tun" --add-interface=tun0
Настройка транзитного трафика между зонами
firewall-cmd --permanent --new-policy="tun_to_exernal"
firewall-cmd --permanent --policy="tun_to_exernal" --set-target=ACCEPT
firewall-cmd --permanent --policy="tun_to_exernal" --add-ingress-zone="my_tun"
firewall-cmd --permanent --policy="tun_to_exernal" --add-egress-zone="my_external"
В политику входящего трафика можно добавлять rich правила
firewall-cmd --permanent --new-policy="exernal_to_tun"
firewall-cmd --permanent --policy="external_to_tun" --add-ingress-zone="my_external"
firewall-cmd --permanent --policy="external_to_tun" --add-egress-zone="my_tun"
firewall-cmd --permanent --policy="external_to_tun" --add-rich-rule='rule family="ipv6" icmp-type name="echo-request" accept'
firewall-cmd --permanent --policy="external_to_tun" --add-rich-rule='rule family="ipv6" destination address="2001:db8:dead:ffff::1000" port port="12345" protocol="tcp" accept'
firewall-cmd --permanent --policy="external_to_tun" --add-rich-rule='rule family="ipv6" destination address="2001:db8:dead:ffff::1000" port port="12345" protocol="udp" accept'
Отклонение остального входящего трафика
firewall-cmd --permanent --new-policy="external_to_tun_r"
firewall-cmd --permanent --policy="external_to_tun_r" --set-priority=1
firewall-cmd --permanent --policy="external_to_tun_r" --add-ingress-zone="my_external"
firewall-cmd --permanent --policy="external_to_tun_r" --add-egress-zone="my_tun"
firewall-cmd --permanent --policy="external_to_tun_r" --add-rich-rule='rule family="ipv6" destination address="2001:db8:dead:ffff::/64" reject type="icmp6-adm-prohibited" limit value="1/s"'
Отладка NDP
В одном окне терминала запускаете tcpdump с фильтром вашей сети
tcpdump -n -i eth0 -U -w ~/eth0.pcap --print "ip6 and (not icmp6 or icmp6[0] != 135 or (icmp6[12] == 0xde and icmp6[13] == 0xad))"
Во втором окне терминала включаете NDP proxy
sysctl -w net.ipv6.conf.all.proxy_ndp=1
sysctl -w net.ipv6.conf.eth0.proxy_ndp=1
Добавляете NDP прокси для эмулируемого адреса 2001:db8:dead:ffff::1000 командой
ip -6 neigh add proxy 2001:db8:dead:ffff::1000 dev eth0
Запускаете инструмент манипуляции пакетов командой:
scapy -H
Отправляете пинг пакет от эмулируемого клиента на quad9:
send(IPv6(src="2001:db8:dead:ffff::1000", dst="2620:fe::9") / ICMPv6EchoRequest())
В результате в первом окне маршрутизатор пришлёт Neighbor Solicitation, прокси ответит NAM, и придёт ответ пинга
Команда принудительного анонса Neighbor Advertisement для машрутизатора:
send(IPv6(src="2001:db8:dead:ffff::1000", dst="ff02::1") / ICMPv6ND_NA(tgt="2001:db8:dead:ffff::1000", R=1, S=1, O=1) / ICMPv6NDOptDstLLAddr(lladdr="мак_адрес_роутера"))
(быть может в адресе источника должен быть link-local адрес хоста, я не помню)
Это очень подробная инструкция. Спасибо. Насколько я вижу, в ней используется именно первый, упомянутый вами метод ndppd, вместе с L2 tap OpenVPN.
Но есть ещё и другой метод с OpenVPN L3, раз там, как вы пишите:
широковещательные пакеты не ходят.
Внесите окончательную ясность. Ваша инструкция уже была очень подробной, вам наверняка не хочется расписывать ещё одну большую инструкцию, но я лишь прошу очень кратко поправить меня в понимании различия этих двух методов.
Ой, я опять перепутал, я забыл, что за L2 и L3 такие. Сейчас напишу.
UPD: в случае когда мультикаст пакеты не бегают, я не использую ndppd с нерекомендуемым static
, а использую обработку событий подключения/отключения клиента OpenVPN в дебиан (пункты 2 и 5 предыдущей инструкции актуальны):
Пример server.conf (выданный /64 префикс разделён на два /80)
management ::1 4000
local eth0
port 12345
proto udp4
dev ovpn
dev-type tun
tun-mtu 1448
script-security 2
setenv is_single_instance "yes"
setenv firewalld_zone_names "my_external my_admin"
setenv external_interface_names "eth0"
up event-handler.sh
down event-handler.sh
client-connect event-handler.sh
client-disconnect event-handler.sh
ca ca.crt
cert server.crt
key server.key
crl-verify crl.pem
dh none
topology subnet
server 10.8.0.0 255.255.255.0
server-ipv6 2001:db8:dead:beef:ffff::/80
ifconfig-pool-persist ipp.txt
client-config-dir ccd
push "redirect-gateway def1 bypass-dhcp"
# Пример выдачи IPv6 only
;push "redirect-gateway def1 !ipv4 ipv6"
push "route-ipv6 2000::/3"
push "dhcp-option DNS 10.8.0.1"
push "dhcp-option DNS 2001:db8:dead:beef:ffff::1"
keepalive 10 120
tls-crypt ta.key 0
cipher AES-256-GCM
auth SHA256
max-clients 128
user nobody
group nogroup
persist-key
persist-tun
status openvpn-status.log
verb 3
explicit-exit-notify 1
Где
event-handler.sh с правами 755
#!/bin/bash
function handleFirewallPortPriveleged {
sudo ${PWD}/event-handler-priveleged.sh "handleFirewallPort" ${1} "${2}" ${3} ${4}
}
function handleNdpProxyPriveleged {
sudo ${PWD}/event-handler-priveleged.sh "handleNdpProxy" ${1} "${2}"
}
function handleIp6ForwardingPriveleged {
sudo ${PWD}/event-handler-priveleged.sh "handleIp6Forwarding" ${1} "${2}"
}
function handleNdpNeighborPriveleged {
sudo ${PWD}/event-handler-priveleged.sh "handleNdpNeighbor" ${1} "${2}" "${3}"
}
function handleFirewallPort {
if [ -n "${1}" -a ${3} -ge 1 -a ${3} -le 65535 ]; then
protocol=""
if [ ${4} == "udp" -o ${4} == "udp4" -o ${4} == "udp6" ]; then
protocol="udp"
elif [ ${4} == "tcp-server" -o ${4} == "tcp-server" -o ${4} == "tcp-server" ]; then
protocol="tcp"
fi
if [ -n "${protocol}" ]; then
handleFirewallPortPriveleged ${1} "${2}" ${3} ${protocol}
fi
fi
}
function handleNdpProxy {
handleNdpProxyPriveleged ${1} "${2}"
}
function handleIp6Forwarding {
handleIp6ForwardingPriveleged ${1} "${2}"
}
function handleNdpNeighbor {
handleNdpNeighborPriveleged ${1} "${2}" "${3}"
}
function onUp {
if [ -n "${firewalld_zone_names}" ]; then
handleFirewallPort "add" "${firewalld_zone_names}" ${local_port_1} ${proto_1}
fi
if [ -n "${external_interface_names}" ]; then
handleNdpProxy "add" "all ${external_interface_names}"
fi
if [ -n "${external_interface_names}" ] || [ -n "${dev}" ] ; then
handleIp6Forwarding "add" "all ${external_interface_names} ${dev}"
fi
}
function onDown {
if [ ${is_single_instance} == "yes" ] && ([ -n "${external_interface_names}" ] || [ -n "${dev}" ]); then
handleIp6Forwarding "remove" "all ${external_interface_names} ${dev}"
fi
if [ ${is_single_instance} == "yes" ] && [ -n "${external_interface_names}" ]; then
handleNdpProxy "remove" "all ${external_interface_names}"
fi
if [ -n "${firewalld_zone_names}" ]; then
handleFirewallPort "remove" "${firewalld_zone_names}" ${local_port_1} ${proto_1}
fi
}
function onLearnAddress {
:
}
function onIpChange {
:
}
function onConnect {
if [ -n "${ifconfig_pool_remote_ip6}" ] && [ -n "${external_interface_names}" ]; then
handleNdpNeighbor "add" "${ifconfig_pool_remote_ip6}" "${external_interface_names}"
fi
}
function onDisconnect {
if [ -n "${ifconfig_pool_remote_ip6}" ] && [ -n "${external_interface_names}" ]; then
handleNdpNeighbor "remove" "${ifconfig_pool_remote_ip6}" "${external_interface_names}"
fi
}
function onTlsVerify {
:
}
function onAuthVerify {
:
}
function onRouteUp {
:
}
function onRoutePreDown {
:
}
function onCrResponse {
:
}
function main {
if [ -n "${script_type}" ]; then
if [ ${script_type} == "up" ]; then
onUp
elif [ ${script_type} == "down" ]; then
onDown
elif [ ${script_type} == "learn-address" ]; then
onLearnAddress
elif [ ${script_type} == "ipchange" ]; then
onIpChange
elif [ ${script_type} == "client-connect" ]; then
onConnect
elif [ ${script_type} == "client-disconnect" ]; then
onDisconnect
elif [ ${script_type} == "tls-verify" ]; then
onTlsVerify
elif [ ${script_type} == "auth-user-pass-verify" ]; then
onAuthVerify
elif [ ${script_type} == "route-up" ]; then
onRouteUp
elif [ ${script_type} == "route-pre-down" ]; then
onRoutePreDown
elif [ ${script_type} == "client-crresponse" ]; then
onCrResponse
fi
fi
}
main "${@}"
# EOF
event-handler-priveleged.sh с правами 755
#!/bin/bash
function addFirewallPort {
(set -x; firewall-cmd --zone="${1}" --add-port="${2}/${3}")
}
function removeFirewallPort {
(set -x; firewall-cmd --zone="${1}" --remove-port="${2}/${3}")
}
function addNdpProxy {
(set -x; sysctl -w net.ipv6.conf."${1}".proxy_ndp=1)
}
function removeNdpProxy {
(set -x; sysctl -w net.ipv6.conf."${1}".proxy_ndp=0)
}
function addIp6Forwarding {
(set -x; sysctl -w net.ipv6.conf."${1}".forwarding=1)
}
function removeIp6Forwarding {
(set -x; sysctl -w net.ipv6.conf."${1}".forwarding=0)
}
function addNdpNeighbor {
(set -x; ip -6 neigh add proxy "${1}" dev ${2})
}
function removeNdpNeighbor {
(set -x; ip -6 neigh del proxy "${1}" dev ${2})
}
function handleFirewallPort {
action=${1}
port=${3}
protocol=${4}
set -- ${2}
for zone_name in $@; do
if [ ${action} == "add" ]; then
addFirewallPort "${zone_name}" ${port} ${protocol}
elif [ ${action} == "remove" ]; then
removeFirewallPort "${zone_name}" ${port} ${protocol}
fi
done
}
function handleNdpProxy {
action=${1}
set -- ${2}
for interface_name in $@; do
if [ ${action} == "add" ]; then
addNdpProxy "${interface_name}"
elif [ ${action} == "remove" ]; then
removeNdpProxy "${interface_name}"
fi
done
}
function handleIp6Forwarding {
action=${1}
set -- ${2}
for interface_name in $@; do
if [ ${action} == "add" ]; then
addIp6Forwarding "${interface_name}"
elif [ ${action} == "remove" ]; then
removeIp6Forwarding "${interface_name}"
fi
done
}
function handleNdpNeighbor {
action=${1}
address=${2}
set -- ${3}
for interface_name in $@; do
if [ ${action} == "add" ]; then
addNdpNeighbor "${address}" ${interface_name}
elif [ ${action} == "remove" ]; then
removeNdpNeighbor "${address}" ${interface_name}
fi
done
}
function main {
if [ -n "${1}" ]; then
if [ ${1} == "handleFirewallPort" ]; then
handleFirewallPort ${2} "${3}" ${4} ${5}
elif [ ${1} == "handleNdpProxy" ]; then
handleNdpProxy ${2} "${3}"
elif [ ${1} == "handleIp6Forwarding" ]; then
handleIp6Forwarding ${2} "${3}"
elif [ ${1} == "handleNdpNeighbor" ]; then
handleNdpNeighbor ${2} "${3}" "${4}"
fi
fi
}
main "${@}"
# EOF
Пакет sudo должен быть установлен и надо создать файл
/etc/sudoers.d/openvpn с правами 644
nobody ALL=(ALL) NOPASSWD: /etc/openvpn/event-handler-priveleged.sh
Спасибо за подробный ответ и развернутую инструкцию. Я разобрался с предложенным вами методом для L3 VPN. Если я правильно понял, логика следующая: вместо ndppd вы используете хуки OpenVPN (client-connect
/client-disconnect
), которые через скрипты и sudo динамически добавляют и удаляют прокси-запись ip -6 neigh add proxy
для IP-адреса клиента в туннеле. Это, по сути, ручная и более контролируемая замена ndppd.
Чтобы я мог применить это например к задаче с on-link /48 и правильно пробросить GUA адреса на устройства в домашней сети, у меня осталось несколько уточняющих вопросов по вашей конфигурации:
- Вы делите выданный /64 на /80. Насколько я понимаю,
server-ipv6 .../80
в конфиге OpenVPN создает лишь пул адресов для самого туннельного соединения. Но как тогда устройства в домашней LAN за роутером получают свои GUA-адреса? Ведь для работы SLAAC им нужна подсеть /64. Или эта /80 подсеть используется только для служебной связи между VPS и роутером, а для LAN предполагается маршрутизация другой, уже полноценной /64 подсети? - Команда
ip -6 neigh add proxy
добавляет запись для одного конкретного адреса. Означает ли это, что для каждого устройства в домашней сети нужно добавлять свою прокси-запись на VPS? Или вы используете другой механизм, например,ip -6 route add .../64 dev tun0
, чтобы ядро VPS знало, куда отправлять всю подсеть целиком, а proxy neigh нужен только для адреса самого роутера? - Есть ли принципиальная причина использовать именно OpenVPN для этого метода? Возможно ли реализовать ту же логику с хуками (
PostUp
/PostDown
) в WireGuard, или есть какие-то ограничения?
Можно обойтись одной подсетью /64:
- На внешний интерфейс прописать любой адерс из выданной подсети с префиксом /128.
- На интерфейс vpn выбрать другой адрес, но префикс задать /64.
- Включить маршрутизацию
Таким-же методом можно смаршрутизировать /64 от провайдера в локальную сеть.
Спасибо за наводку на этот изящный метод борьбы с хостерми-жлобами. Он выглядит гораздо чище, чем запуск отдельного демона.
Если я правильно понял вашу логику, то этот хак с ядром работает следующим образом:
- Мы назначаем на внешний интерфейс (eth0) один-единственный адрес с префиксом /128 из выданной нам /64 подсети.
- Этим мы говорим ядру Linux, что только этот конкретный адрес является локальным для
eth0
, а вся остальная /64 подсеть — нет. - Затем мы назначаем всю /64 подсеть на наш VPN-интерфейс (tun0 или wg0), что эффективно создает для нее маршрут.
- Когда на VPS включен форвардинг (
net.ipv6.conf.all.forwarding=1
), ядро видит, что на него приходят NDP-запросы для сети, которая считается on-link для вышестоящего оборудования, но для которой у него самого есть явный маршрут на другой интерфейс. - В итоге, ядро само, без всяких ndppd, начинает выполнять Proxy NDP: оно отвечает на внешние запросы своим MAC-адресом, а полученные пакеты пересылает в туннель согласно таблице маршрутизации.
Я писал, что моя задача нормально настроить ipv6, я занимаюсь сегментацией своих сетей на несколько vlan, и каждому из них, по стандарту, требуется своя уникальная /64 подсеть, чтобы SLAAC и другие механизмы IPv6 работали корректно. Ваш метод идеально подходит для того, чтобы прокинуть одну единственную подсеть /64 в домашнюю сеть. Если бы у меня была только одна локалка, я бы просто этим не занимался.
Но в вашем решении я увидел универсальный принцип, или вы это и сами имели ввиду. Как мне кажется, можно масштабировать и на мою задачу с on-link /48. Хочу вынести на обсуждение возможное развитие вашей идеи.
Идея масштабирования для on-link /48 и нескольких VLAN:
- Так же, как в вашем методе, назначаем на eth0 один адрес из /48 пула с префиксом /128.
- Включаем форвардинг.
- А теперь, вместо одного маршрута, мы создаем несколько, по одному для каждой /64 подсети, которую хотим использовать дома.
Таким образом, я смогу использовать тот же самый изящный трюк, который вы предожили, но на несколько подсеток? Выглядит как рабочий и чистый план, без лишних демонов. Или я что-то упускаю?
Я, наверное, опять вас не правильно понял. В варианте когда не бегают мультикаст пакеты никак не раздать без передачи служебной информации о подключении. Либо делать через ndppd static
.
Да.
Добавление маршрута в любом случае происходит самой службой тунеля.
Других приложений с обработкой событий не видел.
Эти команды выполняются при запуске и остановки службы на сервере. У пиров нет команд.
Самый простой способ раздать GUA устройствам внутри LAN — это настроить по кастомному L3 тунелю, но c поддержкой мультикаст пакетов (могу скинуть в ЛС) и сделать так (но мне это не подошло, т.к. виндовс не поддерживает несколько таблиц маршрутизации — хотел раздавать внутри локальной сети один префикс, а сама система имела в приоритете по метрике интерфейса другой, а в линуксе это можно сделать)
Спасибо, я понял большую часть. Да скиньте, если можете. Я изучаю все предложенные варианты. Буду исследовать, после возможно напишу статью со всеми вариантами, соберу все скрипты, может перепишу на другой язык, если будет возможно (не люблю bash, но только он иногда полностью доступен для всех).
Для раздачи /64 устройствам в LAN тогда лучше раздавать не /64 из выданного префикса /48, а /52 /56 или /60 клиентам тунеля, а с клиента тунеля тоже сделать разделение на /64 для последующей маршрутизации — так будет по рекомендациям RIPE сделано. Мне же хватает по двухоктетным группам разделять — удобнее записывать.
Да, я уже начал прикидывать попытки реализовать /60, даже его будет достаточно для всех моих подсеток.
И всё же меня впечатляет, как aeza просто бросили с барского плеча /48 on-link, гордо заявив, что он весь дается на впс. На деле же это общий стол, а ты дальше разбирайся и жри сам, как умеешь, ведь остальные и этого не дают.
Потому что прочитали RFC с рекомендациями. Когда общался с поддержкой одного хостера, то они не понимали ничего в IPv6 даже привлекая специалистов, пришлось забить, хотя нужно было просто в виртуальном свитче отключить фильтрацию.