[Bug] Exit node drops traffic to destinations in its directly connected external subnet
Is this a support request?
- This is not a support request
Is there an existing issue for this?
- I have searched the existing issues
Current Behavior
There are two nodes in a Headscale/Tailscale network:
- exitnode — node that advertises itself as an exit node. It is connected directly to the internet:
- Internal IP:
10.1.0.2/24 - External IP:
88.88.88.2/24 - External subnet:
88.88.88.0/24
- clientnode — client that uses exitnode as exit node:
- Internal IP:
10.2.0.2/24
In such a configuration, clientnode accesses the Internet using exitnode's external IP address, 88.88.88.2, as the source address, and general Internet access works as expected. For example, access to external addresses such as 8.8.8.8 works.
However, clientnode cannot access other addresses from the same external subnet, 88.88.88.0/24, that is directly connected to exitnode.
Before enabling exitnode as an exit node, ICMP ping from clientnode works to both publicly routable Internet addresses, 88.88.88.3 and 8.8.8.8:
clientnode$ ping -c1 88.88.88.3
# OK
clientnode$ ping -c1 8.8.8.8
# OKBut after we enable exitnode as the exit node:
clientnode$ tailscale up --exit-node=exitnode --accept-routes --login-server=https://vpn.example.compinging 88.88.88.3 is not working anymore:
clientnode$ ping -c1 88.88.88.3
# FAIL
clientnode$ ping -c1 8.8.8.8
# Still OKThe problem appears only for destinations that belong to the external subnet directly connected to the exit node itself, in this example: 88.88.88.0/24.
Expected Behavior
When clientnode uses exitnode as an exit node, it should be able to reach any publicly routable Internet address through that exit node, including addresses from the external subnet directly connected to exitnode.
In this example, ping from clientnode to both 8.8.8.8 and 88.88.88.3 should work.
Note: we do not want exitnode to announce 88.88.88.0/24 as a subnet route to all clients. Clients that do not use exitnode as an exit node should continue reaching 88.88.88.3 through their normal Internet route, with their own public source IP address visible on the Internet.
Steps To Reproduce
Connect to two nodes to a Headscale/Tailscale network:
exitnode
- advertises itself as an exit node (and is allowed to be used as exitnode in ACL)
- has an external interface with IP
88.88.88.2/24 - the directly connected external subnet is
88.88.88.0/24
clientnode
- uses exitnode as its exit node
From clientnode, verify that both public IP addresses are reachable before enabling the exit node:
ping -c1 88.88.88.3
ping -c1 8.8.8.8Both pings should succeed.
- Enable exitnode as the exit node on clientnode:
tailscale up \
--exit-node=exitnode \
--accept-routes \
--login-server=https://vpn.example.com- From clientnode, ping a normal Internet address:
ping -c1 8.8.8.8This still works.
- From clientnode, ping another public IP address from the external subnet directly connected to exitnode:
ping -c1 88.88.88.3Observe that the ping to 88.88.88.3 fails, even though general Internet access through the exit node still works.
Environment
- OS: Gentoo Linux
- Headscale version: headscale version v0.27.2-rc.1+dirty
commit: c6d399a66ca9ec94bb84e78e8edf9a31981f554c
- Tailscale version: 1.96.5Runtime environment
- Headscale is behind a (reverse) proxy
- Headscale runs in a container
Debug information
We found a workaround: advertise 88.88.88.0/24 on exitnode, but do not approve this route in Headscale.
This prevents the route from being distributed to clients, but it makes the local tailscaled on exitnode allow traffic from clientnode to destinations inside 88.88.88.0/24.
Still, this feels more like a workaround than a proper fix.
The current logic seems intended to exclude LAN/private networks from automatic exit-node access. But in this case the excluded subnet is a publicly routable Internet subnet directly connected to the exit node, not a private LAN.
Maybe publicly routable directly connected networks should not be treated the same way as LAN/private networks when shrinking the default route for an exit node.
https://github.com/juanfont/headscale/issues/1637#issuecomment-4439510460
Source: juanfont/headscale