Created
August 23, 2025 04:14
-
-
Save ClosedPort22/18d7a57541b3ff0614fc0385b7d07f3b to your computer and use it in GitHub Desktop.
Give IPv6 access to WireGuard peers in OpenWrt using NPTv6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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