How to set up a small tinc VPN using IPv6 payloads over IPv4 and iproute2

Follow-Up: How to tunnel IPv4 internet through tinc

Correction: In the first version, I had the tinc host configuration Subnet variables have a /64 network prefix. This was a copy mistake from the tinc-up scripts, it needs to be /128. Thanks to Miguel for the hint.

This describes the setup I use to connect my computers at home, on the road, my home server and virtual server with tinc, „a Virtual Private Network (VPN) daemon that uses tunnelling and encryption to create a secure private network between hosts on the Internet”.

Additionally, I explain the setup for interconnecting mutliple tinc VPNs at the end of this article.

I used an IPv6 ULA subnet (Unique Local Address, RFC4193) and iproute2 for this. A single always-on machine with a static IPv4 address and which is not behind a NAT is handy as a central entry and discovery node for all other machines, but not required (I use my virtual server for this). Port forwarding (per default port 665, needed for both TCP and UDP) and dynamic DNS should work, too.

All other nodes can be behind a NAT, tinc will use UDP hole punching. If it's possible, two peer nodes behind a NAT can communicate directly after an initial discovery using the entry node. This means the entry node relays the traffic between two peer nodes only when it's needed.

If you prefer IPv4 and ifconfig, check out Marex' at least equally good howto over at execfoo.de. His article was a great help for me (and he was first).

This ifconfig / iproute2 comparison helped me a lot with converting ifconfig to iproute2 commands.

Choosing an IPv6 Subnet

With IPv6, there's nothing like 192.168.X.X, as everything is intended to have a globally unique address. Not all addresses need to be routed globally though - for that purpose, there are ULAs. If you have a spare global /64-Subnet, even better, then you could even route your internet traffic through your VPN leaving for the rest of the internet e.g. at your entry node.

But I'll explain how to get a ULA net here. First, you need to get 40bit of random information (a random 10-digit hexadecimal number) for your global ID.

If you don't want to roll the dice by hand (which is a recommended exercise :P ), you can get a prefix generated at unique-local-ipv6.com.

Let's say you rolled a e3b35ddadf (I'd count that as 4 ones in a row if you manage to roll that in my DSA round) and you want to use that as your global ID. You then need to add "fd" as prefix for ULAs, resulting in the IPv6 net

fde3:b35d:dadf::/48

under your control.

From this, we take „zero”st /64-Subnet from this for our VPN:

fde3:b35d:dadf::/64

If you're one of the „IPv6 addresses are soo long! I can memorize 192.168.0.0/32 much better!“ people - well, that's because you and everyone else uses those few subnets in 192.168.X.X and 10.X.X.X.

Imagine you use 192.168.178.0/32 at your home LAN, and you take 192.168.0.0/32 for your tinc VPN. Then you are at your friend's house, want to connect to your VPN, but nothing works, because they already use 192.168.0.0/32 for their LAN.

Or let's say you and your friend think tinc is so cool that want to interconnect your VPNs, and you both chose 192.168.0.0/32 when you created your tinc VPNs some time ago? Baaamm, address clashes, one of you two has to reconfigure all their machines.

All this won't happen with IPv6 and proper random global IDs. And if you don't want to memorize them, just put some entries in your /etc/hosts file. I find plain host names much more convenient than typing any numerical address.

But now, onward to the actual configuration. This assumes you already installed tinc.

Entry Node Configuration

Chose a $VPN_NAME to identify your VPN.

sudo mkdir -p /etc/tinc/$VPN_NAME/hosts

Then, write into /etc/tinc/$VPN_NAME/tinc.conf:

Name = $ENTRY_NODE_NAME

$ENTRY_NODE_NAME can be any name to identify the entry node. Using your hostname (entering hostname in a terminal returns it) is a good idea. The name may only consist of alphanumeric and underscore characters.

Next file is /etc/tinc/$VPN_NAME/hosts/$ENTRY_NODE_NAME:

Address = $STATIC_IP
Subnet = fde3:b35d:dadf::1/128

The Subnet variable specifies which subnet is assigned to that tinc node. We only assign a single IPv6 address to every node, so we set it to the address we want to assing and /128 prefix length.

$STATIC_IP is the static IPv4 address I mentioned earlier. You can specify multiple address statements, e.g. the first one for IPv6 and, if that can't be reached, the second one for IPv4.

I don't do that, because only then tinc can make direct connections between all node pairs when they're needed even in case one sits behind an IPv4 only internet access. (It would still work, but then all traffic will be relayed over you entry node).

Read the docs if you want to use a DNS name.

After that you generate the public/private key pair for your entry node:

sudo tincd -n $VPN_NAME -K4096

Leave everything at the defaults. This conveniently appends the public key to your host configuration file, we'll need that on the other hosts later.

To finish the entry node configuration, we need two scripts for setting up and tearing down the VPN connection:

