Suppose you have both a personal tailnet and a corporate tailnet. As of 2023-10-01, there is no easy way to use both tailnets simultaneously. At best, Tailscale now supports fast user switching, but that's still not quite as convenient as simultaneous use of multiple tailnets. For reasons that won't be discussed here, there are technical challenges with natively merging tailnets within Tailscale.
This article explores manually merging multiple tailnets under the following conditions:
- There is a "primary" tailnet, where Tailscale operates as it normally should.
- There is (one or more) "secondary" tailnets, where Tailscale mainly works for HTTP-based protocols through the browser.
The idea for bridging two tailnets is to have a machine that is part of both tailnets that acts a proxy for relaying traffic to the appropriate tailnet.
For the "primary" tailnet, Tailscale is running at root with kernel-level tunneling support (i.e., as Tailscale is usually run). This allows networking to operate as we would expect it to for the "primary" tailnet.
For the "secondary" tailnet, Tailscale is running in userspace (possibly as non-root) with userspace networking support and exposing a SOCKS5 proxy. Technically, a SOCKS5 proxy can support networking traffic beyond just HTTP, but the challenge is the lack of transparent support for SOCKS5 across all networking applications.
As an example, let's assume the following initial setup:
- We have a single Linux machine we'll use at the proxy.
- Tailscale is already installed at root and logged into the "primary" tailnet.
- Let's suppose that Tailscale instance is
mymachine.primary.ts.netat100.1.2.3.
- Let's suppose that Tailscale instance is
We now want to setup another tailscaled instance on mymachine
that is registered to the "secondary" tailnet under the domain secondary.ts.net.
We can do so by starting up another tailscaled as follows:
STATE_DIR=/path/to/some/state-directory
LISTEN_ADDR=100.1.2.3:1080
tailscaled \
--socket $STATE_DIR/tailscaled.sock \
--state=$STATE_DIR/tailscaled.state \
--statedir=$STATE_DIR \
--tun=userspace-networking \
--socks5-server=$LISTEN_ADDR
where:
STATE_DIRis some directory to hold persistent state files.LISTEN_ADDRis the address to serve a SOCKS5 proxy on.
This starts up a new tailscaled instance running in userspace mode.
It does not start up any kernal-level tunneling devices.
It communicates to the Tailscale control server as tailscaled usually does,
but all the networking logic occurs within the tailscaled process itself
(i.e., it processes IP packets manually and implements a TCP stack).
You will need to register this node with the "secondary" tailnet:
tailscale --socket=$STATE_DIR/tailscaled.sock up --authkey=$AUTH_KEY
In the example above, we have it listen to only the Tailscale network interface
of the "primary" tailnet. As a security precaution, you should lock down the ACLs
on the "primary" tailnet to only allow trusted devices to reach $LISTEN_ADDR.
Anything that can reach the proxy node can make requests into the
"secondary.ts.net" tailnet with the privileges of the proxy node.
To reduce the scope of what can use the SOCKS5 proxy, you can also listen on
localhost:1080 instead to only allow local connections.
The service setup might differ on other operating systems
(e.g., whether to use systemd or some other service manager),
but the flags passed to tailscaled should be identical as userspace networking
does not depend on any operating system specific functionality.
How to setup tailscaled as a persistent service on your given operating system
is an exercise for the reader.
Use of a SOCKS5 proxy differs for each operating system, but almost every system supports use of a "proxy auto-configuration" (PAC) script to dynamically dictate how to proxy each HTTP request.
An example PAC script is as follows:
function FindProxyForURL(url, host) {
if (shExpMatch(host, "*.secondary.ts.net")) {
return "SOCKS5 100.1.2.3:1080";
}
return "DIRECT";
}
This script proxies any request where the hostname matches "*.secondary.ts.net", otherwise it uses a direct connection. For proxied requests, it sends them to "100.1.2.3:1080", which is the node inside the "secondary" tailnet that hosts a SOCKS5 proxy. You may modify the script to have it support proxying requests for more tailnets.
Many operating systems only support a "proxy auto-configuration" (PAC) script
from a web URL. If you need a way to host the PAC file through a web server,
you can use python3 to start a light-weight server:
python3 -m http.server 8080
This starts a file server serving from the current working directory on port 8080. It is an exercise to the reader to make this a persistent service if necessary.
For the steps below, we assume that we have a PAC script loadable at:
http://localhost:8080/proxy.pac
If you are hosting the PAC file on a machine that is only reachable via Tailscale, then beware that HTTP connections may not work while Tailscale is down.
- Open the system "Settings"
- Go to "Network & Internet"
- Go to "Proxy"
- Enable "Use setup script"
- Specify the URL to reach the PAC script for the "Script address"
- Open the system "Settings"
- Go to "Network"
- Click on "VPN"
- Click on "Tailscale: Tailscale"
- Expand the "Proxy" section
- Select "Automatic proxy configuration" for "Connection type"
- Specify the URL to reach the PAC script for the "Autoconfiguration URL"
This setup in ChromeOS is unique in that the PAC is only used if Tailscale is running.
Thank you dsnet.
For those of you on OSX (tested on Ventura 13.5.2), to enable socks5 go to system preferences and search for proxies. Once there, click in
automatic proxy configurationand enter your URL.If you need to update your proxy URL, you can use the following script which will restart the network interfaces that have socks5 enabled: