Qeli 0.8.1 · dual stack and routed IPv6

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.

Two layers

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.

An IPv4 carrier can carry inner IPv6, and an IPv6 carrier can carry inner IPv4. Write an IPv6 literal with brackets: server = [2001:db8::10]:443.
Profile mode

tun.ip_mode = ipv4 | dual | ipv6

ModeClient planUse
ipv4IPv4 lease, routes and DNS.Compatibility or no ready IPv6 uplink.
dualIndependent IPv4 /32 and IPv6 /128 on L3 TUN, both route and DNS sets.Recommended normal mode.
ipv6IPv6 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.

Base profile

Minimal dual-stack example

server.conf · [profile:main]
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.

Egress and routes

routing.ipv6.mode

ValueBehaviorRequirements
nat66Translates the ULA source to the server's WAN IPv6.IPv6 default route, public GUA in 2000::/3 and working ip6tables NAT.
routePreserves 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.
offNo IPv6 egress and fail-closed isolation.Working ip6tables to guarantee DROP.
Do not use a WAN /64 already assigned to the uplink as the client pool. Use a separate routed or separate on-link prefix; 0.8.1 provides a session-aware NDP proxy for the latter.

Use reservations or static_ipv6 for routed GUA; CLI supports qeli add-client --static-ipv6 ….

Routed IPv6 · 0.8.1

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.

server.conf · [profile:public-v6]
routing.ipv6.mode = route
routing.ipv6.interface = ens3
routing.ipv6.ndp_proxy = required
routing.ipv6.ndp_proxy_interface = ens3
ModeBehaviour
offDefault and correct for a normal L3-routed prefix.
autoAttempts to open the responder; warns and continues without it when the interface, Ethernet link or CAP_NET_RAW is unavailable.
requiredRefuses 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.

Do not assign the client prefix to the WAN interface or add a competing connected route through WAN: the profile TUN must remain the only route owner. NDP proxy works only with source-preserving route, never with nat66 or off.
Client policy

ipv6 = auto | required | off

PolicyResult
autoAccepts IPv4, dual or IPv6. Dual may safely downgrade to IPv4 when the platform lacks the complete IPv6 contract.
requiredRequires inner IPv6 and fails closed with an old server, IPv4-only profile, MTU below 1280 or incomplete platform support.
offRequests 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.

DNS and MTU

Configure both families as one plan

  • dns.listen and dns.listen_ipv6 must match active gateway addresses; do not push an unreachable listener.
  • IPv6 requires MTU of at least 1280. required rejects 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.
Migration

An existing config does not become dual-stack automatically

  1. Back up and upgrade the server to 0.8.1.
  2. Select a unique ULA /64 or obtain a provider-routed GUA prefix.
  3. Add tun.ip_mode, tun.ipv6_address, pool.ipv6.cidr, IPv6 DNS and routing.ipv6.mode.
  4. Run qeli check-config; for NAT66 also verify default route, public source GUA and ip6tables NAT.
  5. Keep clients on ipv6=auto, test IPv4 and IPv6 independently, then tighten to required if needed.
The installer keeps active dual stack only with a verified IPv6 uplink and NAT; otherwise it creates a safe active IPv4-only profile and leaves IPv6 values ready for manual enablement.
Boundaries

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.
Primary sources

Complete 0.8.1 IPv6 documentation