IPv6 and dual stack in Qeli
Outer IPv4/IPv6 carriers, inner NetworkPlan v2, ULA/GUA, NAT66 or routing, DNS, MTU, leak controls and safe migration of an existing profile.
The outer carrier and inner TUN are independent
Since 0.8.0, Qeli separates the server-connection family from packet families inside the tunnel. bind/listen/server may use IPv4, IPv6 or a hostname with A/AAAA, while tun.ip_mode, pools and the authenticated NetworkPlan define the inner IPv4/IPv6 data plane.
server = [2001:db8::10]:443.tun.ip_mode = ipv4 | dual | ipv6
| Mode | Client plan | Use |
|---|---|---|
ipv4 | IPv4 lease, routes and DNS. | Compatibility or no ready IPv6 uplink. |
dual | Independent IPv4 /32 and IPv6 /128 on L3 TUN, both route and DNS sets. | Recommended normal mode. |
ipv6 | IPv6 lease and plan only. | Specialized IPv6-only network; IPv4-only services need separate NAT64. |
NetworkPlan v2 separately carries assigned address, on-link prefix and gateway for each family and applies the plan atomically.
Minimal dual-stack example
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
Use a deployment-unique RFC 4193 ULA /48 and one /64 per profile. Do not copy the sample fd71:… across deployments or use fe80::/10 as a pool. Quick Start randomizes the 40-bit Global ID.
routing.ipv6.mode
| Value | Behavior | Requirements |
|---|---|---|
nat66 | Translates the ULA source to the server's WAN IPv6. | IPv6 default route, public GUA in 2000::/3 and working ip6tables NAT. |
route | Preserves the client's IPv6 source. | A provider-routed GUA prefix and reverse route for Internet egress; own routes are enough for LAN/site-to-site. |
off | No IPv6 egress and fail-closed isolation. | Working ip6tables to guarantee DROP. |
Use reservations or static_ipv6 for routed GUA; CLI supports qeli add-client --static-ipv6 ….
routing.ipv6.ndp_proxy = off | auto | required
The preferred design has the provider route a separate client prefix to the server WAN address. Keep off there. When the provider treats a separate prefix as on-link and sends a Neighbor Solicitation for every address, enable the built-in session-aware responder.
routing.ipv6.mode = route
routing.ipv6.interface = ens3
routing.ipv6.ndp_proxy = required
routing.ipv6.ndp_proxy_interface = ens3| Mode | Behaviour |
|---|---|
off | Default and correct for a normal L3-routed prefix. |
auto | Attempts to open the responder; warns and continues without it when the interface, Ethernet link or CAP_NET_RAW is unavailable. |
required | Refuses profile startup until the responder attaches to the selected interface. |
The responder advertises the server MAC only for the exact IPv6 lease of a live session and addresses inside an active non-default client_subnet. Disconnect, revoke or session replacement removes ownership immediately; /0, link-local, multicast, invalid NS packets and unrelated targets are ignored.
route, never with nat66 or off.ipv6 = auto | required | off
| Policy | Result |
|---|---|
auto | Accepts IPv4, dual or IPv6. Dual may safely downgrade to IPv4 when the platform lacks the complete IPv6 contract. |
required | Requires inner IPv6 and fails closed with an old server, IPv4-only profile, MTU below 1280 or incomplete platform support. |
off | Requests IPv4 from a dual profile and rejects IPv6-only plans. |
Full tunnel blocks a missing family by default. allow_ipv6_leak=true or allow_ipv4_leak=true is an explicit escape hatch allowing that family over the physical path. Both default to false.
Configure both families as one plan
dns.listenanddns.listen_ipv6must match active gateway addresses; do not push an unreachable listener.- IPv6 requires MTU of at least 1280.
requiredrejects a smaller plan, while PMTU probing determines a safe carrier budget. route,route_file, server push and ACL accept IPv4/IPv6 CIDRs but apply only to negotiated families.- Outer DNS may return A and AAAA; roaming can move a session across families after authenticated path validation.
An existing config does not become dual-stack automatically
- Back up and upgrade the server to 0.8.1.
- Select a unique ULA /64 or obtain a provider-routed GUA prefix.
- Add
tun.ip_mode,tun.ipv6_address,pool.ipv6.cidr, IPv6 DNS androuting.ipv6.mode. - Run
qeli check-config; for NAT66 also verify default route, public source GUA and ip6tables NAT. - Keep clients on
ipv6=auto, test IPv4 and IPv6 independently, then tighten torequiredif needed.
What Qeli does not provide
- No NAT64/DNS64: an IPv6-only client cannot reach an IPv4-only service without separate NAT64 infrastructure.
- A random WAN /64 does not become a client pool: use a separate routed/on-link prefix and a correct reverse path.
- Inner IPv6 does not prove the server uplink is ready; verify address, default route, firewall and reverse route.