Blog post
Setting up IPv6 for my servers using HE's tunnelbroker.net
How I configured an Debian server as an IPv6 router using Hurricane Electric's free Tunnel Broker service.
Intro
I wanted to give my self-hosted servers proper IPv6 connectivity, but my ISP does not provide native IPv6 (shame on you, Odido). Instead of replacing my existing network setup, I decided to use Hurricane Electric's free IPv6 Tunnel Broker service.
The setup is fairly straightforward: Hurricane Electric provides an IPv6-over-IPv4 tunnel, and I use a small Debian server as the IPv6 router for the rest of my network.
Creating the tunnel
The first step is creating a tunnel through Hurricane Electric's Tunnel Broker.
After creating a tunnel, Hurricane Electric provides the IPv4 endpoints and the IPv6 addresses needed to configure it.
I configured the tunnel on my Debian server by creating a new network interface using the v4tunnel method. The configuration in /etc/network/interfaces looks like below. Variables are replaced with the actual values provided by Tunnel Broker displayed on the Tunnel Details page.
auto he-ipv6
iface he-ipv6 inet6 v4tunnel
address [Client IPv6 address]
netmask 64
endpoint [Server IPv4 address]
local [Client IPv4 address, e.g., the public IPv4 of the Debian server (or local IPv4 if behind NAT)]
ttl 255
gateway [Server IPv6 address]
Once configured, I could verify that IPv6 connectivity through the tunnel was working:
ping -6 [Server IPv6 address]
ping -6 google.com
Forwarding protocol 41 on the EdgeRouter
The Debian router is behind an EdgeRouter X, so the IPv6-over-IPv4 tunnel traffic must be forwarded to the VM. The tunnel does not use TCP or UDP; it uses IP protocol 41.
I added a firewall rule on the WAN interface to allow protocol 41 from Hurricane Electric's tunnel endpoint.
firewall {
name WAN_IN {
rule 21 {
action accept
description "Allow protocol 41 for forwarding"
log disable
protocol 41
source {
address [Server IPv4 address]
}
}
}
}
The source address restriction is important because it only allows protocol 41 from the HE endpoint assigned to the tunnel. I then added a destination NAT rule to send that traffic to the Debian VM:
service {
nat {
rule 1 {
description "Forward protocol 41 to IPv6 Router VM"
inbound-interface eth0.300
inside-address {
address [IPv4 address of the Debian VM]
}
log disable
protocol 41
type destination
}
}
}
Forwarding IPv6 traffic
Since the Debian machine is now acting as a router, IPv6 forwarding needs to be enabled in /etc/sysctl.d/80-ipv6.conf:
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.all.accept_ra=0
After enabling forwarding, traffic can flow between the HE tunnel and the internal network.
At this point, a server with an address such as:
2001:470:1f15:4d6::20
can be reached directly over IPv6.
Forwarding ports with nftables
For some services I still wanted the Debian router to act as a simple IPv6 gateway and forward incoming connections to specific servers. The reason behind this is that not all services are running on the same server, e.g. the reverse proxy is on a different server than the GitLab instance.
I used nftables to accomplish forwarding incoming connections to the appropriate server. For example, to forward HTTP and HTTPS traffic to a web server at 2001:470:1f15:4d6::10, I added the following rules to /etc/nftables.conf:
table ip6 nat {
chain prerouting {
type nat hook prerouting priority dstnat;
policy accept;
iifname "he-ipv6" tcp dport 80 dnat to 2001:470:1f15:4d6::10
iifname "he-ipv6" tcp dport 443 dnat to 2001:470:1f15:4d6::10
}
}
Hairpinning
I also wanted connections from inside my network to behave the same way as connections from the Internet.
For example, accessing:
2001:470:1f15:4d6::1
from the internal network should still reach the web server at:
2001:470:1f15:4d6::10
I added separate DNAT and SNAT rules for the internal interface to accomplish this:
table ip6 nat {
chain prerouting {
type nat hook prerouting priority dstnat;
policy accept;
iifname "ens18" ip6 daddr 2001:470:1f15:4d6::1 tcp dport 80 \
dnat to 2001:470:1f15:4d6::10
iifname "ens18" ip6 daddr 2001:470:1f15:4d6::1 tcp dport 443 \
dnat to 2001:470:1f15:4d6::10
}
chain postrouting {
type nat hook postrouting priority srcnat;
policy accept;
ip6 saddr 2001:470:1f15:4d6::/64 \
ip6 daddr 2001:470:1f15:4d6::10 \
tcp dport 80 \
snat to 2001:470:1f15:4d6::1
ip6 saddr 2001:470:1f15:4d6::/64 \
ip6 daddr 2001:470:1f15:4d6::10 \
tcp dport 443 \
snat to 2001:470:1f15:4d6::1
}
}
This makes the router handle the connection in both directions.
Testing the setup
When debugging IPv6, tcpdump is extremely useful.
For example:
tcpdump -ni any 'ip6 host 2001:470:1f15:4d6::10'
When an external client connects to the router on port 80, I expect to see the packet arrive through the HE tunnel:
he-ipv6 In
... > 2001:470:1f15:4d6::1.80: Flags [S]
and after DNAT, leave through the internal interface:
ens18 Out
... > 2001:470:1f15:4d6::10.80: Flags [S]
This makes it much easier to determine where something is going wrong.
For example, if the packet arrives through he-ipv6 but never appears on ens18, the problem is somewhere between the incoming connection and the forwarding decision.
Conclusion
Using Hurricane Electric's Tunnel Broker gave me a relatively simple way to add IPv6 to my existing network without changing my ISP or replacing my router.
The nice thing about this setup is that the servers now have proper globally routable IPv6 addresses, while the Debian machine can still be used to forward or NAT specific services when needed.
Problems?
If you find mistakes or have suggestions, let me know. If you encounter problems, check the tunnel endpoints, IPv6 routes, forwarding settings and nftables rules. tcpdump can help determine whether the packet reaches the router and which interface it leaves through. Also, make sure the EdgeRouter is forwarding protocol 41 to the Debian router and that your routers and hypervisors firewall rules allow the traffic to pass through.