mirror of
https://github.com/tailscale/tailscale.git
synced 2026-06-03 21:01:54 +08:00
net/netmon: in Android, replace system/bin/ip call with cached LinkProperties gateway (#19804)
bind() on NETLINK_ROUTE sockets does not work on Android 11+ (https://developer.android.com/identity/user-data-ids#mac-11-plus) . Since system/bin/ip uses bind(), likelyHomeRouterIPHelper() always fails on Andoroid 11+, so that GatewayAndSelfIP never caches the result, causing repeated ip process spawns on every periodic ReSTUN. This replaces the system/bin/ip fallback with a cached gateway IP pushed from Android’s ConnectivityManager via LinkProperties.getRoutes(). This is the same patterm used by UpdateLastKnownDefaultRouteInterface for the interface name (see https://github.com/tailscale/tailscale/pull/11784/). We keep the proc/net/route path as a fallback for early startup before NetworkChangeCallback has fired. Updates tailscale/tailscale#18622 Updates tailscale/tailscale#13352 Signed-off-by: kari-ts <kari@tailscale.com>
This commit is contained in:
parent
c9fb05b6f5
commit
1a17ec1988
@ -4,10 +4,8 @@
|
||||
package netmon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
"sync/atomic"
|
||||
|
||||
"go4.org/mem"
|
||||
@ -19,6 +17,7 @@
|
||||
|
||||
var (
|
||||
lastKnownDefaultRouteIfName syncs.AtomicValue[string]
|
||||
lastKnownDefaultGateway syncs.AtomicValue[string]
|
||||
)
|
||||
|
||||
var procNetRoutePath = "/proc/net/route"
|
||||
@ -42,123 +41,99 @@ func init() {
|
||||
ens18 0000000A 00000000 0001 0 0 0 0000FFFF 0 0 0
|
||||
*/
|
||||
func likelyHomeRouterIPAndroid() (ret netip.Addr, myIP netip.Addr, ok bool) {
|
||||
if procNetRouteErr.Load() {
|
||||
// If we failed to read /proc/net/route previously, don't keep trying.
|
||||
return likelyHomeRouterIPHelper()
|
||||
}
|
||||
lineNum := 0
|
||||
var f []mem.RO
|
||||
for lr := range lineiter.File(procNetRoutePath) {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
procNetRouteErr.Store(true)
|
||||
return likelyHomeRouterIP()
|
||||
}
|
||||
|
||||
lineNum++
|
||||
if lineNum == 1 {
|
||||
// Skip header line.
|
||||
continue
|
||||
}
|
||||
if lineNum > maxProcNetRouteRead {
|
||||
break
|
||||
}
|
||||
f = mem.AppendFields(f[:0], mem.B(line))
|
||||
if len(f) < 4 {
|
||||
continue
|
||||
}
|
||||
gwHex, flagsHex := f[2], f[3]
|
||||
flags, err := mem.ParseUint(flagsHex, 16, 16)
|
||||
if err != nil {
|
||||
continue // ignore error, skip line and keep going
|
||||
}
|
||||
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
|
||||
continue
|
||||
}
|
||||
ipu32, err := mem.ParseUint(gwHex, 16, 32)
|
||||
if err != nil {
|
||||
continue // ignore error, skip line and keep going
|
||||
}
|
||||
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
|
||||
if ip.IsPrivate() {
|
||||
ret = ip
|
||||
break
|
||||
if gwStr := lastKnownDefaultGateway.Load(); gwStr != "" {
|
||||
if ip, err := netip.ParseAddr(gwStr); err == nil {
|
||||
return ip, netip.Addr{}, true
|
||||
}
|
||||
}
|
||||
if ret.IsValid() {
|
||||
// Try to get the local IP of the interface associated with
|
||||
// this route to short-circuit finding the IP associated with
|
||||
// this gateway. This isn't fatal if it fails.
|
||||
if len(f) > 0 && !disableLikelyHomeRouterIPSelf() {
|
||||
ForeachInterface(func(ni Interface, pfxs []netip.Prefix) {
|
||||
// Ensure this is the same interface
|
||||
if !f[0].EqualString(ni.Name) {
|
||||
return
|
||||
}
|
||||
// Fall back to /proc/net/route for early startup before
|
||||
// NetworkChangeCallback has fired, or if the cached gateway
|
||||
// was empty (e.g., cellular with no private gateway).
|
||||
if !procNetRouteErr.Load() {
|
||||
lineNum := 0
|
||||
var f []mem.RO
|
||||
for lr := range lineiter.File(procNetRoutePath) {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
procNetRouteErr.Store(true)
|
||||
return likelyHomeRouterIP()
|
||||
}
|
||||
|
||||
// Find the first IPv4 address and use it.
|
||||
for _, pfx := range pfxs {
|
||||
if addr := pfx.Addr(); addr.Is4() {
|
||||
myIP = addr
|
||||
break
|
||||
lineNum++
|
||||
if lineNum == 1 {
|
||||
// Skip header line.
|
||||
continue
|
||||
}
|
||||
if lineNum > maxProcNetRouteRead {
|
||||
break
|
||||
}
|
||||
f = mem.AppendFields(f[:0], mem.B(line))
|
||||
if len(f) < 4 {
|
||||
continue
|
||||
}
|
||||
gwHex, flagsHex := f[2], f[3]
|
||||
flags, err := mem.ParseUint(flagsHex, 16, 16)
|
||||
if err != nil {
|
||||
continue // ignore error, skip line and keep going
|
||||
}
|
||||
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
|
||||
continue
|
||||
}
|
||||
ipu32, err := mem.ParseUint(gwHex, 16, 32)
|
||||
if err != nil {
|
||||
continue // ignore error, skip line and keep going
|
||||
}
|
||||
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
|
||||
if ip.IsPrivate() {
|
||||
ret = ip
|
||||
break
|
||||
}
|
||||
}
|
||||
if ret.IsValid() {
|
||||
// Try to get the local IP of the interface associated with
|
||||
// this route to short-circuit finding the IP associated with
|
||||
// this gateway. This isn't fatal if it fails.
|
||||
if len(f) > 0 && !disableLikelyHomeRouterIPSelf() {
|
||||
ForeachInterface(func(ni Interface, pfxs []netip.Prefix) {
|
||||
// Ensure this is the same interface
|
||||
if !f[0].EqualString(ni.Name) {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return ret, myIP, true
|
||||
}
|
||||
if lineNum >= maxProcNetRouteRead {
|
||||
// If we went over our line limit without finding an answer, assume
|
||||
// we're a big fancy Linux router (or at least not a home system)
|
||||
// and set the error bit so we stop trying this in the future (and wasting CPU).
|
||||
// See https://github.com/tailscale/tailscale/issues/7621.
|
||||
//
|
||||
// Remember that "likelyHomeRouterIP" exists purely to find the port
|
||||
// mapping service (UPnP, PMP, PCP) often present on a home router. If we hit
|
||||
// the route (line) limit without finding an answer, we're unlikely to ever
|
||||
// find one in the future.
|
||||
procNetRouteErr.Store(true)
|
||||
// Find the first IPv4 address and use it.
|
||||
for _, pfx := range pfxs {
|
||||
if addr := pfx.Addr(); addr.Is4() {
|
||||
myIP = addr
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return ret, myIP, true
|
||||
}
|
||||
if lineNum >= maxProcNetRouteRead {
|
||||
// If we went over our line limit without finding an answer, assume
|
||||
// we're a big fancy Linux router (or at least not a home system)
|
||||
// and set the error bit so we stop trying this in the future (and wasting CPU).
|
||||
// See https://github.com/tailscale/tailscale/issues/7621.
|
||||
//
|
||||
// Remember that "likelyHomeRouterIP" exists purely to find the port
|
||||
// mapping service (UPnP, PMP, PCP) often present on a home router. If we hit
|
||||
// the route (line) limit without finding an answer, we're unlikely to ever
|
||||
// find one in the future.
|
||||
procNetRouteErr.Store(true)
|
||||
}
|
||||
}
|
||||
return netip.Addr{}, netip.Addr{}, false
|
||||
}
|
||||
|
||||
// Android apps don't have permission to read /proc/net/route, at
|
||||
// least on Google devices and the Android emulator.
|
||||
func likelyHomeRouterIPHelper() (ret netip.Addr, _ netip.Addr, ok bool) {
|
||||
cmd := exec.Command("/system/bin/ip", "route", "show", "table", "0")
|
||||
out, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return
|
||||
// UpdateLastKnownDefaultGateway is called by libtailscale in the Android app when
|
||||
// the connectivity manager provides an updated default gateway IP from LinkProperties.
|
||||
func UpdateLastKnownDefaultGateway(ipStr string) {
|
||||
if old := lastKnownDefaultGateway.Swap(ipStr); old != ipStr {
|
||||
log.Printf("defaultgateway: update from Android, gateway = %s (was %s)", ipStr, old)
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Printf("interfaces: running /system/bin/ip: %v", err)
|
||||
return
|
||||
}
|
||||
// Search for line like "default via 10.0.2.2 dev radio0 table 1016 proto static mtu 1500 "
|
||||
for lr := range lineiter.Reader(out) {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
const pfx = "default via "
|
||||
if !mem.HasPrefix(mem.B(line), mem.S(pfx)) {
|
||||
continue
|
||||
}
|
||||
line = line[len(pfx):]
|
||||
sp := bytes.IndexByte(line, ' ')
|
||||
if sp == -1 {
|
||||
continue
|
||||
}
|
||||
ipb := line[:sp]
|
||||
if ip, err := netip.ParseAddr(string(ipb)); err == nil && ip.Is4() {
|
||||
ret = ip
|
||||
log.Printf("interfaces: found Android default route %v", ip)
|
||||
}
|
||||
}
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
return ret, netip.Addr{}, ret.IsValid()
|
||||
}
|
||||
|
||||
// UpdateLastKnownDefaultRouteInterface is called by libtailscale in the Android app when
|
||||
|
||||
Loading…
Reference in New Issue
Block a user