Configure IPv6 on OpenWrt Using NAT66 + ULA

Configure IPv6 on OpenWrt Using NAT66 + ULA

@Ariful
@Ariful Mar 08, 2026 • 6 min read

The Problem

My Google TV were experiencing random IPv6 disconnections but normal when not using IPv6.

My initial setup used NDP Proxy to share addresses from the WAN /64 prefix with LAN devices. It worked, but was inherently unstable because devices like Google TV aggressively send Router Solicitations, making them more susceptible to connectivity drops during prefix changes

The more reliable solution is NAT66 + ULA — LAN devices use a stable ULA (Unique Local Address) prefix that never changes, while the router masquerades traffic to the dynamic WAN IPv6 address.


The Solution: NAT66 + ULA

How It Works

LAN devices  →  fdda:8411:a77f::xxxx  (ULA, stable & permanent)
                          ↓
              OpenWrt NAT66 Masquerade
                          ↓
              WAN IPv6 from ISP (dynamic, changes frequently)
                          ↓
                       Internet

Advantages:

  • LAN device IPv6 addresses never change regardless of ISP prefix rotation
  • No DHCPv6-PD support required from the ISP
  • IPv6 connectivity remains stable as long as WAN IPv6 is up

Trade-offs:

  • LAN devices do not get a native public IPv6 address — similar to how NAT44 works for IPv4
  • Some services requiring end-to-end native IPv6 may not work optimally

Configuration Guide

Prerequisites

  • OpenWrt or ImmortalWrt 23.05+
  • Firewall backend using nftables (fw4) — default since version 23.05
  • ISP is already providing IPv6 on the WAN interface (verify with ip -6 addr show dev wan)

Step 1: Configure the Network

Assign the ULA prefix to the LAN interface and configure wan6 to request an address only, not a prefix delegation:

uci set network.lan.ip6assign='48'
uci set network.globals.ula_prefix='fdda:8411:a77f::/48'
uci set network.wan6.reqprefix='no'
uci set network.wan6.reqaddress='force'
uci commit network

Note: The ULA prefix fdda:8411:a77f::/48 is taken from this router’s existing config. Check yours with uci get network.globals.ula_prefix and use that value instead.

Step 2: Enable DHCPv6 and Router Advertisement on LAN

This allows LAN devices to automatically receive the ULA prefix via SLAAC:

uci set dhcp.lan.dhcpv6='server'
uci set dhcp.lan.ra='server'
uci set dhcp.lan.ra_management='1'
uci set dhcp.lan.ra_default='1'
uci commit dhcp

Step 3: Create the NAT66 Rule with nftables

OpenWrt 23.05+ uses fw4 + nftables instead of ip6tables. Add the NAT66 masquerade rule manually:

mkdir -p /etc/nftables.d
cat > /etc/nftables.d/99-nat66.nft << 'EOF'
table ip6 nat66 {
    chain postrouting {
        type nat hook postrouting priority srcnat;
        oifname "wan" ip6 saddr fdda:8411:a77f::/48 masquerade;
    }
}
EOF

# Load the rule immediately without restarting
nft -f /etc/nftables.d/99-nat66.nft

Verify the rule is active:

nft list table ip6 nat66

Expected output:

table ip6 nat66 {
    chain postrouting {
        type nat hook postrouting priority srcnat; policy accept;
        oifname "wan" ip6 saddr fdda:8411:a77f::/48 masquerade
    }
}

Step 4: Add a Default Route for ULA Traffic

This is the most commonly missed step. The ISP’s default IPv6 route is typically source-based (from 2402:xxxx::/64), meaning packets sourced from the ULA range have no outbound route. Fix this by adding a non-source-restricted default route:

ip -6 route add default via fe80::1 dev wan metric 1024

Note: Replace fe80::1 with the link-local gateway of your ISP’s router. Check it with ip -6 route show | grep default.

Test from the router:

ping6 -c3 -I fdda:8411:a77f::1 2606:4700:4700::1111

If ping succeeds, proceed to make everything persistent.

Step 5: Make the Configuration Persistent

Add the default route to the network config so it survives reboots:

