IPv6 и dual-stack в Qeli
Внешний IPv4/IPv6 carrier, внутренний NetworkPlan v2, ULA/GUA, NAT66 или маршрутизация, встроенный NDP proxy для on-link префиксов, DNS, MTU и безопасная миграция.
Внешний carrier и внутренний TUN независимы
Начиная с 0.8.0 Qeli разделяет семейство соединения с сервером и семейства пакетов внутри туннеля. bind/listen/server могут использовать IPv4, IPv6 или hostname с A/AAAA, а tun.ip_mode, пулы и аутентифицированный NetworkPlan определяют внутренний IPv4/IPv6 data plane.
server = [2001:db8::10]:443.tcpdump.tun.ip_mode = ipv4 | dual | ipv6
| Режим | Что получает клиент | Применение |
|---|---|---|
ipv4 | IPv4 lease, маршруты и DNS. | Совместимость или сервер без готового IPv6 uplink. |
dual | Независимые IPv4 /32 и IPv6 /128 на L3 TUN, оба набора маршрутов и DNS. | Рекомендуемый обычный режим. |
ipv6 | Только IPv6 lease и план. | Специализированная IPv6-only сеть; доступа к IPv4-only сервисам без отдельного NAT64 не будет. |
NetworkPlan v2 отдельно передаёт assigned address, on-link prefix и gateway для каждого семейства и применяется атомарно. Полуприменённый план не считается успешным подключением.
Минимальный dual-stack пример
tun.ip_mode = dual
tun.name = vpn0
tun.address = 10.19.0.1
tun.ipv6_address = fd71:e1:8000:102::1
tun.mtu = 1400
tun.device_type = tun
pool.cidr = 10.19.0.0/24
pool.ipv6.cidr = fd71:e1:8000:102::/64
routing.nat.enabled = true
routing.ipv6.mode = nat66
dns.enabled = true
dns.listen = 10.19.0.1
dns.listen_ipv6 = fd71:e1:8000:102::1
dns.push_servers = 10.19.0.1, fd71:e1:8000:102::1
Используйте уникальный для развёртывания RFC 4193 ULA /48 и отдельный /64 на профиль. Не копируйте пример fd71:… буквально между независимыми установками и не используйте link-local fe80::/10 как пул. Quick Start генерирует случайный 40-битный Global ID.
routing.ipv6.mode
| Значение | Что делает | Требования |
|---|---|---|
nat66 | Подменяет ULA-источник на IPv6 WAN сервера. | IPv6 default route, публичный GUA из 2000::/3 и рабочий ip6tables NAT. Удобно на обычном VPS, но это NAT. |
route | Сохраняет исходный IPv6 клиента. | Для выхода в интернет — маршрутизируемый провайдером GUA-префикс и обратный маршрут. Для LAN/site-to-site достаточно маршрутов между своими сетями. |
off | Не выпускает IPv6 из профиля и ставит fail-closed изоляцию. | Рабочий ip6tables нужен, чтобы DROP был гарантирован. |
Для routed GUA задавайте reservation или static_ipv6; CLI поддерживает qeli add-client --static-ipv6 ….
NDP proxy: когда нужен и как настроить
NDP (Neighbor Discovery Protocol) в IPv6 выполняет в том числе задачу, похожую на ARP в IPv4: соседний маршрутизатор отправляет Neighbor Solicitation (NS) и спрашивает, какой MAC-адрес обслуживает конкретный IPv6. Адрес VPN-клиента находится на TUN, а не на WAN-интерфейсе сервера, поэтому при on-link префиксе Qeli отвечает upstream-маршрутизатору сообщением Neighbor Advertisement (NA) с MAC-адресом сервера и затем маршрутизирует пакет в активную VPN-сессию.
routing.ipv6.ndp_proxy = off.Выберите режим по топологии провайдера
| Что делает провайдер | Признак | Настройка Qeli |
|---|---|---|
| Маршрутизирует отдельный префикс через WAN-адрес сервера | Для клиентских адресов на WAN не приходят NS; в панели провайдера указан route/next hop. | routing.ipv6.mode = routerouting.ipv6.ndp_proxy = off |
| Считает отдельный префикс on-link | На WAN приходят NS для каждого публичного IPv6 клиента. | routing.ipv6.mode = routerouting.ipv6.ndp_proxy = required |
| Топология пока неизвестна | Нет точного описания у провайдера. | Сначала проверьте трафик через tcpdump. auto допустим для диагностики, но для рабочей on-link схемы используйте required. |
Как понять, нужен ли proxy
Не назначайте клиентский префикс WAN-интерфейсу. Остановите профиль или сервер, запустите захват на фактическом IPv6 uplink и с независимого внешнего IPv6-хоста обратитесь к будущему адресу клиента:
# На сервере Qeli
$ sudo systemctl stop qeli-server
$ ip -6 addr show dev ens3
$ ip -6 route show table all
$ sudo tcpdump -ni ens3 'icmp6 && ip6[40] == 135'
# На внешнем IPv6-хосте
$ ping -6 2001:db8:1200:10::100Строка Neighbor Solicitation, who has 2001:db8:1200:10::100 означает, что upstream ищет MAC для клиентского адреса и этой схеме нужен NDP proxy. Если NS нет, сначала проверьте правильность адреса, WAN-интерфейс, firewall/security group и условия провайдера: одно отсутствие пакета ещё не доказывает L3-routed схему.
Полный фрагмент профиля
Адреса 2001:db8::/32 ниже предназначены только для документации. Замените их отдельным GUA-префиксом, выданным провайдером для клиентов.
[profile:public-v6-onlink]
tun.ip_mode = ipv6
tun.name = vpn-public
tun.ipv6_address = 2001:db8:1200:10::1
tun.mtu = 1280
pool.ipv6.cidr = 2001:db8:1200:10::/64
# Сохранять публичный IPv6 клиента; MASQUERADE не создаётся.
routing.ipv6.mode = route
routing.ipv6.interface = ens3
routing.ipv6.ndp_proxy = required
routing.ipv6.ndp_proxy_interface = ens3
[user:alice]
static_ipv6 = 2001:db8:1200:10::100pool.ipv6.cidr интерфейсу ens3 и не создавайте для него connected route через WAN. Маршрут клиентского префикса должен вести только в TUN профиля; upstream выполняет NDP со своей стороны. Для dual-stack замените tun.ip_mode = ipv6 на dual и сохраните существующие параметры IPv4.Параметры и требования
| Режим | Поведение |
|---|---|
off | Responder выключен. Значение по умолчанию и правильный выбор для обычного L3-routed префикса. |
auto | Пытается открыть responder. При неподходящем интерфейсе, отсутствии Ethernet или CAP_NET_RAW пишет предупреждение и продолжает работу без него. |
required | Не запускает профиль, пока responder не привязан к выбранному интерфейсу. Рекомендуется для рабочей on-link схемы. |
Пустой routing.ipv6.ndp_proxy_interface использует фактический uplink из routing.ipv6.interface или IPv6 default route. Нужен Ethernet-совместимый Linux-интерфейс и право CAP_NET_RAW; обычный системный сервис Qeli уже запускается с необходимыми правами. NDP proxy допустим только вместе с source-preserving режимом route, но не с nat66 или off.
Какие адреса Qeli объявляет
- Точный IPv6 lease активной сессии из
pool.ipv6.cidr, включаяstatic_ipv6. - Адреса внутри non-default IPv6
client_subnet, зарегистрированного за подключённым router/site-to-site клиентом. - Только адреса живых сессий: disconnect, revoke или замена сессии немедленно снимают ownership.
/0, link-local, multicast, неверные NS и посторонние target-адреса игнорируются. Qeli не превращает responder в общий multicast relay.
Проверка после запуска
$ sudo qeli check-config --config /etc/qeli/server.conf
$ sudo systemctl restart qeli-server
$ sudo journalctl -u qeli-server -n 100 --no-pager | grep -F 'IPv6 NDP proxy'
$ ip -6 route show 2001:db8:1200:10::/64
$ sudo tcpdump -ni ens3 'icmp6 && (ip6[40] == 135 || ip6[40] == 136)'При required журнал должен сообщить session-aware IPv6 NDP proxy active on 'ens3', а маршрут клиентского /64 — вести через vpn-public. Пока Alice отключена, новый NS для её адреса не получает NA. После подключения Qeli отвечает своим MAC, а внешний трафик направляется в её TUN-сессию. После отключения новые ответы прекращаются; старая запись в neighbor cache upstream может сохраняться до истечения своего таймера.
| Симптом | Что проверить |
|---|---|
required не запускает профиль | Имя uplink, Ethernet-тип интерфейса, root/CAP_NET_RAW и сообщение в journal. |
В tcpdump нет NS | Правильность адреса и WAN-интерфейса, provider route/security group; возможно, префикс уже маршрутизируется и proxy не нужен. |
| NS есть, но NA нет при подключённом клиенте | Фактически выданный static_ipv6, состояние сессии и попадание target в pool или активный client_subnet. |
| NA есть, но трафик не доходит | Маршрут /64 в TUN, IPv6 forwarding, ip6tables/cloud firewall и firewall клиента. |
auto запустился без NDP | Найдите предупреждение NDP proxy auto mode is unavailable, исправьте причину и переключитесь на required. |
ipv6 = auto | required | off
| Политика | Результат |
|---|---|
auto | Принимает IPv4, dual или IPv6. Dual может безопасно понизиться до IPv4, если платформа не умеет полный IPv6-контракт. |
required | Требует внутренний IPv6 и fail-closed при старом сервере, IPv4-only профиле, MTU ниже 1280 или неполной поддержке платформы. |
off | Просит IPv4 у dual-профиля и отклоняет IPv6-only план. |
В full-tunnel отсутствие одного семейства блокируется по умолчанию. allow_ipv6_leak=true или allow_ipv4_leak=true — явный escape hatch, разрешающий отсутствующему семейству идти физическим путём. Оба значения по умолчанию false.
Настройте оба семейства целиком
dns.listenиdns.listen_ipv6должны совпадать с активными gateway-адресами профиля;dns.push_serversне должен раздавать недоступный listener.- IPv6 требует MTU не меньше 1280. Политика
requiredотвергает меньший план, а PMTU probing подбирает безопасный carrier budget. route,route_file, server push и ACL принимают IPv4/IPv6 CIDR, но применяются только для согласованного семейства.- Outer DNS для hostname endpoint может вернуть A и AAAA; roaming способен перенести сессию между семьями после аутентифицированной проверки пути.
Старый конфиг не становится dual-stack автоматически
- Сделайте backup и обновите сервер до 0.8.1.
- Выберите уникальный ULA /64 либо получите у провайдера routed GUA-префикс.
- Добавьте
tun.ip_mode,tun.ipv6_address,pool.ipv6.cidr, IPv6 DNS иrouting.ipv6.mode. - Выполните
qeli check-config; для NAT66 дополнительно проверьте default route, публичный source GUA и ip6tables NAT. - Оставьте клиентам
ipv6=auto, проверьте IPv4 и IPv6 отдельно, затем при необходимости ужесточайте доrequired.
Чего Qeli не делает
- Qeli не предоставляет NAT64/DNS64. IPv6-only клиент не откроет IPv4-only ресурс без отдельной NAT64-инфраструктуры.
- Qeli не превращает случайный WAN /64 в клиентский pool: нужен отдельный routed/on-link префикс и корректный обратный путь.
- IPv6 внутри туннеля не означает, что серверный uplink автоматически готов: проверяйте адрес, default route, firewall и обратный маршрут.