Skip to main content

Use Apple TV through a Tailscale Exit Node

ß

Update October 2, 2023 #

With the release of tvOS 17 and its support for VPNs, Tailscale is now available as an app on the Apple TV. For my use case, that’s much easier than setting up a second WiFi router just to maintain the Tailscale connection for the Apple TV.

Contents #

  1. Why one might do this
  2. Minimum requirements
  3. Install Raspberry Pi OS on the SD card using Raspberry Pi imager
  4. Install drivers for the second WiFi interface
  5. Configure the Pi as a WiFi access point
  6. Install Tailscale
  7. Use iptables to route traffic and normalize MTU
  8. Connect your Apple TV or other device

1. Why one might do this #

Shortly after I wrote a guide to using a Raspberry Pi with dual-WiFi as a WireGuard VPN for an Apple TV, I started experimenting with Tailscale. My WireGuard VPN solution worked well enough, but I wanted to replace my VPN provider with a homespun solution. Tailscale seemed like a great way to solve that problem, especially as I had access to multiple networks.

The setup isn’t very different from the VPN router, though RaspAP is a bit overkill for a Tailscale-based architecture. Instead, this guide will walk through installing only hostapd, which is one tool RaspAP uses under the hood, to turn the Raspberry Pi into a WiFi access point for the Apple TV, eventually routing all its traffic through Tailscale.

2. Minimum requirements #

If you don’t need your traffic to go through a remote network, your Tailscale exit node could be the same Raspberry Pi you configure as an access point for the Apple TV, but that’s really no different than connecting to your regular WiFi router.

In an ideal scenario, the remote location you use will be a residential network, though it could be on any machine located anywhere you choose to run a Tailscale exit node. Your use case should determine your needs.

For my purposes, I installed Tailscale on another Raspberry Pi and configured it as an exit node. Then I deployed that machine to a remote network I have access to.

While you should be able to follow along with these commands relatively easily, this guide does assume basic familiarity with the Linux CLI environment, for example with editing files and restarting services.

3. Install Raspberry Pi OS on the SD card using Raspberry Pi imager #

Download Raspberry Pi imager to install Raspberry Pi OS onto your SD card. Follow the instructions in Raspberry Pi imager to select the operating system and SD card. Before you click “write,” click on the button with the gear icon to set up options like allowing SSH, the default user, and the details of your local WiFi network. Save the settings, click “write,” and wait for the imaging to complete.

Plug your newly flashed SD card into your Raspberry Pi and power it on. It should be all set up and connected to your network. Consult your router to learn the new ip address of your Pi. Mine showed up with an address of 192.168.7.183.

4. Install drivers for the second WiFi interface #

Log into the Pi using the username and password you added through Raspberry Pi imager:

ssh pi@192.168.7.183

Before plugging in the second WiFi adapter, you need to update the system and install additional software and drivers.

First, make sure the system is up to date:

sudo apt update && sudo apt upgrade -y

Now install necessary packages and software to enable building the WiFi driver from source:

sudo apt install -y raspberrypi-kernel-headers bc build-essential dkms git

Reboot the Pi to start using the new headers and updated software:

sudo reboot now

Log back in and download the source for the WiFi driver:

mkdir -p ~/src
cd ~/src
git clone https://github.com/morrownr/8821cu-20210118.git

Change into the directory:

cd ~/src/8821cu-20210118

Configure the Makefile to make it suitable for the Pi. If your Pi is 32 bit:

./ARM_RPI.sh

Or if your Pi is 64 bit:

./ARM64_RPI.sh

Install the driver and reboot:

sudo ./install-driver.sh NoPrompt && sudo reboot now

Plug in the AC WiFi adapter and log back in. Check to see that both WiFi interfaces are active:

ip addr show

You should now see two wireless interfaces. wlan0 is the internal WiFi, and wlan1 should be the new WiFi adapter.

5. Configure the Pi as a WiFi access point #

Most of the instructions in this section come from the Raspberry Pi Wireless Access Point tutorial from PiMyLifeUp.

Install the software necessary to run the Pi as a WiFi access point:

sudo apt install -y hostapd dnsmasq iptables

Modify /etc/dhcpcd.conf to set a static address for the wlan0 interface and to instruct that interface to ignore WiFi connection details provided by wpa_supplicant. You will also tell the service to use wlan1 as a gateway.

Add these lines at the bottom of the file:

interface wlan1
gateway

interface wlan0
    static ip_address=192.168.56.1/24
    static routers=192.168.56.1
    nohook wpa_supplicant
    metric 500

Restart the dhcpcd service:

sudo systemctl restart dhcpcd

Create a configuration file for hostapd:

sudo nano /etc/hostapd/hostapd.conf

Add the following parameters, and don’t forget to change the network SSID and passphrase:

interface=wlan0

hw_mode=g
channel=6
ieee80211n=1
wmm_enabled=0
macaddr_acl=0
ignore_broadcast_ssid=0

