Skip to content

Instantly share code, notes, and snippets.

@ClosedPort22
Created August 23, 2025 04:14
Show Gist options
  • Select an option

  • Save ClosedPort22/18d7a57541b3ff0614fc0385b7d07f3b to your computer and use it in GitHub Desktop.

Select an option

Save ClosedPort22/18d7a57541b3ff0614fc0385b7d07f3b to your computer and use it in GitHub Desktop.
Give IPv6 access to WireGuard peers in OpenWrt using NPTv6
#!/bin/sh
# Give IPv6 access to WireGuard peers in OpenWrt using NPTv6
# This is necessary because many ISPs only assign dynamic IPv6 prefixes, which makes it impractical to configure static, globally routable addresses for peers.
# Tested on OpenWrt 24.10.2 (fw4 compatible)
#
# Adapted from https://gist.github.com/MisakaMikoto-35c5/93d7a32860bbb31b47a05a8c8a0152b8
#
# License: 0BSD
#
# Usage:
# 1. Edit the constants below according to your needs
# 2. Place nptv6.sh in your directory of choice
# 3. Add a static route in LuCI > Network > Routing > Static IPv6 Routes with ::/0 as target and your PRIVATE_IP as source. Adjust the metric if needed
# 4. Include the script in /etc/config/firewall (see https://openwrt.org/docs/guide-user/firewall/firewall_configuration#config_include_section_with_shell_script)
# 5. Run '/etc/init.d/firewall reload'
# 6. In LuCI, go to Interfaces > <wg_interface> > Peers > Edit > Allowed IPs, add a static IPv6 address in the format of <private_prefix>:<suffix>/128, e.g. fd01::beef/128
# 7. Save settings and restart the WG interface
# 8. Add the address from step 6 to peer config
# 9. Your WG peer should now have IPv6 connectivity. If not, check if IPv6 traffic is routed through the tunnel
TRACK_INTERFACE=wg0
WAN_INTERFACE=pppoe-wan
PRIVATE_IP=fd01::/64
remove_old_rules() {
nft -a list table inet fw4 | grep DO_NOT_EDIT_Managed_by_nptv6.sh | grep "daddr" | sed -e 's/.* \# handle //' | while read handle; do
# echo "Deleting $handle"
[ $handle ] && nft delete rule inet fw4 dstnat handle $handle
done
nft -a list table inet fw4 | grep DO_NOT_EDIT_Managed_by_nptv6.sh | grep "saddr" | sed -e 's/.* \# handle //' | while read handle; do
# echo "Deleting $handle"
[ $handle ] && nft delete rule inet fw4 srcnat handle $handle
done
}
add_new_rules() {
local wan="$1"
local public_ip="$2"
local private_ip="$3"
nft insert rule inet fw4 dstnat ip6 daddr $public_ip iif $wan \
counter meta nftrace set 1 dnat ip6 prefix to ip6 daddr map { $public_ip : $private_ip } comment DO_NOT_EDIT_Managed_by_nptv6.sh
nft insert rule inet fw4 srcnat ip6 saddr $private_ip oif $wan \
counter meta nftrace set 1 snat ip6 prefix to ip6 saddr map { $private_ip : $public_ip } comment DO_NOT_EDIT_Managed_by_nptv6.sh
}
main() {
source /lib/functions/network.sh
network_flush_cache
echo "Removing old NPTv6 rules..."
remove_old_rules
network_get_subnet6 PUBLIC_IP $TRACK_INTERFACE
if [ ! $TRACK_INTERFACE ]
then
echo "Unable to get public IPv6 address from $TRACK_INTERFACE"
return
fi
echo "Current IPv6 address for $TRACK_INTERFACE: $PUBLIC_IP"
echo "Adding new NPTv6 rules..."
add_new_rules $WAN_INTERFACE $PUBLIC_IP $PRIVATE_IP
}
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment