| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package netmon
- import (
- "bytes"
- "log"
- "net/netip"
- "os/exec"
- "sync/atomic"
- "go4.org/mem"
- "golang.org/x/sys/unix"
- "tailscale.com/net/netaddr"
- "tailscale.com/syncs"
- "tailscale.com/util/lineiter"
- )
- var (
- lastKnownDefaultRouteIfName syncs.AtomicValue[string]
- )
- var procNetRoutePath = "/proc/net/route"
- // maxProcNetRouteRead is the max number of lines to read from
- // /proc/net/route looking for a default route.
- const maxProcNetRouteRead = 1000
- func init() {
- likelyHomeRouterIP = likelyHomeRouterIPAndroid
- }
- var procNetRouteErr atomic.Bool
- /*
- Parse 10.0.0.1 out of:
- $ cat /proc/net/route
- Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
- ens18 00000000 0100000A 0003 0 0 0 00000000 0 0 0
- 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 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
- }
- // 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
- }
- 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
- // the connectivity manager detects a network path transition. If ifName is "", network has been lost.
- // After updating the interface, Android calls Monitor.InjectEvent(), triggering a link change.
- func UpdateLastKnownDefaultRouteInterface(ifName string) {
- if old := lastKnownDefaultRouteIfName.Swap(ifName); old != ifName {
- log.Printf("defaultroute: update from Android, ifName = %s (was %s)", ifName, old)
- }
- }
- func defaultRoute() (d DefaultRouteDetails, err error) {
- if ifName := lastKnownDefaultRouteIfName.Load(); ifName != "" {
- d.InterfaceName = ifName
- }
- return d, nil
- }
|