Tailscale exit node, ProtonVPN and DNS

Hi all,

I’ve set up a lightweight VM with OpenWRT on Proxmox. A wireguard interface connected successfully to ProtonVPN as a client. Installed tailscale with advertising exit node. Both the wireguard is in their respective firewall zones, both are masqueraded. With the Policy-based routing package I’ve successfully managed to get the tailscale interface to use the wireguard interface as gateway. I also set in pbr a DNS rule that tailscale IPs should use Proton’s 10.2.0.1 DNS

Connecting to the exit node from another tailnet partially works, the client’s IP is a proton IP. But, the DNS resolvers it sees are the ones OpenWRT gets from the Proxmox host. I’ve tried setting proton’s DNS as custom DNS on both the wireguard and tailscale interfaces, but it changed nothing.

I only want exit node users to use Proton’s DNS, the OpenWRT itself should continue using the resolvers Proxmox provides. How to achieve this?

The IP 10.2.0.1 is a private range IP it cannot be reached by the router (and its apps) itself directly because of this and the fact that the IP can only be reached through wireguard interface. So a PBR DNS policy rule does not work here. Because PBR does not know howto reach the private IP and the DNS policy rule does not allow to add an interface.

In theory you could use a simple policy rule within PBR instead to achive this:

src_addr (one client or subnet)
dest_port 53
Interface wgvpn

But then you have to make the clients aware of the DNS server. Which does not make much sense if you have more then one or two clients. If you have more then you would use DNSMasq instance anyway.

So you would go this route (as I have it):

  • setup a new dnsmasq instance and give it as dns server the ProtonDNS (e. g. 10.2.0.1@vpn) and assign it to a zone (e. g. lan) to make it reachable via an IP the router knows.
  • create a firewall rule to intercept any DNS traffic comming from the zones you have in mind and force it to the new DNS server IP.

In this way you can even add an adblocker in between to filter out crap as I do.

EDIT: As a third option you could deal with routes and firewall rules directly also. Its not so difficult but you have to be aware that they exist if you use mainly a gui to manage the device.

Thanks for the reply! However, something’s still not right. The exit node client gets the proton IP, but the DNS resolvers are the ones coming through proxmox (and my main LAN).
I couldn’t set the listen port for the second dnsmasq instance to 53, even if I limited the first to only the LAN interface and the second to tailscale. Still complained that port already in use. Configs:
dhcp:

config dnsmasq
option domainneeded '1'
option localise_queries '1'
option rebind_protection '1'
option rebind_localhost '1'
option local '/lan/'
option domain 'lan'
option expandhosts '1'
option cachesize '1000'
option authoritative '1'
option readethers '1'
option leasefile '/tmp/dhcp.leases'
option localservice '1'
option ednspacket_max '1232'
list server '192.168.20.30#53'
list interface 'lan'
list notinterface 'tailscale'

config dhcp 'lan'
option interface 'lan'
option start '100'
option limit '150'
option leasetime '12h'
option dhcpv4 'server'
option dhcpv6 'server'
option ra 'server'
option ra_slaac '1'
list ra_flags 'managed-config'
list ra_flags 'other-config'

config dhcp 'wan'
option interface 'wan'
option ignore '1'

config odhcpd 'odhcpd'
option maindhcp '0'
option leasefile '/tmp/hosts/odhcpd'
option leasetrigger '/usr/sbin/odhcpd-update'
option loglevel '4'
option piofolder '/tmp/odhcpd-piofolder'

config dnsmasq 'tailscale'
option rebind_protection '0'
option localservice '0'
list interface 'tailscale'
option port '54'
list server '10.2.0.1'
option noresolv '1'


firewall:

config defaults
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
option synflood_protect '1'

config zone
option name 'lan'
list network 'lan'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'ACCEPT'

config zone
option name 'wan'
list network 'wan'
list network 'wan6'
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
option masq '1'
option mtu_fix '1'

config forwarding
option src 'lan'
option dest 'wan'

config rule
option name 'Allow-DHCP-Renew'
option src 'wan'
option proto 'udp'
option dest_port '68'
option target 'ACCEPT'
option family 'ipv4'

config rule
option name 'Allow-Ping'
option src 'wan'
option proto 'icmp'
option icmp_type 'echo-request'
option family 'ipv4'
option target 'ACCEPT'

config rule
option name 'Allow-IGMP'
option src 'wan'
option proto 'igmp'
option family 'ipv4'
option target 'ACCEPT'

config rule
option name 'Allow-DHCPv6'
option src 'wan'
option proto 'udp'
option dest_port '546'
option family 'ipv6'
option target 'ACCEPT'

config rule
option name 'Allow-MLD'
option src 'wan'
option proto 'icmp'
option src_ip 'fe80::/10'
list icmp_type '130/0'
list icmp_type '131/0'
list icmp_type '132/0'
list icmp_type '143/0'
option family 'ipv6'
option target 'ACCEPT'

config rule
option name 'Allow-ICMPv6-Input'
option src 'wan'
option proto 'icmp'
list icmp_type 'echo-request'
list icmp_type 'echo-reply'
list icmp_type 'destination-unreachable'
list icmp_type 'packet-too-big'
list icmp_type 'time-exceeded'
list icmp_type 'bad-header'
list icmp_type 'unknown-header-type'
list icmp_type 'router-solicitation'
list icmp_type 'neighbour-solicitation'
list icmp_type 'router-advertisement'
list icmp_type 'neighbour-advertisement'
option limit '1000/sec'
option family 'ipv6'
option target 'ACCEPT'

