Windows Kills SMB Speeds When Using Tailscale
Dan Salmon
The Issue
Yesterday when trying to transfer an ISO file from a TrueNAS SMB share, I was getting horrible transfer speeds.
The NAS can definitely saturate a gigabit link which is what my desktop connects to the switch over. I had seen this behavior before and had a feeling it was related to having Tailscale running - the last time it happened I quit Tailscale and my fast transfers returned.
Why would this matter?
Well, in my home network I have a Proxmox VM that, among other things, runs Tailscale as a subnet router. I set it up this way so that my homelab services (especially Pi-hole) are available to my laptop and phone from outside my house.
Consequently, my desktop now has 2 interfaces that can both reach my trusted 10.10.1.1/24
network - the gigabit NIC connected to the switch and the Tailscale virtual adapter.
A quick online search led me to a thread on the Tailscale forums describing the exact behavior I was seeing.
I learned that Windows has a system for determining which interface will handle a request when multiple interfaces could be used. Just like dynamic routing protocols calculate the “cost” of each route to a remote network, Windows has a system to calculate which interface to use. Windows refers to this cost as the “Interface Metric” which is calculated based on the physical interface medium and its link speed (as documented here in the last table).
Since the Tailscale interface advertises a link speed of 100Gbps, Windows assigns it a much lower interface metric than the integrated gigabit NIC on my motherboard. Lower metric = lower “cost” associated.
PS C:\> Get-NetAdapter | Where-Object {$_.Name -EQ "Tailscale" -or $_.Name -EQ "Ethernet"}
Name InterfaceDescription ifIndex Status LinkSpeed
---- -------------------- ------- ------ ---------
Ethernet Intel(R) I211 Gigabit... 10 Up 1 Gbps
Tailscale Tailscale Tunnel 9 Up 100 Gbps
PS C:\> Get-NetIPInterface | Where-Object { ($_.InterfaceAlias -EQ "Tailscale" -or $_.InterfaceAlias -EQ "Ethernet") -and $_.AddressFamily -eq "IPv4"}
ifIndex InterfaceAlias AddressFamily NlMtu(Bytes) InterfaceMetric Dhcp ConnectionState PolicyStore
------- -------------- ------------- ------------ --------------- ---- --------------- -----------
10 Ethernet IPv4 1500 25 Enabled Connected ActiveStore
11 Tailscale IPv4 1280 5 Disabled Connected ActiveStore
I only want traffic destined for my Tailnet to use the Tailscale interface. To ensure Windows chooses the Ethernet interface in all other cases, we just need to increase the Interface Metric on the Tailscale interface to be higher than 25.
The Fix
The usual way to change this is in the Control Panel, but if you try to set the Interface Metric manually, Windows won’t let you because the Tailscale interface somehow has an “empty” static IP address set.
Powershell to the rescue:
PS C:\Windows\system32> Get-NetAdapter | Where-Object -FilterScript {$_.InterfaceAlias -Eq "Tailscale"} | Set-NetIPInterface -InterfaceMetric 500
PS C:\Windows\system32> Get-NetIPInterface |Where-Object { ($_.InterfaceAlias -EQ "Tailscale" -or $_.InterfaceAlias -EQ "Ethernet") -and $_.AddressFamily -eq "IPv4"}
ifIndex InterfaceAlias AddressFamily NlMtu(Bytes) InterfaceMetric Dhcp ConnectionState PolicyStore
------- -------------- ------------- ------------ --------------- ---- --------------- -----------
10 Ethernet IPv4 1500 25 Enabled Connected ActiveStore
9 Tailscale IPv4 1280 500 Disabled Connected ActiveStore
After I changed this, I was back to getting near-gigabit speeds over the Ethernet interface.
Future Steps
Switch this machine to a Linux distro and upgrade the NIC to 10G!