IPv6
Taranac answers IPv4 and IPv6 clients on the same ports, and every service a device or a browser dials sees an IPv6 client by its own address: TACACS+, RADIUS, NAC, the EST and SCEP gateway, the MFA service, the web interface and the captive portal record it, match it against the inventory and count it in their limits by that address. Nothing has to be switched on for this. It needs a host that can carry IPv6 inside Docker, and it changes three things an operator looks after: device inventory entries, the host firewall, and how per-address limits count a client.
The host itself needs no IPv6 address for the stack to run. It needs one, with a route, only to be reached by IPv6 clients.
Key concepts
Section titled “Key concepts”| Term | Meaning |
|---|---|
| Stack network | The Docker network every container of the node sits on, taranac_default. It carries IPv4 10.255.255.224/27 and the IPv6 ULA fd64:1fbc:c16e::/64. |
| Client-facing services | The containers clients dial from outside: edge, taranac-mfa, tacacs, radius, nac, est-gateway and captive-portal. Only these get an IPv6 address on the stack network. |
| IPv4-only containers | frontend, api, postgres, taranac-renderer and winbind are kept off IPv6: no IPv6 address and no AAAA record, so a name inside the stack always resolves to IPv4. |
| Own address | Docker forwards an IPv6 packet for a published port into the container with its source address unchanged, as it does for IPv4. The service sees the device’s address. |
| Gateway address | When Docker’s ip6tables is off, Docker’s userland proxy accepts the IPv6 connection on the host and opens a new one into the container. The service then sees the stack network’s gateway, 10.255.255.225, instead of the client. |
| /64 | The unit an IPv6 client is counted in by the per-address limits. An IPv6 host owns a whole /64 and changes address inside it on its own. |
What the host needs
Section titled “What the host needs”| Requirement | Why |
|---|---|
| Docker Engine 28.0 or newer | On older Docker a container kept off IPv6 still gets an IPv6 address and an AAAA record, so connections inside the stack try a dead address first and the api and the portal stall. |
| Docker Engine, not Podman | Podman answering the docker command (podman-docker) is refused: the stack’s network and compose files are Docker Engine’s. |
| IPv6 in the kernel | The edge and the AAA daemons listen on IPv6 as well as IPv4 and cannot start without it. A kernel booted with ipv6.disable=1 is refused; IPv6 turned off by sysctl is fine. |
| Docker able to create a dual-stack network | Checked with a throwaway network with IPv6 beside IPv4. When Docker refuses it, its own error is shown. A frequent cause is the ip6_tables kernel module not being loaded (sudo modprobe ip6_tables), or a firewall manager holding ip6tables. |
| Docker’s ip6tables on (the default) | Not a refusal, a warning: with it off, IPv6 clients reach Taranac from the gateway address. |
bootstrap.sh, install.sh and ./taranac update check all of this before they change
anything. A refusal says what it found, prints the command that fixes it on this host (the
Docker update that matches how Docker was installed, the GRUB change for the kernel, the
docker network create test to re-run), and ends with the command to run again. bootstrap.sh
keeps a Docker that is already on the host unless it is older than 28.0 or is Podman.
When the host passes, the installer and the updater say so in one line, which the update also
writes to .taranac-update.log:
Host: Docker 29.1.2 ✓, kernel IPv6 ✓, dual-stack network test ✓, accept_ra: nothing changedSee Installation → Prerequisites.
What each service does with an IPv6 client
Section titled “What each service does with an IPv6 client”| Service | Ports | Over IPv6 |
|---|---|---|
| TACACS+ | 49, 6049 (TLS) | One dual-stack listener per port. A device is matched by its own IPv6 address and needs an inventory entry covering it, with its key; one device record holds one address, so a device that sends over IPv4 and IPv6 needs an entry for each. See IPv6 devices. |
| RADIUS | 1812/1813 UDP | Listens on IPv4 and IPv6. The request belongs to the device whose entry covers its IPv6 source address, which picks the shared secret. See How a request finds its device. |
| NAC (802.1X) | 1814/1815 UDP | Accepts packets over IPv6 from the device’s own address. Not supported: NAS-IPv6-Address, CoA or Disconnect to a switch over IPv6, and finding a captive portal guest’s MAC address by an IPv6 address. Register switches that need CoA by an IPv4 address. |
| EST and SCEP gateway | 8445, 8446 | Listens on IPv6. Networks devices may request certificates from takes IPv6 networks; the enrollment guard counts an IPv6 source by its /64. See EST and SCEP. |
| Web interface and API | 443 / 80 (edge) | The audit log, login lockout and rate limits read the browser’s own address. |
| MFA service | 8443, or 443 with TARANAC_MFA_DOMAIN | Phones register and answer pushes over IPv6; the signature ban counts by /64. |
| Captive portal | 8080 / 8444 | Guests reach it over IPv6; its limits count each guest’s full address. |
A client-facing container can also open connections out over IPv6 when a name it dials has an AAAA record (an LDAP server, a syslog collector, the push service). Docker sends them out with the host’s address. Which path is used is decided by your DNS.
Per-address limits count a /64
Section titled “Per-address limits count a /64”Every limit that counts attempts per client counts an IPv6 client by its /64, because a host walking through the addresses of its own /64 would otherwise never reach a limit:
- the API rate limits (login, token refresh, password reset, MFA challenge and verify, EST enrollment);
- the login CAPTCHA guard;
- the EST and SCEP enrollment guard: a blocked /64 is listed and unblocked as such;
- the MFA service’s signature ban.
An IPv4 client is counted by its address, and an IPv4 client of a dual-stack socket
(::ffff:a.b.c.d) is the IPv4 client. Logs, the audit trail and sessions always keep the full
address.
The captive portal is the exception: its limits count a guest’s full IPv6 address, because on the portal’s LAN one /64 is the whole guest VLAN.
“Login from a new IP” means a new network. The USER_LOGIN_NEW_IP audit entry and the
email to the user (email.login_new_ip_notify_enabled, on by default) are written when a login comes from
another /64 or another IPv4 address than the previous one. An IPv6 host that changes address
inside its /64 is not a new IP.
The host firewall
Section titled “The host firewall”IPv6 to the client-facing ports — 80/443, MFA, TACACS+, RADIUS, NAC, EST/SCEP and the
captive portal — is forwarded by Docker into the container, exactly as IPv4 always is. Rules
in the host’s INPUT chain, and therefore ufw, do not see that traffic. Filter it in Docker’s
DOCKER-USER chain instead (Docker’s guide: Packet filtering and firewalls). For example,
accept RADIUS over IPv6 only from your management prefix:
sudo ip6tables -I DOCKER-USER -p udp -m conntrack --ctorigdstport 1812 ! -s 2001:db8:10::/48 -j DROPThe same rule written with iptables and an IPv4 network covers IPv4. Match the published
port with --ctorigdstport, as above: by the time a packet reaches DOCKER-USER its
destination has already been rewritten to the container. Rules added this way are not kept
across a reboot unless your host saves its ip6tables and iptables rules.
The same applies to port 8446 (plain-HTTP SCEP), which is published on every installation
whether SCEP is on or not — see SCEP → Ports and firewall.
The HA ports 5432, 8008 and 2379 belong to containers kept off IPv6, so IPv6 to them still
passes the host’s INPUT chain and ufw. The HA host-firewall rule stays as it is — see
The node’s own Docker network.
Router advertisements: accept_ra
Section titled “Router advertisements: accept_ra”Docker’s first IPv6 network turns IPv6 forwarding on, and the kernel then ignores router
advertisements on interfaces with accept_ra=1. A host that takes its IPv6 address or its
default route from advertisements would lose that route when the current one expires, hours
later.
install.sh and ./taranac update therefore set accept_ra=2 on exactly the interfaces that
have accept_ra=1 and a dynamic global IPv6 address or a default route learned from an
advertisement, keep it in /etc/sysctl.d/99-taranac-accept-ra.conf, and say so. A host with a
static IPv6 configuration, or with no IPv6 at all, is left as it is.
The change needs root: they run as root, use sudo without a password, or let sudo ask at a
terminal. Without any of these they stop with nothing changed and print the commands, one
echo '<key> = 2' | sudo tee -a /etc/sysctl.d/99-taranac-accept-ra.conf line per interface;
run those once, then the same command again.
Docker’s ip6tables turned off
Section titled “Docker’s ip6tables turned off”With "ip6tables": false in Docker’s daemon configuration (or --ip6tables=false), Docker
cannot forward IPv6 into the containers. Its userland proxy takes the connection on the host
instead, and every IPv6 client arrives from the gateway address 10.255.255.225. Devices are
then matched, logged and blocked by that one address. install.sh and the update warn about
it. To give IPv6 clients their own address, remove the setting, restart Docker
(sudo systemctl restart docker) and run ./taranac up -d.
A TACACS+ connection from the gateway address is logged as The source address was rewritten by Docker and raises no alert. Never add the gateway to the inventory: every client the proxy forwards would arrive from it and be let in with its key. See A rewritten source address.
Inventory entries that covered the gateway
Section titled “Inventory entries that covered the gateway”An inventory entry that covers the Docker gateway — the stack’s subnet, a /8, 0.0.0.0/0 —
matches IPv6 devices only while they arrive from the gateway address. With their own address
they match the IPv6 entries alone and are refused as unknown without one.
./taranac update and install.sh run on an existing installation look for such entries before
they change anything. While such entries exist and the inventory has no IPv6 entry at all
(no IPv6 address, prefix or range with TACACS+, RADIUS or NAC on), they name the entries and
stop: add an IPv6 entry beside each with the same key — the devices’ IPv6 addresses or prefix,
or ::/0 for all of them — and run the command again. With an IPv6 entry in the inventory they
name the entries and go on. To go on regardless, answer CONTINUE at the prompt, or pass
--ipv6-own-address-ok (./taranac update, ./install.sh) or set
TARANAC_IPV6_OWN_ADDRESS_OK=1; IPv6 devices are refused until they have entries.
./taranac diagnose names those entries too: as a finding while IPv6 devices still arrive from
the gateway, otherwise in its ipv6.txt.
Checking a node
Section titled “Checking a node”./taranac diagnose --protocol <tacacs|radius|nac> --device <address> writes ipv6.txt into
its archive: Docker’s version and whether its ip6tables is on, kernel IPv6, IPv6 forwarding,
accept_ra per interface, whether the stack network carries IPv6, and for each published port
of the daemon its IPv6 listener and Docker’s IPv6 DNAT rule (the DNAT part needs root or
sudo). Problems found there appear in the summary marked [IPv6]. See
Diagnostic bundles.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
IPv6 devices are logged or refused with the address 10.255.255.225 | Docker’s ip6tables is off, so the userland proxy forwards IPv6 | Turn ip6tables back on, restart Docker, ./taranac up -d. ipv6.txt shows docker_ip6tables: OFF. |
| An IPv6 device is refused as Not in the inventory | No entry covers its IPv6 address | Add an IPv6 entry with the device’s key. See IPv6 devices. |
| An IPv6 RADIUS device gets no answer | No RADIUS-enabled entry covers the address the packets come from | Register that IPv6 address or prefix. |
| ufw rules do not stop IPv6 to a service port | Published ports are forwarded, not delivered to INPUT | Filter in DOCKER-USER, above. |
| The host loses its IPv6 default route hours after installing | accept_ra=1 on an interface that lives on router advertisements, not covered by the change above | Set accept_ra=2 on it and persist it in /etc/sysctl.d/99-taranac-accept-ra.conf. |
| Install or update refuses: Docker … is too old, The docker command here is Podman, kernel has IPv6 turned off, or Docker refused to create a network with IPv6 beside IPv4 | The host fails a requirement above | Follow the printed fix; nothing was changed. |
| A CoA to a switch registered by its IPv6 address fails | CoA over IPv6 is not supported | Register the switch’s IPv4 management address for CoA. |
Related
Section titled “Related”- Installation — prerequisites and ports
- Network devices → IPv6 devices — inventory entries and keys for IPv6 devices
- Backups & upgrades → Upgrading from an older release — what the first update with IPv6 changes
- High availability — cluster ports and the host firewall
- Diagnostic bundles —
ipv6.txt - Troubleshooting