Kludging AAAA for a tailnet

hdhoang
2 min readJul 26, 2021

Tailscale is great. Avery Pennarun asked for questions. Here’s my question: can we get more details on IPv6 for peers in the KB, please?

With MagicDNS enabled, we can resolve node.domain.example.beta.tailscale.net. to the tailnet 100.64/10 addresses:

❯ nslookup peer.domain.example.beta.tailscale.net.
Server: UnKnown
Address: 100.100.100.100
Name: peer.domain.example.beta.tailscale.net
Address: 100.99.46.53

Apps resolves & caches such IPv4 addresses. However, when the peer disconnects from tailnet, or changes its address, our apps’ packets to old addresses would go out via non-encrypted IPv4 default routes:

# ip ro get 100.101.102.103
100.101.102.103 dev tailscale0 src 100.117.192.41
# # that's correct & fine
# ip ro get 100.99.46.53
100.99.46.53 via 10.1.1.1 dev br-lan src 10.9.9.9
# # oh whoops, I hope we have additional encryption/authentication for these packets!!!

The tailnet also allocate IPv6 address from ULA subnet fd7a:115c:a1e0::/48 with straight-forward embedding of the 3-octet tail (heh):

$ tailscale status -json | \
jq -r '.Peer[] | .DNSName + "\t" + .TailAddr' | \
perl -npe 's/100\.(\d+)\.(\d+)\.(\d+)/sprintf("fd7a:115c:a1e0:ab12:4843:cd96:62%02x:%02x%02x",int($1),int($2),int($3))/e'

(Please keep in mind, the status -json data format is subject to change)

The core embedding is 100.99.46.102 as fd7a:115c:a1e0:ab12:4843:cd96:98.99.46.102 (I don’t get why they change 100 to 98 though, maybe to satisfy /104 mask somewhere). The ULA is inserted into route table as a /48 subnet, instead of exact-/32-one-host-only as with IPv4:

❯ route -6 print fd7a*IPv6 Route Table
==================
Active Routes:
If Metric Network Destination Gateway
4 5 fd7a:115c:a1e0::/48 On-link
4 261 fd7a:115c:a1e0:ab12:4843:cd96:62ba:be22/128
On-link

As I understand, it’s more leak-proof than IPv4 addresses (and the convenient MagicDNS names). Sadly, the world is full of potholes, and tailscale explicitly discards IPv6 addresses from MagicDNS answers for peers, even though KB 1054 shows such an answer in the Windows output.

Either way, we can generate a hosts file with custom -6 names for our current peers:

tailscale status -json | jq '
(.Self | [.TailscaleIPs[1], .HostName + "-v6 ", .HostName + ".$animal-$dance.ts.net"]) ,
(.Peer | map([.TailscaleIPs[1], .HostName +"-v6 ", .HostName + ".$animal-$dance.ts.net"]) |.[])
|@tsv' --raw-output
fd7a:115c:a1e0:ab12:4843:cd96:62ba:be22 here-v6 here.yak-bebop.ts.net
fd7a:115c:a1e0:ab12:4843:cd96:6265:6667 hello-v6 hello.yak-bebop.ts.net
fd7a:115c:a1e0:ab12:4843:cd96:62ab:cd12 peer-v6 peer.yak-bebop.ts.net

It’s not Magic. It’s downright manual like the primordial Inter-network, but it wfm. And it will cover HTTPS-enabled tailnet aliases as well.

With “static” hosts file, we can also make services bind to the ULA tailnet address the way Fly suggests for their 6PN network:

syncthing -gui-address=http://$(uname -n)-v6:8384/

Voila, only trusted, up-to-date remote management only.

--

--