/etc/tinc/$VPN_NAME/tinc-up:

1
2
3
#!/bin/sh
ip link set $INTERFACE up
ip addr add fde3:b35d:dadf::1/64 dev $INTERFACE

/etc/tinc/$VPN_NAME/tinc-down:

1
2
#!/bin/sh
ip link set $INTERFACE down

No need to replace $INTERFACE here, it will be replaced by tinc with the virtual network device it uses for your VPN.

The IPv6 address above needs to be the one you configured in the Subnet variable in your host configuration and is the address used for the virtual tinc network interface. But in contrast to the Subnet variable, the /64 subnet prefix length here specifies which subnet you are connected to via tinc, so this should be the /64 subnet you take your IPv6 addresses from for your tinc VPN.

Think of the subnet prefix length here as "which addresses can be reached directly in one hop, without going through a router?".

It's an IPv6 best practice to use only subnets of /64 size, with the exception of /128 for a single address and /127 for a point to point connection. See Section 3 of RFC5375 for more explanation why.

Many features like Router Advertisement, Stateless Link Addres Autoconfiguration and more rely on /64 subnets, but you can use prefixes longer than /64 e.g. when assigning addresses manually. But you need to avoid some special addresses, see Appendix B2 of RFC5375, „Considerations for Subnet Prefixes Longer than /64“ for more details.

Make the scripts executable:

sudo chmod +x tinc-up tinc-down

And you're done configuring your entry node!

Peer Node Configuration

The peer node configuration is very similar. You need to pick a $NODE_NAME (again your hostname makes sense) and a different X for each node for the IP addresses.

sudo mkdir -p /etc/tinc/$VPN_NAME/hosts

/etc/tinc/$VPN_NAME/tinc.conf:

Name = $NODE_NAME
ConnectTo = $ENTRY_NODE_NAME

/etc/tinc/$VPN_NAME/hosts/$NODE_NAME:

Subnet = fde3:b35d:dadf::X/128

Generate the public/private key pair for your this node:

sudo tincd -n $VPN_NAME -K4096

The setup/teardown scripts are exactly the same, except the IP address in the tinc-up script:

/etc/tinc/$VPN_NAME/tinc-up:

1
2
3
#!/bin/sh
ip link set $INTERFACE up
ip addr add fde3:b35d:dadf::X/64 dev $INTERFACE

/etc/tinc/$VPN_NAME/tinc-down:

1
2
#!/bin/sh
ip link set $INTERFACE down

Don't forget to make them executable:

sudo chmod +x tinc-up tinc-down

As already mentioned, then transfer /etc/tinc/$VPN_NAME/hosts/$ENTRY_NODE to your local /etc/tinc/$VPN_NAME/hosts/ folder, and the /etc/tinc/$VPN_NAME/hosts/$NODE_NAME to your entry node's /etc/tinc/$VPN_NAME/hosts/ folder.

No need to copy peer node host configuration files around between each other, they'll find each other over the entry node without any additional configuration. Yay, tinc magic! \o/

Enable your tinc VPN on system startup

Start your tinc engines on your entry node and all peer nodes, and test your VPN!

For systems using systemd:

sudo systemctl enable tincd@$VPN_NAME
sudo systemctl start tincd@$VPN_NAME

For systems using SystemV-style init (=> Debian, because it's still the only one ancient enough for them) or upstart (=> Ubuntu, because they're Ubuntu), that is:

cat $VPN_NAME >> /etc/tinc/nets.boot
service tinc start

Your connection will need some time after startup until it's established. Test it with

ping6 fde3:b35d:dadf::1

and have fun!

Interconnecting tinc networks

If you want to connect you tinc network to the tinc network of that friend mentioned earlier, not much work is left.

Let's say they rolled a

fde3:b35d:dadf::/48

and use the subnet

fde3:b35d:dadf::/64

for their tinc network.

Then, to everey tinc-up script on your nodes, you have to add a line saying:

ip route add fde3:b35d:dadf::/64 dev $INTERFACE

Again, $INTERFACE is replaced by tinc, no need to do it yourself.

Next, you need to copy their entry node host configuration file to your /etc/tinc/$VPN_NAME/hosts/ on your entry node.

Now the entry node knows the details about the friend's entry node. You then need to add another line to the /etc/tinc/$VPN_NAME/tinc.conf on your entry node:

ConnectTo = $ENTRY_NODE_NAME_OF_FRIEND

and you're done! (Don't forget to restart tinc, though. ;) )

Remarks? Additions? Corrections? For anything you want to tell me about this blog post, feel free to send me an email.
Despite having no comments section (isn't that easy with a static site generator and without relying on a proprietary 3rd party service), I greatly appreciate direct feedback. ;) In case of additions, I'll mention the name from the mail if you don't object.