Setting up IPv6 exit node in Zerotier or WireGuard with SLAAC Address

The Problem I am facing

In my university, the network type can be divided into two kinds:

The access point is placed in every dormitory with four Gigabit Ethernet ports and Wi-Fi 6 wireless. However, it has two limitations:

However, using an Ethernet cable connection (but not wireless), the speed from the dormitory area to the research area (i.e. Intranet access) can be up to 100Mbps and require no authentication when establishing a connection on port 53 using UDP.

The access point, which is several Gigabit Ethernet ports on the wall, is placed in nearly every room in administration buildings or research buildings.

The external backbone is CERNET IPv4 / IPv6 dual stack and can be up to 1000Mbps speed per account.

However, IPv6 is assigned by SLAAC rather than Prefix Delegation. The host cannot be accessed from the Internet because there exists at least one stateful firewall in the path.

When I back to my home, the official tunnel provided by my uni was limited to access some services only. That is, it is a selective tunnel rather than a "proxy all" tunnel.

In case I want to access a website that is not in the proxy range, I will not be able to do that since this traffic is not routed via (more precisely, accepted by) the school tunnel.

Plus, this official tunnel is completely unable to be used when registering for the courses. I need to find another way to get into uni's Intranet to avoid the tragedy.

To summarize, the problems are as follows:

Summarize needs

For problem 1:

Since legally I can only use my account for authentication which limits the port connection, I must find a way to establish a fast and secure tunnel on port 53 using UDP.

Plain DNS tunnel is just too slow and w/o support on multi-platform. Other tunnel protocols are too complex and too slow when compared to WireGuard.

So WireGuard is the best choice. It has the fastest speed and runs on UDP.

For problem 2:

Since there exists a stateful firewall which neither I have access to nor can bypass easily, the choices are limited to either setting up a public server served as a relay or using a "managed" virtual network.

And I don't want to pay an extra fee. So after comparison, I choose Zerotier.

After solving usability, we can consider something more. Both WireGuard and Zerotier support IPv6.

And rethink from a higher level, can I set up Zerotier to solve problem one?

The answer is no. I managed to change the default port from 9993 to 53. However, the connection quality to jumphost is very bad. I assume the reason behind this is the host discovery mechanism is not suitable in this scenario.

Part 0, Basic setup

I use Ubuntu 24.04 LTS as the jumphost operating system.

Setup software:

sudo apt update && sudo apt full-upgrade
sudo apt install wireguard ndppd
curl -s https://install.zerotier.com | sudo bash

For mainland China readers, apart from configuring apt mirrors, you may want to replace the last command with this:

curl https://install.zerotier.com/ | sed 's,download.zerotier.com/,mirrors.ustc.edu.cn/zerotier/,g' | sudo bash

And you should always follow the official guide:

Download Zerotier

Install WireGuard

ndppd's Github repo

Basic settings and checks

Edit /etc/sysctl.conf to add the following lines to support IPv4 & IPv6 forwarding:

net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1

# And optionally, BBR congestion algorithm:
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr

Some useful commands for checking firewall status, IP address, route tables.

iptables -L
iptables -L -t nat
nft list ruleset
ufw status
ip a
ip r

And we do some conventions here:

INTERNET_IF: the interface on jumphost that has Internet access and the user's device has access to it.
INTERNET_IPv4 / INTERNET_IPv6: the corresponding IP of INTERNET_IF
INTERNET_IPv4_RANGE / INTERNET_IPv6_RANGE: the corresponding IP range of INTERNET_IF

ZT_IF: Zerotier interface on jump host
ZT_IPv4 / ZT_IPv6: the corresponding IP of ZT_IF
ZT_IPv4_RANGE / ZT_IPv6_RANGE: the corresponding IP range of ZT_IF

WG_IF: Wireguard interface on jump host
WG_IPv4 / WG_IPv6: the corresponding IP of WG_IF
WG_IPv4_RANGE / WG_IPv6_RANGE: the corresponding IP range of WG_IF

The most important thing to check is the WAN_IPv6 subnet mask. As long as not an /128, we can use the NDP relay scheme. Otherwise, we can only use the IPv6 NAPT scheme. Luckily, the writer's uni distributes /64 subnet. This means the writer can customize the remaining 64 bits of network address.

Part 1, WireGuard setup

Brainstorming

We need a good IPv6 range that has the same prefix as INTERNET_IPv6, and the prefix length is a subnet mask. We can choose freely in the remaining bits. But notice that, the more bits you customize, the fewer clients you can have. e.g.

INTERNET_IPv6_RANGE: aaaa:bbbb:cccc:dddd:1111:2222:3333:4444/64

Since we have /64 masks, means our WG_IPv6_RANGE must have aaaa:bbbb:cccc:dddd as the beginning. And we like this interesting string: 0d00 0721. So:

WG_IPv6_RANGE: aaaa:bbbb:cccc:dddd:0d00:0721:0001:0/112

We fixed 0d00:0721:0001 as our WG_IPv6_RANGE's prefix and its corresponding mask is /112

Second, choose a proper IPv4 range. The writer recommends to choose within 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16 with two limitations:

This is to avoid some confusion.

This is obvious I guess?

(And this is checked when using WireguardConfigGenerator written by the writer)

Wireguard Configuration

The writer has written a simple generator that supports IPv6.

WireguardConfigGenerator

The writer recommends that readers read official Wireguard documentation, as they provide enough background information.

