Tailscale with nftables support

Wanted to start a discussion for testing tailscale with nftables support. See the post here:

As #8555 is merged, we will have an initial support for nftables based configuration on Linux behind a temporary flag in the 1.46 release.

In this commit, we have added nftables support for equivalent functionality to iptables. This includes setting up Tailscale rules in nftables fashion, clear nftables rules when tailscaled --cleanup is ran, and a envknob to tell Tailscale to use nftables instead of iptables.

This is an initial implementation and may be unstable. Testing and feedback on various linux distributions is appreciated, and will help us to improve this functionality over time.

To use tailscale with nftables, user has to set TS_DEBUG_USE_NETLINK_NFTABLES=true. This can be done by going to /etc/default/tailscaled and add the line

TS_DEBUG_USE_NETLINK_NFTABLES=true

Then run tailscale up as usual. If tailscale has been ran before you might need to run systemctl restart tailscaled to restart.

I don't have enough knowledge to find where to set that variable, and also current tailscale package for OpenWRT is only at 1.42, not yet 1.46.

Perhaps @joshenders could be of some help? :slight_smile:

Saw that update the other day—very excited to test it on OpenWrt!

/etc/default is a Debian convention which provides a convenient location for customizing environment variables passed to init scripts. Debian init scripts (and by virtue, systemd unit files) source the shell fragments to override the default values provided by Debian package maintainers.

On OpenWrt, we just need to build a binary that contains this new commit and it can be tested by exporting that var in the current shell environment or prepending the tailscale[d] invocation.

For example:

TS_DEBUG_USE_NETLINK_NFTABLES=true tailscale up

I’ll try to carve out some time to test this but the section I wrote at the bottom of the tailscale page which outlines instructions for building a custom minified tailscale package can be used as a guide if need be.

Fwiw, the Tailscale package on OpenWrt should probably be updated to reference a single multi-call binary for both tailscale and tailscaled. I don’t see any reason to maintain two packages and keep these separate.

Well I was able to just download and install the static binary for my arm64 device. Unpacked it and ran
TS_DEBUG_USE_NETLINK_NFTABLES=true ./tailscaled --state=tailscaled.state
followed by
TS_DEBUG_USE_NETLINK_NFTABLES=true ./tailscale up
and everything worked! As far as I can tell. I've only tested it on a router that isn't my main, so not really using all features.

Only error-like line I saw in the log was router: failed to determine ip command fwmask support: exit status 1 and I'm not sure what that means.
Seems all the nft rules populated correctly.

root@OpenWrt:~/tailscale_1.46.1_arm64# nft list tables
table inet fw4
table ip ts-filter
table ip6 ts-filter
table ip ts-nat
table ip6 ts-nat

and the ts-filter chain

nft list table ip ts-filter
table ip ts-filter {
        chain ts-forward {
                type filter hook forward priority filter - 1; policy accept;
                iifname "tailscale0*" counter packets 0 bytes 0 meta mark set meta mark & 0xffff04ff | 0x00000400
                meta mark & 0x0000ff00 == 0x00000400 counter packets 0 bytes 0 accept
                oifname "tailscale0*" ip saddr 100.64.0.0/10 counter packets 0 bytes 0 drop
                oifname "tailscale0*" counter packets 0 bytes 0 accept
        }

        chain ts-input {
                type filter hook input priority filter - 1; policy accept;
                iifname "lo*" ip saddr 100.77.252.80 counter packets 0 bytes 0 accept
                iifname != "tailscale0*" ip saddr 100.115.92.0/23 counter packets 0 bytes 0 return
                iifname != "tailscale0*" ip saddr 100.64.0.0/10 counter packets 0 bytes 0 drop
        }
}

The device was created properly. Not sure if an interface would need to be created?
Will look a little more at it later.

Well I messed around with tailscale with NFTABLES and seems everything is working well. I used subnet router with no issues and was even able to use it as an exit node.

I downloaded the static binary, extracted tailscale and tailscaled to /usr/sbin and set the proper permissions.
Then I took inspiration from https://github.com/adyanth/openwrt-tailscale-enabler/tree/main and created my init.d file (seen below).
With

/etc/init.d/tailscale start
/etc/init.d/tailscale enable

the program started up and a standard tailscale up command worked without issue. I then created a new unmanaged interface linked to the tailscale0 device and set the appropriate firewall rules for that interface.

#!/bin/sh /etc/rc.common

# Copyright 2020 Google LLC.
# SPDX-License-Identifier: Apache-2.0

USE_PROCD=1
PROCD_DEBUG=1
START=80
STOP=1

start_service() {
  procd_open_instance
  procd_set_param command /usr/sbin/tailscaled

  # Set the port to listen on for incoming VPN packets.
  # Remote nodes will automatically be informed about the new port number,
  # but you might want to configure this in order to set external firewall
  # settings.
  #procd_append_param command --port 41641

  # OpenWRT /var is a symlink to /tmp, so write persistent state elsewhere.
  procd_append_param command --state /etc/tailscale/tailscaled.state

  # Persist files for TLS cert & Taildrop files
  #procd_append_param command --statedir /etc/tailscale/

  # set TS_DEBUG_USE_NETLINK_NFTABLES=true to use NFTABLES (currently beta)
  procd_set_param env TS_DEBUG_USE_NETLINK_NFTABLES=true

  # watch for tailscale0 device changes
  procd_set_param netdev tailscale0

  procd_set_param respawn
  procd_set_param stdout 1
  procd_set_param stderr 1

  procd_close_instance
}

stop_service() {
  /usr/sbin/tailscaled --cleanup
}

I don't know or quite follow how to make/update a 'package' in the openwrt database, but maybe I'll keep reading :slight_smile:

Ok Just saw there is a pull request to update to 1.46.1 to include NFT...