IPv6 access

My current ISP, Telia, doesn’t yet fully support IPv6 on their consumer network, but provides a 6rd tunnel for IPv6 networking. However, since I use a Linux box as my main router, I needed to set up the tunnel manually. After searching through forums, I’ve found some examples of that being done, but no direct copy & paste was possible. Most examples were on older distros, some were in languages I couldn’t run and had no knowledge how to edit to do required stuff, and settings were different than what I’ve seen appear on dhcp client log.

The script at the end of page, is what I ended up putting in /etc/dhcp/dhclient-exit-hooks.d/ folder, for DHCP Client to run after ip4 address change. Script is based on multiple scripts from the past, found on various forums and sites, but none worked for me on Ubuntu 18.

DHCP Data

Depending on distro and ISP, you might need to edit dhclient.conf and explicitly request 6rd-option and provide formatting for it: option option-6rd code 212 = { integer 8, integer 8, array of ip-address }; where first integer is IPv4 mask length for ISP’s block, second integer is mask length for IPv6 6rd addresses, and then IPv6 network address and IPv4 6rd gateway. I did not need to do this, as my ISP sent this option regardless. For IPv6 forwarding, set net.ipv6.conf.default.forwarding=1 and net.ipv6.conf.all.forwarding=1 or choose per-interface

My ISP actually provides larger prefix than /64 for IPv6. This is an example of content of my /var/lib/dhcp/dhclient.leases I replaced ip addresses with ###.###.###.###.

lease {
  interface "enp9s0f1";
  fixed-address ###.###.###.###;
  option subnet-mask 255.255.240.0;
  option routers ###.###.###.###;
  option dhcp-lease-time 1200;
  option dhcp-message-type 5;
  option domain-name-servers ###.###.###.###,###.###.###.###;
  option dhcp-server-identifier ###.###.###.###;
  option option-6rd 14 38 ####:####:####:: ###.###.255.254;
  option ntp-servers ###.###.###.###;
  renew 1 2020/11/16 16:54:31;
  rebind 1 2020/11/16 17:03:21;
  expire 1 2020/11/16 17:05:51;
}

Here option-6rd line syntax is: IPv4 mask bits, IPv6 mask bits, IPv6 prefix, 6rd border router. At the time of scripting I didn’t find docs that would specify this, but luckily in my case I didn’t have to guess much, as there is no way 38 bits could be mask for IPv4 :)

RFC 5969 Does have a full specification if you are interested.

The Script

I utilized ipcalc and ipv6calc to automate calculations on ip subnets. Ubuntu 18 doesn’t allow bash to run this script due to security concerns, so default scripting shell is dash. Which is posix shell, but complicates some stuff, most notably splitting option-6rd to different variables.

Router ADVertisement Daemon is required for network computers to be able to communicate over IPv6, as DHCPv6 does not provide a way to assign the default route.

DHCPv6 is not a requirement, if you do not need to set specific addresses to network devices.

2001:4860:4860::8888 and 2001:4860:4860::8844 are Google’s IPv6 DNS servers. probably should have went with variables…

#!/bin/sh
# NOTE: /bin/sh is dash
# Requirements: ipcalc, ipv6calc
# Options: radvd, dhcpd6 

# Should radvd or dhcpd6 be used
use_radvd="yes"
use_dhcpd6="yes"

#dhcpd configuration file (include in dhcpd6.conf) note, apparmor might block this if not located in /etc/dhcp/
dhcpdcfg="/etc/dhcp/dhcpd6-telia.conf"
#radvd configuration file
radvdcfg="/etc/radvd.conf"

# Interface names
LanInterface="br0"
TunInterface="i6rd"

#change to smaller if ::/64 is not available or want to use more smaller networks
LanIPSixSubnet="64"

sixrd_down() {
	if [ "x$use_radvd" = "xyes" ]; then 
	   service radvd stop
	fi
	if [ "x$use_dhcpd6" = "xyes" ]; then 
	   service isc-dhcpd-server6 stop
	fi
	#destroy tunnel and flush global IPv6 addresses
	ip tunnel del $TunInterface > /dev/null 2>&1 || true
	ip -l 5 -6 addr flush scope global dev $LanInterface 
}



