Enabling IPv6 via PPPoE on a Telecom Italia (TIM) link

This HowTo uses a Raspberry Pi 3 running Raspbian and acting as a router for my Home Network.

IPv6 Home Network

The router provided by Telecom Italia (TIM) is IPv4 only. The Raspberry Pi is the only other host on that subnet. All other hosts (laptop, desktop, tables, …) are behind the Raspberry Pi router, on the network. The Raspberry Pi router NAT all hosts using the eth1’s IP address:

# iptables -t nat -v -n --line-num -L POSTROUTING
Chain POSTROUTING (policy ACCEPT 6810 packets, 433K bytes)
num   pkts bytes target     prot opt in     out     source               destination
1    41921 2777K MASQUERADE  all  --  *      eth0  

The Raspberry Pi router brings up a ppp tunnel, getting:

  • a single IPv6 address for the ppp0 tunnel, acting as a default gateway for IPv6 networks;
  • a single /64 network (yes, only one network for Telecom Italia customers, and yes, it sucks) for internal use. This network is dynamic and it will change every time the ppp tunnel restarts (and yes, it sucks twice).

Preparing the environment

By default every Linux distribution brings a default DHCP client. It should be disabled or removed:

# apt-get purge dhcpcd5

Install required packages:

# apt-get -y install ppp pppoeconf wide-dhcpv6-client

Configuring PPPoE connection

Configure the ppp connection using the following file:

# /etc/ppp/peers/telecom-ipv6
+ipv6 ipv6cp-use-ipaddr
lcp-echo-interval 30
lcp-echo-failure 4
mtu 1492
plugin rp-pppoe.so eth0
user adsl@alice6.it
password IPV6@alice6

By default some settings must be tuned every time the ppp link goes up:

# cat /etc/ppp/ip-up.d/ipv6ppp0

echo 2 > /proc/sys/net/ipv6/conf/ppp0/accept_ra
echo 2 > /proc/sys/net/ipv6/conf/ppp0/forwarding

If the above script is not executed every time, the DHCP daemon won’t receive any prefix, and eth1 interface will miss the IPv6 address.

Be sure also the ppp0 interface is configured to start at boot:

auto ppp0
iface ppp0 inet ppp
	provider telecom-ipv6
	pre-up /bin/ip link set eth0 up

Finally try to bring up the ppp0 tunnel:

# pon telecom-ipv6

And check related logs:

# fgrep pppd /var/log/debug

If everything is fine, the ppp0 now has a global IPv6 address:

# ip -6 addr show dev ppp0
    inet6 2012:3456:7899:bcde::1/64 scope global mngtmpaddr dynamic
       valid_lft 2591984sec preferred_lft 604784sec
    inet6 fe80::1/10 scope link
       valid_lft forever preferred_lft forever

Assign the received IPv6 prefix to the internal network

Configure DHCP to listen to the ppp0 interface and assigned the received network to the eth1 interface:

# /etc/wide-dhcpv6/dhcp6c.conf
profile default {
	request domain-name-servers;
	request domain-name;
	script "/etc/wide-dhcpv6/dhcp6c-script";

interface ppp0 {
	send ia-pd 1;
	send rapid-commit;

id-assoc pd 1 {
	prefix-interface eth1 {
		sla-len 0;
		sla-id 1;
		ifid 1;

Enable it on boot and start it:

# systemctl enable wide-dhcpv6-client
# systemctl start wide-dhcpv6-client
# systemctl enable wide-dhcpv6-client

And check related logs:

# fgrep wide-dhcpv6-client /var/log/daemon.log

If everything works fine, eth1 now have a global IPv6 address too:

# ip -6 addr show dev eth1
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 2012:3456:789a:bcde::1/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::20b:e6ff:fe0a:cdce/64 scope link
       valid_lft forever preferred_lft forever

Announcing the IPv6 prefix on internal network

Once the prefix is configured, it must be announced on the internal network. The radvd daemon could be used, but I preferred DNSMasq. Here the small related configuration:

dhcp-range=2012:3456:789a:bcde::1000, 2012:3456:789a:bcde::1fff, 64, 168h

But because the eth1 IPv6 prefix changes every time the ppp0 interface restart, it cannot be statically configured. A small script can help:


DNSMASQ_IP=$(egrep -e "^dhcp-range=[0-9a-f:]+, [0-9a-f:]+, 64, " /etc/dnsmasq.conf | cut -d'=' -f2 | cut -d ',' -f1)
CURRENT_IP=$(ip -f inet6 addr show dev eth1 | grep global | sed 's/^.*inet6 \([0-9a-f:]\+\)\/64.*/\1/g')

if [ "${DNSMASQ_IP}" != "${CURRENT_IP}000" ]; then
	logger -p daemon.info -t SCRIPT "Updating dnsmasq.conf IPv6 DHCP range"
	sed -i "s/dhcp-range=[0-9a-f:]\+, [0-9a-f:]\+, 64, .*$/dhcp-range=${CURRENT_IP}000, ${CURRENT_IP}fff, 64, 168h/g" /etc/dnsmasq.conf
	if [ $? -ne 0 ]; then
		logger -p daemon.error -t SCRIPT "Failed to update dnsmasq.conf IPv6 DHCP range"
		exit 1

	systemctl reload dnsmasq
	if [ $? -ne 0 ]; then
		logger -p daemon.error -t SCRIPT "Failed to reload dnsmasq.conf"
		exit 1

The above script check current eth1 IPv6 address and current DNSMasq configuration: if there is a mismatch, the dnsmasq.conf file is updated and the DNSMasq daemon is restarted. It must run every minute:

# cat /etc/cron.d/update_dnsmasq
* * * * * root /usr/local/sbin/update_dnsmasq.sh

Firewalling with IPv6

Assigning a global IPv6 address to each internal host, means that every host is reachable from Internet. So be sure only allowed connections are permitted. I suggest to allows ICMPv6 because it’s part of IPv6 protocol. If some ICMPv6 types are filtered, IPv6 can be broken.