The reason appears to be related to the various things GL.iNet are doing in their own firewall rules, as far as I can tell. If we look on a device running V4.1, we see:
0: from all lookup local
51: from all fwmark 0x100000/0x100000 lookup 51
52: from all fwmark 0x80000/0x80000 lookup 52
53: from all fwmark 0x60000/0x60000 lookup 53
1001: from all iif eth0 lookup 1
2001: from all fwmark 0x100/0x3f00 lookup 1
2061: from all fwmark 0x3d00/0x3f00 blackhole
2062: from all fwmark 0x3e00/0x3f00 unreachable
32766: from all lookup main
32767: from all lookup default
where a stock device would show… well, nothing:
root@OpenWrt:~# ip rule
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
root@OpenWrt:~#
Tailscale normally inserts rules in table 52 with priorities in the 5200s, seen below from a custom MT1300 OpenWRT image running Tailscale:
0: from all lookup local
5210: from all fwmark 0x80000 lookup main
5230: from all fwmark 0x80000 lookup default
5250: from all fwmark 0x80000 lookup unspec unreachable
5270: from all lookup 52
32766: from all lookup main
32767: from all lookup default
The previous fix was intended to move the Tailscale rules in front of the mwan3 rules - which it does correctly on GLI devices. See below (the rules inserted in the 1300 range instead of 5200 range, as per the patch):
0: from all lookup local
51: from all fwmark 0x100000/0x100000 lookup 51
52: from all fwmark 0x80000/0x80000 lookup 52
53: from all fwmark 0x60000/0x60000 lookup 53
1001: from all iif eth0 lookup 1
1002: from all iif apclix0 lookup 2
1310: from all fwmark 0x80000/0xff0000 lookup main
1330: from all fwmark 0x80000/0xff0000 lookup default
1350: from all fwmark 0x80000/0xff0000 unreachable
1370: from all lookup 52
2001: from all fwmark 0x100/0x3f00 lookup 1
2002: from all fwmark 0x200/0x3f00 lookup 2
2061: from all fwmark 0x3d00/0x3f00 blackhole
2062: from all fwmark 0x3e00/0x3f00 unreachable
32766: from all lookup main
32767: from all lookup default
The problem appears to be that GLI’s firmware seems to be inserting stuff in tables 51 and 52 (and presumably 53, though that crashes):
root@GL-AXT1800:~# ip route show table 51
default via 192.168.20.1 dev eth0 proto static src 192.168.20.196
192.168.8.0/24 dev br-lan proto kernel scope link src 192.168.8.1
192.168.20.0/24 dev eth0 proto kernel scope link src 192.168.20.196
root@GL-AXT1800:~# ip route show table 52
default via 192.168.20.1 dev eth0 proto static src 192.168.20.196
192.168.8.0/24 dev br-lan proto kernel scope link src 192.168.8.1
192.168.20.0/24 dev eth0 proto kernel scope link src 192.168.20.196
root@GL-AXT1800:~# ip route show table 53
Dump terminated
(with 192.168.20.0 being the internal subnet the GLI router is on, and 192.168.20.196 being its ip address).
Tailscale tries to insert rules in table 52 - for exit nodes, specifically:
default dev tailscale0
throw 100.100.100.100 dev tailscale0
throw 127.0.0.0/8
throw 192.168.8.0/24
(for a router with a 192.168.8.0/24 lan where --exit-node-allow-lan-access has been enabled)
The problem, as near as I can tell, is that 1) GLI have inserted the table 51-53 lookup rules with priority 51-53, and 2) The fact that GLI have inserted the various rules into Table 51/52 mean that instead of having a clean table 52 like above, you also get something that looks like this:
default dev tailscale0
default via 192.168.20.1 dev eth0 proto static src 192.168.20.196 metric 20
throw 100.100.100.100 dev tailscale0
throw 127.0.0.0/8
192.168.8.0/24 dev br-lan proto kernel scope link src 192.168.8.1
throw 192.168.8.0/23
192.168.20.0/24 dev eth0 proto static scope link metric 20
throw 192.168.20.0/24
Basically the extra rules and table lookups break --exit-node in Tailscale. It would be great if GLI could issue some sort of fix for this, as their new routers (the AXT1800 in my case, and presumably the new MT3000 as well) are more than powerful enough to run this in a road-warrior setup, which is fantastic. Unfortunately for now the only solution I can see is building a stock luci build using the gl-infra-builder, which works great - but it would be nicer if it were possible to use with stock.
(honestly it would be nicer if GLI just integrated tailscale in, since it’s a) dead simple and b) wouldn’t require too much of a UI to make things work… but there are a lot of priorities and my guess is it’s pretty far down on the list, either way perhaps @alzhao can comment or bring to developers attention).