sixrd_up() {

	# bash would be nice, but must make do with dash (sh) 
	IFS=" "
	set -- $new_option_6rd
	sixrd_masklen=$1
	sixrd_prefixlen=$2
	sixrd_prefix=$3
	sixrd_borderrtr=$4
	
	relayprefix=$(ipcalc -bn $new_ip_address/$sixrd_masklen|sed -En 's/^Network:[[:blank:]]+([0-9./]+).*/\1/p')

	# Log params
	logger -t dhcp-option-6rd "6rd parameters: 6rd-prefix ${sixrd_prefix}/${sixrd_prefixlen} 6rd-relay_prefix ${relayprefix} br ${sixrd_borderrtr}"
	
	#calculating IPv6 prefix that we can use
	delegated_prefix=`ipv6calc -q --action 6rd_local_prefix --6rd_prefix ${sixrd_prefix}/${sixrd_prefixlen} --6rd_relay_prefix ${relayprefix} $new_ip_address`
	
	#ipv6 address and mask for tunnel
	ipsix_masklen="$(echo "$delegated_prefix" | awk '{split($0,a,"/"); print a[2]}')"
	ifname_ip6addr="$(echo "$delegated_prefix" | awk '{split($0,a,"/"); print a[1]}')1/${ipsix_masklen}"
	
	#ipv6 network and address lan interface
	lan_ip6addr="$(echo "$delegated_prefix" | awk '{split($0,a,"/"); print a[1]}')2"
	lan_ip6net="$(echo "$delegated_prefix" | awk '{split($0,a,"/"); print a[1]}')"

	#Build tunnel and set addresses
	ip tunnel add $TunInterface mode sit local $new_ip_address
	ip tunnel 6rd dev $TunInterface 6rd-prefix ${sixrd_prefix}/${sixrd_prefixlen} 6rd-relay_prefix ${relayprefix}
	ip link set dev $TunInterface up

	ip -6 addr add "$ifname_ip6addr" dev $TunInterface
	ip -6 route replace default via ::${sixrd_borderrtr} dev $TunInterface metric 1

	#Add IPv6 address to lan interface
	ip -6 addr add $lan_ip6addr/${LanIPSixSubnet} dev $LanInterface

	# Enable routing back to LAN interface
	ip -6 route replace "$delegated_prefix" dev $LanInterface metric 1
	
	#Build radvd config file
	if [ "x$use_radvd" = "xyes" ]; then 
	echo "
interface $LanInterface {
   AdvSendAdvert on;
   MinRtrAdvInterval 3;
   MaxRtrAdvInterval 10;
   AdvLinkMTU 1280;
 prefix ${lan_ip6net}/${LanIPSixSubnet} {
   AdvOnLink on;
" > $radvdcfg
	fi
	#autonomous address allocation on if only radvd is used
	if [ "x$use_dhcpd6" != "xyes" -a "x$use_radvd" = "xyes" ]; then 
	echo "
   AdvAutonomous on;
" >> $radvdcfg
   else
   echo "
   AdvAutonomous off;
" >> $radvdcfg
	fi
	if [ "x$use_radvd" = "xyes" ]; then 
	echo "
   AdvRouterAddr on;
   AdvValidLifetime 86400;
   AdvPreferredLifetime 86400;
   };
 RDNSS 2001:4860:4860::8888 2001:4860:4860::8844 {};
};
" >> $radvdcfg
	service radvd start
	fi
	
	#Build dhcpd6 config file 
	if [ "x$use_dhcpd6" = "xyes" ]; then 
	echo "
subnet6 ${lan_ip6net}/${LanIPSixSubnet} {
	range6 ${lan_ip6net}1000 ${lan_ip6net}1fff;
	option dhcp6.name-servers $lan_ip6addr;
	option dhcp6.domain-search \"example.org\";
}
" > $dhcpdcfg
	service isc-dhcpd-server6 start
	fi
}

case $reason in
	BOUND | REBOOT)
		if [ -z "$new_option_6rd" ]; then
			logger -t dhcp-option-6rd "No 6rd option in response"
			sixrd_down
			return
		else
			sixrd_down
			sixrd_up
		fi
		;;
	RENEW | REBIND)
		if [ "$new_ip_address" != "$old_ip_address" ]; then
			if [ -z "$new_option_6rd" ]; then
				logger -t dhcp-option-6rd "No 6rd option in response"
				sixrd_down
				return
			else
				sixrd_down
				sixrd_up
			fi
		fi    
		;;
	STOP | EXPIRE | FAIL | RELEASE)
		sixrd_down
		;;
esac