config rule
option name 'Allow-ICMPv6-Forward'
option src 'wan'
option dest '*'
option proto 'icmp'
list icmp_type 'echo-request'
list icmp_type 'echo-reply'
list icmp_type 'destination-unreachable'
list icmp_type 'packet-too-big'
list icmp_type 'time-exceeded'
list icmp_type 'bad-header'
list icmp_type 'unknown-header-type'
option limit '1000/sec'
option family 'ipv6'
option target 'ACCEPT'

config rule
option name 'Allow-IPSec-ESP'
option src 'wan'
option dest 'lan'
option proto 'esp'
option target 'ACCEPT'

config rule
option name 'Allow-ISAKMP'
option src 'wan'
option dest 'lan'
option dest_port '500'
option proto 'udp'
option target 'ACCEPT'

config rule
option target 'ACCEPT'
option src 'wan'
option proto 'tcp'
option dest_port '80'
option name 'Allow-Luci-WAN'

config rule
option target 'ACCEPT'
option src 'wan'
option proto 'tcp'
option dest_port '22'
option name 'Allow-SSH-WAN'

config zone
option name 'wireguard'
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
list network 'proton'
option masq '1'

config zone
option name 'tailscale'
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
list network 'tailscale'
option masq '1'

config forwarding
option src 'tailscale'
option dest 'wireguard'

config forwarding
option src 'tailscale'
option dest 'lan'

config rule
option src 'tailscale'
option name 'Allow Tailscale DNS'
option dest_port '54'
option target 'ACCEPT'
list proto 'tcp'
list proto 'udp'
option dest 'lan'

config redirect
option dest 'lan'
option target 'DNAT'
option name 'intercept tailscale dns'
option src 'tailscale'
option src_dport '53'
option dest_port '54'

network:

config interface 'loopback'
option device 'lo'
option proto 'static'
option ipaddr '127.0.0.1'
option netmask '255.0.0.0'

config globals 'globals'
option ula_prefix 'fd70:6a31:893b::/48'
option packet_steering '1'

config interface 'wan'
option device 'eth1'
option proto 'dhcp'
option peerdns '0'
list dns '192.168.20.30'

config interface 'lan'
option device 'eth0'
option proto 'static'
option ipaddr '192.168.25.1'
option netmask '255.255.255.0'

config interface 'proton'
option proto 'wireguard'
option private_key 'xxx'
list addresses '10.2.0.2/32'
list dns '10.2.0.1'

config wireguard_proton
option description 'protonpeer'
option public_key 'xxx'
list allowed_ips '0.0.0.0/0'
option endpoint_host '<proton-ip>'
option endpoint_port '51820'
option persistent_keepalive '25'

config interface 'tailscale'
option proto 'none'
option device 'tailscale0'

pbr:

config pbr 'config'
option enabled '1'
option verbosity '2'
option strict_enforcement '1'
option resolver_set 'none'
list resolver_instance '*'
option ipv6_enabled '0'
list ignored_interface 'vpnserver'
option rule_create_option 'add'
option procd_boot_trigger_delay '5000'
option procd_reload_delay '1'
option webui_show_ignore_target '0'
option nft_rule_counter '0'
option nft_set_auto_merge '1'
option nft_set_counter '0'
option nft_set_flags_interval '1'
option nft_set_flags_timeout '0'
option nft_set_policy 'performance'
list webui_supported_protocol 'all'
list webui_supported_protocol 'tcp'
list webui_supported_protocol 'udp'
list webui_supported_protocol 'tcp udp'
list webui_supported_protocol 'icmp'

config include
option path '/usr/share/pbr/pbr.user.dnsprefetch'
option enabled '0'

config include
option path '/usr/share/pbr/pbr.user.aws'
option enabled '0'

config include
option path '/usr/share/pbr/pbr.user.netflix'
option enabled '0'

config policy
option name 'proton-tailscale'
option src_addr '100.64.0.0/16'
option interface 'proton'

Well, I told you the basic about PBR. But your setup is huge/complex and hard to diagnose. Esp. the fact OpenWrt is running on Proxmox. This alone can be configured in several ways including some mayor errors like having just one NIC (which is error-prone configuring it right) or wrong wiring. Tailscale is another layer of complexity to this. I cannot diagnose this investing a huge amount of time here. This is certainly not a common household setup and more like a SB setup and out of scope and prop. the reason why nobody was answering to this topic at all. ;D

I don't know much about tailscale and its pitfalls/setup options. But it makes sense that in first place the clients are getting the LAN's DNS.

A short view to your config:

"Allow Tailscale DNS" and the following redirect rule do not make sense to me. 1st: What is listening on port 54 within your lan? 2nd: If a lan client sends a DNS request on port 53 and you want it redirect to port 54 you want to have an dest. IP too (so your 10.2.0.1). As I wrote already the router does not know howto reach this IP. So rediret to the 2nd DNS server IP having 10.2.0.1@device_vpn. As tailscale is a route into your LAN and you want to force certain clients to use specific DNS you have to force those clients to do so modifying your DNAT rule with clients IP's/MAC you want to steer (this is cumbersome). OpenWrt does not support IP range-based split DNS (AFAIK). So if you want easier setup you have to leverage ipset and use it as SRC for the firewall redirect rule.

Your DNS server needs the vpn device in addition 10.2.0.1@device_vpn because of the address. I'm not sure if tailscale device should be full masq'ed resp. if tailscale is doing those settings. Because I would think it should be like in=masq_off, out=masq_on. But I don't know tailscale.

1 Like