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, the built-in NDP proxy for on-link prefixes, DNS, MTU and safe migration.

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.
Did the provider issue a separate public on-link prefix? Jump to “NDP proxy: setup and checks” for the packet flow, complete configuration, and tcpdump diagnostics.
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 ….

Public IPv6 without NAT66 · 0.8.1

NDP proxy: when it is needed and how to configure it

NDP (Neighbor Discovery Protocol) performs, among other jobs, the IPv6 equivalent of IPv4 ARP: a neighbouring router sends a Neighbor Solicitation (NS) to discover which MAC address serves a particular IPv6 address. A VPN client address lives on the TUN rather than the server WAN. For an on-link prefix, Qeli therefore sends a Neighbor Advertisement (NA) containing the server MAC and then routes the packet into the active VPN session.

This is not NAT66. The client's public source IPv6 is preserved. NDP proxy is needed only when the provider treats a separate client prefix as directly connected to its L2 segment. If the provider installs a normal L3 route through the server WAN address, keep routing.ipv6.ndp_proxy = off.

Select the mode from the provider topology

Provider behaviourSignalQeli setting
Routes a separate prefix through the server WAN addressNo NS for client addresses arrives on WAN; the provider panel shows a route/next hop.routing.ipv6.mode = route
routing.ipv6.ndp_proxy = off
Treats the separate prefix as on-linkNS packets for individual public client addresses arrive on WAN.routing.ipv6.mode = route
routing.ipv6.ndp_proxy = required
Topology is not documentedThe provider gives no precise routing description.Inspect traffic with tcpdump. auto is useful for diagnostics, but use required for a production on-link deployment.

Determine whether the proxy is required

Do not assign the client prefix to the WAN interface. Stop the profile or server, capture on the actual IPv6 uplink, and probe a future client address from an independent external IPv6 host:

Qeli server and external IPv6 host
# On the Qeli server
$ 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'

# On an external IPv6 host
$ ping -6 2001:db8:1200:10::100

Neighbor Solicitation, who has 2001:db8:1200:10::100 means that the upstream resolves the client address through NDP and this topology needs a proxy. If no NS appears, first verify the address, WAN interface, firewall/security group, and provider conditions; absence of one packet does not by itself prove an L3-routed topology.

Complete profile fragment

The 2001:db8::/32 addresses below are documentation-only. Replace them with a separate GUA prefix issued by your provider for VPN clients.

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

# Preserve the public client source address; no MASQUERADE is created.
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::100
Do not assign pool.ipv6.cidr to ens3 and do not add a connected route for it through WAN. The client-prefix route must point only to the profile TUN; the upstream performs NDP on its side. For dual stack, change tun.ip_mode = ipv6 to dual and retain the existing IPv4 settings.

Modes and requirements

ModeBehaviour
offResponder disabled. This is the default and correct choice for a normal L3-routed prefix.
autoAttempts to open the responder. If the interface, Ethernet link, or CAP_NET_RAW is unavailable, it logs a warning and continues without the responder.
requiredRefuses profile startup until the responder attaches to the selected interface. Recommended for a production on-link deployment.

An empty routing.ipv6.ndp_proxy_interface reuses the effective uplink from routing.ipv6.interface or the IPv6 default route. It requires an Ethernet-compatible Linux interface and CAP_NET_RAW; the normal Qeli system service already runs with the needed privileges. NDP proxy is valid only with source-preserving route, never with nat66 or off.

Which addresses Qeli advertises

  • The exact IPv6 lease of a live session from pool.ipv6.cidr, including static_ipv6.
  • Addresses inside a non-default IPv6 client_subnet registered by a connected router or site-to-site client.
  • Only live-session addresses: disconnect, revoke, or session replacement immediately removes ownership.
  • /0, link-local, multicast, invalid NS, and unrelated targets are ignored. Qeli does not turn the responder into a general multicast relay.

Verify after startup

configuration, journal, route and NDP
$ 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)'

With required, the journal must report session-aware IPv6 NDP proxy active on 'ens3', and the client /64 must route through vpn-public. While Alice is offline, a new NS for her address receives no NA. Once connected, Qeli advertises its own MAC and routes external traffic into her TUN session. New responses stop after disconnect; an old entry may remain in the upstream neighbour cache until its timer expires.

SymptomCheck
required does not start the profileUplink name, Ethernet link type, root/CAP_NET_RAW, and the journal message.
tcpdump shows no NSAddress and WAN interface, provider route/security group; the prefix may already be routed and require no proxy.
NS arrives but no NA while the client is onlineThe actual static_ipv6, session state, and whether the target belongs to the pool or an active client_subnet.
NA appears but traffic does not arriveThe /64 route to TUN, IPv6 forwarding, ip6tables/cloud firewall, and client firewall.
auto starts without NDPFind NDP proxy auto mode is unavailable, correct the cause, and switch to required.
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