uci add network route6
uci set network.@route6[-1].interface='wan'
uci set network.@route6[-1].target='::/0'
uci set network.@route6[-1].gateway='fe80::1'
uci commit network

Create a hotplug script to automatically reload NAT66 whenever the WAN interface comes back up:

cat > /etc/hotplug.d/iface/99-nat66 << 'EOF'
#!/bin/sh
[ "$ACTION" = "ifup" ] && [ "$INTERFACE" = "wan" ] && {
    nft delete table ip6 nat66 2>/dev/null
    nft -f /etc/nftables.d/99-nat66.nft
    ip -6 route add default via fe80::1 dev wan metric 1024 2>/dev/null
}
EOF

chmod +x /etc/hotplug.d/iface/99-nat66

Step 6: Restart Services

/etc/init.d/network restart
sleep 5
/etc/init.d/odhcpd restart
/etc/init.d/firewall restart

Verification

On the Router

# Router should have ULA assigned to br-lan
ip -6 addr show dev br-lan

# NAT66 rule should be present
nft list table ip6 nat66

# Test IPv6 ping from ULA source
ping6 -c3 -I fdda:8411:a77f::1 2606:4700:4700::1111

On a Client Device (Windows)

# Check if client received a ULA address
ipconfig

# Test IPv6 internet connectivity
ping google.com -6

The client should have an address in the fdda:8411:a77f::/48 range and be able to reach the internet over IPv6.


Troubleshooting

Client gets a ULA address but cannot ping IPv6 internet → The default route for ULA is likely missing. Re-run Step 4 and verify with ip -6 route show default.

NAT66 rule disappears after reboot → Make sure the hotplug script exists and is executable. Check with ls -la /etc/hotplug.d/iface/99-nat66.

Duplicate NAT66 rules after reboot → The hotplug script handles this by running nft delete table ip6 nat66 before reloading. If running manually, delete the table first before loading the file.

ip6tables: not found error → OpenWrt 23.05+ uses nftables. The ip6tables binary no longer exists — use nft commands as shown in this guide.

Kernel module dependency errors during opkg install → Your firmware may be a custom build (e.g., ImmortalWrt) with a different kernel version than the default OpenWrt repo. Update /etc/opkg/distfeeds.conf to point to the correct ImmortalWrt repository URL matching your release.


Disabling NAT66

If you want to roll back or disable NAT66, there are two approaches depending on your intent.

Option A: Temporary — Pause Without Removing Files

This removes the active rule from memory but leaves all config files intact so you can re-enable it anytime:

# Remove rule from memory
nft delete table ip6 nat66

# Re-enable anytime by loading the file again
nft -f /etc/nftables.d/99-nat66.nft

Option B: Permanent — Full Removal

Remove all NAT66 components completely:

# 1. Remove active rule from memory
nft delete table ip6 nat66

# 2. Delete the nft rule file
rm /etc/nftables.d/99-nat66.nft

# 3. Remove the hotplug script
rm /etc/hotplug.d/iface/99-nat66

# 4. Remove the ULA default route from memory
ip -6 route del default via fe80::1 dev wan metric 1024

# 5. Remove the persistent route from UCI
# Check the index first
uci show network | grep -A4 route6

# Then delete the matching entry (adjust index if needed)
uci delete network.@route6[0]
uci commit network

If you also want to stop LAN devices from receiving ULA addresses entirely:

uci delete network.lan.ip6assign
uci set network.wan6.reqprefix='auto'
uci commit network

uci set dhcp.lan.dhcpv6='disabled'
uci set dhcp.lan.ra='disabled'
uci commit dhcp

/etc/init.d/network restart
/etc/init.d/odhcpd restart

NAT66 + ULA is a pragmatic solution for stable IPv6 connectivity when your ISP does not support DHCPv6 Prefix Delegation or rotates the WAN prefix dynamically. While it lacks the end-to-end transparency of native IPv6, it is significantly more reliable than NDP Proxy for everyday use cases including media streaming on devices like Google TV.

If your ISP ever starts offering DHCPv6-PD, migrating to a proper prefix delegation setup would be the architecturally correct next step — but until then, this setup gets the job done reliably.

© 2026 Nixpoin.com. Built with performance in mind.