There is virtually no reason for a home user to use IPv6 NAT of any sort.
If you‘re here to “fix” an IPv6 puzzle, this likely isn‘t what you need.
This page describes how to set up NAT6 masquerading on your OpenWrt router. Most users won‘t need or want this, but there are use cases for NAT even on IPv6 networks - e.g. if you want to create a subnet, but the network doesn‘t support subnetting or prefix delegation. Since NAT6 is available in the netfilter framework of the Linux kernel, it‘s fairly easy to set up on OpenWrt.
This guide assumes you already have a working IPv6 connection on your OpenWrt router. It‘s further assumed that you are (mostly) using the OpenWrt default settings, especially those related to IPv6 and DHCPv6 on your LAN interface. See the Extras: DHCPv6 section below.
NAT6 requires the package on OpenWrt which is available in the official repositories. The NAT6 setup outlined in this HOWTO has been reported working on Barrier Breaker and Chaos Calmer, but it should work just fine on Trunk (Designated Driver) as well.
Install the necessary packages.
opkg update
opkg install kmod-ipt-nat6
If you are handing out only local addresses (i.e. not part of delegated prefix from your upstream), change the first letter of the IPv6 ULA prefix. See the Extras: ULA prefix section below.
uci set network.globals.ula_prefix="$(uci get network.globals.ula_prefix | sed -e "s/^./d/")"
uci commit network
/etc/init.d/network restart
Set the DHCPv6 server to always announce default router.
uci set dhcp.lan.ra_default="1"
uci commit dhcp
/etc/init.d/odhcpd restart
Enable the new masq6
option in your firewall on your upstream zone.
uci set $(uci show firewall | sed -n -e "/\.name=‘wan‘$/s//.masq6=‘1‘/p" | sed -n -e "1p")
uci commit firewall
Since masquerading is enabled, disable the redundant firewall rule “Allow-ICMPv6-Forward”.
uci set $(uci show firewall | sed -n -e "/\.name=‘Allow-ICMPv6-Forward‘$/s//.enabled=‘0‘/p" | sed -n -e "1p")
uci commit firewall
Save the NAT6 firewall script.
cat << "EOF" > /etc/firewall.nat6
# Masquerading nat6 firewall script
#
# Then you can configure in /etc/config/firewall per zone, ala where you have:
# option masq 1
# Just drop this in beneath it:
# option masq6 1
# For IPv6 privacy (temporary addresses used for outgoing), also add:
# option masq6_privacy 1
#
# Hope it‘s useful!
#
# https://github.com/akatrevorjay/openwrt-masq6
# ~ trevorj <github@trevor.joynson.io>
set -eo pipefail
. /lib/functions.sh
. /lib/functions/network.sh
. /usr/share/libubox/jshn.sh
log() {
logger -t nat6 -s "$@"
}
get_ula_prefix() {
uci get network.globals.ula_prefix
}
validate_ula_prefix() {
local ula_prefix="$1"
if [ $(echo "$ula_prefix" | grep -c -E "^([0-9a-fA-F]{4}):([0-9a-fA-F]{0,4}):") -ne 1 ] ; then
log "Fatal error: IPv6 ULA ula_prefix=\"$ula_prefix\" seems invalid. Please verify that a ula_prefix is set and valid."
return 1
fi
}
ip6t() {
ip6tables "$@"
}
ip6t_add() {
if ! ip6t -C "$@" &>/dev/null; then
ip6t -I "$@"
fi
}
nat6_init() {
iptables-save -t nat | sed -e "/\s[DS]NAT\s/d;/\sMASQUERADE$/d" | ip6tables-restore -T nat
}
masq6_network() {
# $config contains the ID of the current section
local network_name="$1"
local device
network_get_device device "$network_name" || return 0
local done_net_dev
for done_net_dev in $DONE_NETWORK_DEVICES; do
if [ "$done_net_dev" = "$device" ]; then
log "Already configured device=\"$device\", so leaving as is."
return 0
fi
done
log "Found device=\"$device\" for network_name=\"$network_name\"."
if [ $zone_masq6_privacy -eq 1 ]; then
log "Enabling IPv6 temporary addresses for device=\"$device\"."
log "Accepting router advertisements on $device even if forwarding is enabled (required for temporary addresses)"
echo 2 > "/proc/sys/net/ipv6/conf/$device/accept_ra" || log "Error: Failed to change router advertisements accept policy on $device (required for temporary addresses)"
log "Using temporary addresses for outgoing connections on interface $device"
echo 2 > "/proc/sys/net/ipv6/conf/$device/use_tempaddr" || log "Error: Failed to enable temporary addresses for outgoing connections on interface $device"
fi
append DONE_NETWORK_DEVICES "$device"
}
handle_zone() {
# $config contains the ID of the current section
local config="$1"
local zone_name
config_get zone_name "$config" name
# Enable masquerading via NAT6
local zone_masq6
config_get_bool zone_masq6 "$config" masq6 0
log "Firewall config=\"$config\" zone=\"$zone_name\" zone_masq6=\"$zone_masq6\"."
if [ $zone_masq6 -eq 0 ]; then
return 0
fi
# IPv6 privacy extensions: Use temporary addrs for outgoing connections?
local zone_masq6_privacy
config_get_bool zone_masq6_privacy "$config" masq6_privacy 1
log "Found firewall zone_name=\"$zone_name\" with zone_masq6=\"$zone_masq6\" zone_masq6_privacy=\"$zone_masq6_privacy\"."
log "Setting up masquerading nat6 for zone_name=\"$zone_name\" with zone_masq6_privacy=\"$zone_masq6_privacy\""
local ula_prefix=$(get_ula_prefix)
validate_ula_prefix "$ula_prefix" || return 1
local postrouting_chain="zone_${zone_name}_postrouting"
log "Ensuring ip6tables chain=