auth_algs=1
wpa=2
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP

# This is the name of the network
ssid=Change-To-Your-Network-Name

# The network passphrase
wpa_passphrase=Your-Network-Password

Make sure hostapd knows about the configuration file:

sudo sed -i 's|#DAEMON_CONF=\"\"|DAEMON_CONF=\"/etc/hostapd/hostapd.conf\"|g' /etc/default/hostapd

Back up the default dnsmasq configuration file:

sudo mv /etc/dnsmasq.conf /etc/dnsmasq.conf.bak

Create a new dnsmasq configuration file at /etc/dnsmasq.conf with the following content:

interface=wlan0      # Use interface wlan0
server=1.1.1.1       # Use Cloudflare DNS
dhcp-range=192.168.56.50,192.168.56.150,12h # IP range and lease time

Enable forwarding of traffic on IPv4 and IPv6:

sudo sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/g' /etc/sysctl.conf
sudo sed -i 's/#net.ipv6.conf.all.forwarding=1/net.ipv6.conf.all.forwarding=1/g' /etc/sysctl.conf

Add the following iptables rule to configure a NAT between the two WiFi interfaces:

sudo iptables -t nat -A POSTROUTING -o wlan1 -j MASQUERADE

Save the iptables rules so they can be re-enabled on boot:

sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"

Add the following line to /etc/rc.local, above exit 0 to apply those iptables rules after each boot:

iptables-restore < /etc/iptables.ipv4.nat

Restart the hostapd and dnsmasq services and reboot:

sudo systemctl unmask hostapd
sudo systemctl enable hostapd
sudo systemctl restart hostapd
sudo service dnsmasq restart
sudo reboot now

When the Pi comes back online, at this point it should be broadcasting a WiFi network that you can connect to and through which you can get access to the internet. Once you’re connected to the new WiFi network, you can ssh into the Pi using the static IP address you assigned earlier:

ssh pi@192.168.56.1

6. Install Tailscale #

Tailscale provides excellent documentation for installing on a Raspberry Pi. They also provide a handy one-liner if you’re comfortable with it:

curl -fsSL https://tailscale.com/install.sh | sh

When the installer script finishes, authorize the Pi to access your private tailnet:

sudo tailscale up

Tell Tailscale to use an exit node and allow LAN access:

sudo tailscale up --exit-node=YOUR-TAILSCALE-EXIT-NODE-IP --exit-node-allow-lan-access

Now if you call curl ifconfig.me from the Pi command line, you should see the IP address of your exit node. But the current configuration breaks internet access from any machine connected to the Pi’s WiFi network. To fix that, you need to add another rule to iptables:

sudo iptables -t nat -A POSTROUTING -o tailscale0 -j MASQUERADE

With that, the Pi knows how to manage traffic between its different interfaces. curl ifconfig.me from your computer connected to the Pi’s WiFi should now also show the Tailscale exit node IP address.

7. Use iptables to route traffic and normalize MTU #

One more step remains. Tailscale uses an MTU of 1280, while the standard network interface MTU is 1500. What’s MTU?

In networking, maximum transmission unit (MTU) is a measurement representing the largest data packet that a network-connected device will accept. Imagine it as being like a height limit for freeway underpasses or tunnels: Cars and trucks that exceed the height limit cannot fit through, just as packets that exceed the MTU of a network cannot pass through that network.

However, unlike cars and trucks, data packets that exceed MTU are broken up into smaller pieces so that they can fit through. This process is called fragmentation. Fragmented packets are reassembled once they reach their destination.

MTU is measured in bytes — a “byte” is equal to 8 bits of information, meaning 8 ones and zeroes. 1,500 bytes is the maximum MTU size.

Why does this matter? I found that some websites wouldn’t load properly for any machine connected to the Pi’s WiFi. In particular, any website that uses Fastly failed to load because the initial TLS handshake broke down. Testing with curl produced the following output:

curl --verbose https://fastly.com
*   Trying 151.101.193.57:443...
* Connected to fastly.com (151.101.193.57) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):

From a regular browser, often it would like the request would just hang or it would time out. With so many moving parts in play between a handful of devices, I thought at first it was a networking or DNS issue. After a day of troubleshooting, I traced the issue back to MTU, a known problem as described here and here.

I was able to solve this problem with iptables, as proposed in the forum post:

sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

Indeed, RaspAP employs a similar rule when using WireGuard.

I’m no networking expert and the fine points of a detailed explanation are beyond me, but as I understand it that rule modifies packet transmission size if it proves problematic for Tailscale to handle.

To make that rule and forwarding traffic through Tailscale persistent, save the iptables rules again:

sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"

With that, you should have a working Raspberry Pi WiFi access point that routes all traffic through a Tailscale exit node.

8. Connect your Apple TV or other device #

Finally, open the Apple TV network settings and connect to the new WiFi network, or do the same from any device you’d like to have use the Tailscale exit node.