@@ -482,7 +482,7 @@ func (r *linuxRouter) Set(cfg *router.Config) error {
482482
483483 // Issue 11405: enable IP forwarding on gokrazy.
484484 advertisingRoutes := len (cfg .SubnetRoutes ) > 0
485- if getDistroFunc () == distro .Gokrazy && advertisingRoutes {
485+ if getDistroFunc () == distro .Gokrazy || runtime . GOOS == "android" && advertisingRoutes {
486486 r .enableIPForwarding ()
487487 }
488488
@@ -1297,24 +1297,60 @@ var ubntIPRules = []netlink.Rule{
12971297 },
12981298}
12991299
1300- var androidIPRules = []netlink.Rule {
1301- // Priority 7300 (12500): Tailscale CGNAT range (100.64.0.0/10) always uses table 52, before VPN rules
1302- // This ensures peer-to-peer traffic doesn't go through other VPNs
1303- {
1304- Priority : 7300 , // 5200 + 7300 = 12500
1305- Dst : netipx .PrefixIPNet (netip .MustParsePrefix ("100.64.0.0/10" )),
1306- Table : tailscaleRouteTable .Num ,
1307- },
1308- // Priority 13001: after Android VPN rules at 13000, before default network (14999+)
1309- // When VPN active: VPN rules at 13000 catch traffic first (VPN wins)
1310- // When VPN off: Tailscale catches traffic as fallback
1311- {
1312- Priority : 7801 , // 5200 + 7801 = 13001
1313- Invert : true ,
1314- Mark : tsconst .LinuxBypassMarkNum ,
1315- Mask : tsconst .LinuxFwmarkMaskNum ,
1316- Table : tailscaleRouteTable .Num ,
1317- },
1300+ // detectAndroidDefaultTable returns the routing table number for the current
1301+ // default route on Android. Returns 0 if detection fails.
1302+ func detectAndroidDefaultTable () int {
1303+ routes , err := netlink .RouteGet (net .IPv4 (8 , 8 , 8 , 8 ))
1304+ if err != nil || len (routes ) == 0 {
1305+ return 0
1306+ }
1307+ if routes [0 ].Table > 0 {
1308+ return routes [0 ].Table
1309+ }
1310+ return 0
1311+ }
1312+
1313+ // getAndroidIPRules returns Android-specific IP rules, including a dynamic
1314+ // exit node rule that uses the current default network's routing table.
1315+ func getAndroidIPRules () []netlink.Rule {
1316+ rules := []netlink.Rule {
1317+ // Priority 7300 (12500): Tailscale CGNAT range (100.64.0.0/10) always uses table 52, before VPN rules
1318+ // This ensures peer-to-peer traffic doesn't go through other VPNs
1319+ {
1320+ Priority : 7300 , // 5200 + 7300 = 12500
1321+ Dst : netipx .PrefixIPNet (netip .MustParsePrefix ("100.64.0.0/10" )),
1322+ Table : tailscaleRouteTable .Num ,
1323+ },
1324+ {
1325+ Priority : 7300 , // 5200 + 7300 = 12500
1326+ Dst : netipx .PrefixIPNet (netip .MustParsePrefix ("fd7a:115c:a1e0::/48" )),
1327+ Table : tailscaleRouteTable .Num ,
1328+ },
1329+ // Priority 13001: after Android VPN rules at 13000, before default network (14999+)
1330+ // When VPN active: VPN rules at 13000 catch traffic first (VPN wins)
1331+ // When VPN off: Tailscale catches traffic as fallback
1332+ {
1333+ Priority : 7801 , // 5200 + 7801 = 13001
1334+ Invert : true ,
1335+ Mark : tsconst .LinuxBypassMarkNum ,
1336+ Mask : tsconst .LinuxFwmarkMaskNum ,
1337+ Table : tailscaleRouteTable .Num ,
1338+ },
1339+ }
1340+
1341+ // Add exit node rule: route traffic FROM Tailscale network to internet
1342+ // using the current default network's routing table
1343+ if table := detectAndroidDefaultTable (); table > 0 {
1344+ // Traffic from tailscale0 is marked with LinuxSubnetRouteMark in ts-forward
1345+ rules = append (rules , netlink.Rule {
1346+ Priority : 7801 , // 5200 + 7801 = 13001
1347+ Mark : tsconst .LinuxSubnetRouteMarkNum , // 0x8000000
1348+ Mask : tsconst .LinuxFwmarkMaskNum ,
1349+ Table : table ,
1350+ })
1351+ }
1352+
1353+ return rules
13181354}
13191355
13201356// ipRules returns the appropriate list of ip rules to be used by Tailscale. See
@@ -1323,7 +1359,7 @@ func ipRules() []netlink.Rule {
13231359 if getDistroFunc () == distro .UBNT {
13241360 return ubntIPRules
13251361 } else if runtime .GOOS == "android" {
1326- return androidIPRules
1362+ return getAndroidIPRules ()
13271363 }
13281364 return baseIPRules
13291365}
0 commit comments