Tl;dr:

{
 "client_number": YOUR_CLIENT_NUMBER,
 "ipv4_range": "WG_IPv4_RANGE",
 "ipv6_range": "WG_IPv6_RANGE",
 "preshared_key_flag": true,
 "server": {
 "persistent_keepalive": "",
 "listen_port": "51820",
 "fwmark": "",
 "dns": "",
 "mtu": "",
 "table": "",
 "preup": "",
 "postup": "",
 "predown": "",
 "postdown": "",
 "saveconfig": ""
 },
 "client": {
 "allowed_ips": "0.0.0.0/0, ::/0",
 "endpoint": "INTERNET_IPv4:51820",
 "persistent_keepalive": "",
 "fwmark": "",
 "dns": "8.8.8.8",
 "mtu": "",
 "table": "",
 "preup": "",
 "postup": "",
 "predown": "",
 "postdown": "",
 "saveconfig": ""
 }
}

Then, put generated server.conf at jumphost's /etc/wireguard/ directory.

Last, of this sector, activate auto start on booting:

systemctl enable --now wg-quick@server

Modify Firewall

The writer recommends to use nftables over iptables and uses it. So the writer will provide nftables configuration only.

The writer's config is not optimized for security at the current time. Plus when copying and pasting this config, the reader needs to make sure ufw is disabled and iptables is clean. Be wary of these!

Edit /etc/nftables.conf

Perhaps it's better to set a proper MTU value instead of doing MSS clamping since MSS clamping only affect TCP packet.

#!/usr/sbin/nft -f

flush ruleset

table ip nat {
        chain prerouting {
                type nat hook prerouting priority dstnat; policy accept;
        }

        chain postrouting {
                type nat hook postrouting priority srcnat; policy accept;
                oifname "INTERNET_IF" masquerade
        }
}

table inet filter {
        chain input {
                type filter hook input priority filter; policy accept;
        }

        chain forward {
                type filter hook forward priority filter; policy accept;
                tcp flags syn tcp option maxseg size set rt mtu
        }

        chain output {
                type filter hook output priority filter; policy accept;
        }
}

Enable and reboot nftables service:

systemctl enable nftables
systemctl restart nftables

(Optional) Setup NDP Proxy

Now if the reader is lucky, by connecting Wireguard the client is already connected to the Internet via jumphost. However, there exists a situation in which the gateway of INTERNET_IF does not handle IPv6 traffic without NDP discovery. Then we need to do NDP proxy.

The writer uses ndppd to proxy NDP. But its behaviour is a mystery. If not working, please try other options in the configuration file or switch to other NDP proxy tools. The writer cannot guarantee that it is usable and has a plan to implement NDP proxy in the future. Stay tuned.

Edit /etc/ndppd.conf

route-ttl 30000
proxy INTERNET_IF {
 router yes
 timeout 500
 keepalive yes
 rule WG_IPv6_RANGE {
 static
 }
}

Part 2, Zerotier setup

Brainstorming

Just the same as Part 1, but the writer recommends not assigning the same subnet for clarity.

Zerotier Configuration: Pane Parts

The reader has two (actually three) choices:

The writer recommends ZeroTier 6PLANE over others because:

See:

6PLANE introduction

Now, go to Settings > Advanced > IPv6 Auto-Assign. Select ZeroTier 6PLANE and unselect ZeroTier RFC4193. Then configure Auto-Assign from Range by using Set IPv6 Address Pool. Fill Range Start and Range End according to ZT_IPv6_RANGE.

Then, go to Settings > Advanced > IPv4 Auto-Assign. Set Auto-Assign Pools by filling Range Start and Range End according to ZT_IPv4_RANGE.

After that, go to Settings > Advanced > Managed Routes. Make sure that it has ZT_IPv4_RANGE (LAN) and ZT_IPv6_RANGE (LAN). If not, put ZT_IPv6_RANGE or ZT_IPv6_RANGE in Add Routes > Destination and leave Via blank to add these rules.

Now, the reader can check and change auto-assigned IPv4 and IPv6 addresses for Zerotier clients.

The last but optional step is if the reader wants a "connect and use" VPN. The reader needs to add these two routes: 0.0.0.0/0 to ZT_IPv4 and::/0 to ZT_IPv6(if using 6PLANE, set to 6PLANE IP). However, the writer's choice is to manually set it per client.

Zerotier Configuration: Local Parts

Run these commands:

zerotier-cli set <networkId> allowDefault=1
zerotier-cli set <networkId> allowGlobal=1
zerotier-cli set <networkId> allowManaged=1

NOTICE: these commands need to be executed on every client and jump host if using "connect and use" VPN mode.

Modify Firewall

The same as Part 1.

(Optional) Setup NDP Proxy

The same as Part 1.

(Optional) How to Set up Full Tunnel

Most OS can have only one effective default route. However, we can do this:

route add 0.0.0.0 mask 128.0.0.0 ZT_IPv4
route add 128.0.0.0 mask 128.0.0.0 ZT_IPv4
route add ::/1 ZT_IPv4
route add 8000::/1 ZT_IPv6

And this is how Zerotier does the Full Tunnel. The reason behind it is very simple and leaves it as a quiz for the reader.

Checklist

Path MTU prober

PathMTUProber written by me



You've reached the end of this page. And you may Go to index or visit my friends.
About me and contacts
Except where otherwise noted, this site is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License