OpenWRT Firewall Traffic Rules with Dynamic IPv6 Addressing

Hey, I have created this guide as it’s an issue I have ran into myself and have seen a few posts about it over on the OpenWRT forums.

You have a server of some sort on your network that you want to be publicly accessible via IPv6, but your ISP provides only dynamic IPv6 addresses. Here in the UK, BSkyB do just that and in some cases, the IPv6 addresses of devices can change very often!

OpenWRT’s firewall doesn’t currently support dynamic IPv6 very well, to forward traffic to a device you have to create a firewall rule and it’s destination IP address must be set to the public address of your server. When the IP changes, the firewall rule needs to be updated manually for IPv6, whereas IPv4 allows you to set the MAC address in firewall rules and the IP can be updated dynamically. Also IPv4 destination addresses would be local addresses anyway which can be set as static.

Step 1 – Create a firewall traffic rule

So you should firstly create a traffic rule for your server, you can set the options as you wish. I have mine set to allow all traffic to the server allowing me to make use of the firewall on the machine without touching the router again. Here are the settings I use:

Name: Web-ServerIPv6
Restrict to address family: IPv6 Only
Prototol: TCP + UDP
Source Zone: WAN
Destination Zone: LAN
Destination Address: [Your Server’s Public IPv6 Address]

With those settings, you should be able to access your server via IPv6 externally!

Step 2 – Updating the Destination Address

Now you can access your server externally through IPv6, you need to keep that traffic rule up to date. As in the introduction, when your IPv6 address changes, the address in this firewall rule will also need to change and OpenWRT doesn’t provide that function yet.

Using my script below, you can dynamically update your firewall rule. (GitHub)

#!/bin/sh

# CONFIGURABLE PARAMETER: PREFIX
# Set the prefix to the name of the rules that need to be updated. (Can update multiple rules with same name)
PREFIX=Web-ServerIPv6
PREFIX_LEN=${#PREFIX}

# CONFIGURABLE PARAMETER: getIP
# Set your method of getting IPv6 address in here
# Current method is through ip neighbor with MAC address (Lowercase, :)(getIP=$(ip neighbor | grep "Your MAC Here" | grep -v "STALE" | cut -d" " -f1))
# One example is wget which accesses a page on the web-server showing current IP address (getIP=$(wget --read-timeout=10 http://checkipv6.dyndns.com -q -O -))
# Another option could be nslookup your domain to get the IPv6 address. getIP=$(nslookup -query=AAAA $hostname)
printf "Getting your IPv6 address... \n"
getIP=$(ip -6 neigh | grep "YOUR MAC ADDRESS" | grep -v "STALE" | grep -v "fe80" | cut -d" " -f1)

if [ "$getIP" = "" ]
then
    printf "Failed to get IP."
    exit 0
fi

# Set m flag accordingly, only first match is accepted.
prefix6=$(echo "$getIP" | grep -m 1 -E -o "([0-9a-fA-F]{1,4}(:?)){8}")

if [ "$prefix6" = "" ]
then
    printf "Request successful, but no IPv6 detected. \n"
    exit 0
fi

printf "Your current IPv6: {$prefix6}\n\n"

changed=0
index=0
name=$(uci get firewall.@rule[$index].name 2> /dev/null)

while [ "$name" != "" ]
do
    subname=${name:0:$PREFIX_LEN}

    if [ "$subname" == "$PREFIX" ]
    then
        dest_ip=$(uci get firewall.@rule[$index].dest_ip 2> /dev/null)
        printf "Current stored IP address: {$dest_ip} \n"

        if [ "$dest_ip" != "$prefix6" ]
        then
            printf "The IP has changed! \n"
            printf "Updating\n\n"
            changed=1
            uci set firewall.@rule[$index].dest_ip=$prefix6
            uci commit firewall
        else
            printf "IP is the same, no changes made.\n"
        fi

        break 2
    fi

    index=$(expr $index + 1)
    name=$(uci get firewall.@rule[$index].name 2> /dev/null)
done

if [ $changed -eq 1 ] 
then
    printf "Restarting firewall... \n"
    /etc/init.d/firewall reload 2> /dev/null
    printf "All up to date. \n"
fi

exit 0

What does it do?

This script by default uses the ip neighbor command to find the addresses of hosts on your network and filters them by MAC address. It then will scan through firewall rules until it finds the one you specify and checks to see if your current IPv6 address is different to the one in the rule, if they are different it will be updated and the firewall is reloaded.

How to use it?

  1. SSH into your router and create a file, call it whatever you like, mine is called “dynamic_ipv6_update” with ‘chmod +x’ for execution.
  2. Paste the code into the file, editing the PREFIX to whatever you called your Traffic Rule, and set the MAC address to your server’s MAC address (use lowercase and colons).
  3. Test run the script, it will talk you through what is happening.
  4. Create a cronjob to run the file (can be done through Luci in System->Scheduled Tasks). Mine is “*/20 * * * * /usr/bin/dynamic_ipv6_update  > /dev/null 2>&1”

If you copied what I did exactly, you will have the script running every 20 minutes to update your traffic rule.

DDNS Tips

Dynamic DNS can be troublesome with IPv6, as again, OpenWRT’s DDNS application doesn’t allow you to specify a device for it’s public IP address.

Personally, I use FreeDNS from Afraid.org and they have some nice update URLs available in their updated interface, you visit the URLs from the machine you want to be public and it sets the IP address automatically.

Set cronjobs on your public machine with these URLs and you are away, this is by far the simplest solution.


Hopefully this post has been of some use for you, the fact you are reading this far down is promising! Feel free to leave me a comment if you appreciate my work :).