For context, I’m running Tailscale on a Raspberry Pi which I start with:
tailscale up --advertise-routes=10.0.10.0/24,10.0.80.0/24,10.0.90.0/24 --advertise-exit-node --accept-dns=false
This Raspberry Pi also acts as a DNS server (I’m using AdGuard Home, similar to Pi-Hole) and has the 10.0.10.200 IP address. This is the IP address I’m using as the global nameserver:
When I connect to my Tailscale account (for instance, with my Android phone), all DNS requests go through my DNS server as expected. The problem I’m facing is that the identified source IP for the DNS request is not the one from the Android client, instead it’s the Raspberry Pi IP address itself. This is an issue for me because I have different rules for different clients that use my DNS server, which don’t work because all DNS requests connected to my Tailscale account are all identified with the same source IP address.
This is also problematic in a different scenario…
On the same Raspberry Pi I have a reverse proxy running (Traefik), which points specific hostnames to web services (i.e. webapp.example.com → http://10.0.10.50:8080). These hostnames are protected with Authelia. The 10.0.10.50 machine has firewall rules that only allows direct access to webapp running on port 8080 if the source IP is 10.0.10.200 (the reverse proxy, same as the DNS server, same as where Tailscale is running), this effectively forces one to open http://webapp.example.com, because opening http://10.0.10.50:8080 will be denied by the firewall.
This all works as expected when I’m NOT connected through Tailscale, the client source IP is identified correctly, and the firewall rules do their thing as expected. However, when connected through Tailscale, the source IP will be the exact one which the firewall rules allow, and this creates a loophole, allowing direct access to http://10.0.10.50:8080 when connected through Tailscale.
Run ‘tailscale up --help’ and look at the SNAT-related options. That’s what you want.
However… if you disable SNAT of incoming connections through the relay, then the other nodes in your network will need to have routes put in place to allow them to reply to the VPN clients.
I had tried --snat-subnet-routes=false before, but everything just stopped working. I suspect I need to add routes like @kpfleming mentioned above. But I don’t know how to do that, and I also think it you would make things more complex like you said.
As for using the Raspberry Pi 100.x.x.x address as the global nameserver, I also tried that but didn’t notice any difference. AdGuard Home logs still showed a single IP address for any connected VPN client. Not sure if I’m doing something wrong.
Something went wrong in that setup, if the Raspberry Pi that AdGuard runs on is running tailscaled it should see the unique Tailscale IP address for each client.
You’d need to add the Tailscale IP address 100.x.y.z of the Adguard server to Tailscale, not the 10.0.10.200 address.
OK. It is possible that the pi is doing some sort of NAT for the container, causing the source addresses to all look the same to AdGuard.
In which case, doing the opposite of what I just suggested, and running Tailscale in the container that is running AdGuard - or running AdGuard natively on the pi, rather than in a container will simplify the networking a great deal.
OK. It is possible that the pi is doing some sort of NAT for the container, causing the source addresses to all look the same to AdGuard.
And how do I figure that out? I have a feeling this was working before…
In which case, doing the opposite of what I just suggested, and running Tailscale in the container that is running AdGuard - or running AdGuard natively on the pi, rather than in a container will simplify the networking a great deal.
I’m not sure Tailscale can run inside Docker containers, and even if it did, it doesn’t look like the correct solution to me. Running AdGuard natively is also not an option for me. There must be something else I can do…
The original post had the DNS server address as 10.0.10.200, but the source IP in DNS requests was the Raspberry Pi’s address.
The DNS server address now is 100.78.45.65, the Tailscale IP address of the Raspberry Pi. What source IP address is seen by AdGuard in the DNS requests now?
Let’s assume AdGuard is running in a Docker container. The container will have an IP address like 172.17.0.2. Docker on the host will have created an interface for 172.17.0.1. It is usually named docker0
With Tailscale configured to distribute a DNS server address of 100.78.45.65, all of the Tailscale clients would be sending their DNS packets with a source IP address of their own 100.x.y.z address.
The working assumption is that something within the Raspberry Pi is performing NAT and rewriting source IPs before sending them to the AdGuard container.
I just set up my Pi (including Tailscale and AdGuard) from scratch, and how AdGuard correctly recognizes Tailscale subnet IPs correctly. And yes, I’m using the Tailscale IP as the DNS server in the Tailscale configuration. There was definitely some misconfiguration somewhere, not sure where, though. This issue is solved.
However, the other one, the one where I access services in a remote machine through my Traefik reverse proxy (also running on the Pi) where those services are behind a firewall that is meant to allow connections only where the source is the Pi, that one is still problematic.
For some reason, my Synology NAS (where the firewalled services are running) is not blocking access to those services as it’s supposed when connected through Tailscale. The firewall is working correctly when I’m connected directly to my home network. Here’s the firewall rules:
They are applied from top to bottom. If the source is 10.0.10.200 (the Pi), then let them through, otherwise deny. This for 20000-29999 ports only, which is the range of ports where all the services I want protected like this area listening in. Then I also allow everything else for 10.0.10.0/24, which is my home network main subnet.
And here’s a connection flow example:
Connect to Tailscale through my Android phone (with a mobile connection, to be outside my home network)
Open a SMB share on my Android phone (I use Solid Explorer app)
The SMB share host is machine-name.example.com
A DNS request for machine-name.example.com is made to AdGuard (running on the Pi) through the 100.x.x.x Tailscale IP
The 10.0.10.205 address is returned (my Synology NAS)
The SMB connection is established
Logs in the Synology show that the connection came from 10.0.10.200 (the Pi)
I don’t understand why the source IP is the Pi instead of a Tailscale IP, but this is the reason why the “deny” firewall rule is being ignored. Not sure if I’m doing something wrong with my configuration somewhere, but would love to get this working as well.
This is normal behavior for Tailscale subnet routers; they ‘source NAT’ (SNAT) the connections from remote endpoints so that those connections appear to be coming from the router itself. This is done so that the LAN where the router is running does not need to have routing table entries pointing back out through the router to the Tailscale remote endpoints.
If you don’t want this behavior you can disable it in the subnet router’s Tailscale client, but then you’ll need to configure the rest of your network to have a route for 100.x.x.x pointing to the Pi.