| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- //go:build !android
- package netmon
- import (
- "bufio"
- "bytes"
- "errors"
- "fmt"
- "io"
- "log"
- "net"
- "net/netip"
- "os"
- "strings"
- "sync/atomic"
- "github.com/jsimonetti/rtnetlink"
- "github.com/mdlayher/netlink"
- "go4.org/mem"
- "golang.org/x/sys/unix"
- "tailscale.com/feature/buildfeatures"
- "tailscale.com/net/netaddr"
- "tailscale.com/util/lineiter"
- )
- func init() {
- likelyHomeRouterIP = likelyHomeRouterIPLinux
- }
- 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 likelyHomeRouterIPLinux() (ret netip.Addr, myIP netip.Addr, ok bool) {
- if !buildfeatures.HasPortMapper {
- return
- }
- if procNetRouteErr.Load() {
- // If we failed to read /proc/net/route previously, don't keep trying.
- return ret, myIP, false
- }
- lineNum := 0
- var f []mem.RO
- for lr := range lineiter.File(procNetRoutePath) {
- line, err := lr.Value()
- if err != nil {
- procNetRouteErr.Store(true)
- log.Printf("interfaces: failed to read /proc/net/route: %v", err)
- return ret, myIP, false
- }
- 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
- }
- func defaultRoute() (d DefaultRouteDetails, err error) {
- v, err := defaultRouteInterfaceProcNet()
- if err == nil {
- d.InterfaceName = v
- return d, nil
- }
- // Issue 4038: the default route (such as on Unifi UDM Pro)
- // might be in a non-default table, so it won't show up in
- // /proc/net/route. Use netlink to find the default route.
- //
- // TODO(bradfitz): this allocates a fair bit. We should track
- // this in net/interfaces/monitor instead and have
- // interfaces.GetState take a netmon.Monitor or similar so the
- // routing table can be cached and the monitor's existing
- // subscription to route changes can update the cached state,
- // rather than querying the whole thing every time like
- // defaultRouteFromNetlink does.
- //
- // Then we should just always try to use the cached route
- // table from netlink every time, and only use /proc/net/route
- // as a fallback for weird environments where netlink might be
- // banned but /proc/net/route is emulated (e.g. stuff like
- // Cloud Run?).
- return defaultRouteFromNetlink()
- }
- func defaultRouteFromNetlink() (d DefaultRouteDetails, err error) {
- c, err := rtnetlink.Dial(&netlink.Config{Strict: true})
- if err != nil {
- return d, fmt.Errorf("defaultRouteFromNetlink: Dial: %w", err)
- }
- defer c.Close()
- rms, err := c.Route.List()
- if err != nil {
- return d, fmt.Errorf("defaultRouteFromNetlink: List: %w", err)
- }
- for _, rm := range rms {
- if rm.Attributes.Gateway == nil {
- // A default route has a gateway. If it doesn't, skip it.
- continue
- }
- if rm.Attributes.Dst != nil {
- // A default route has a nil destination to mean anything
- // so ignore any route for a specific destination.
- // TODO(bradfitz): better heuristic?
- // empirically this seems like enough.
- continue
- }
- // TODO(bradfitz): care about address family, if
- // callers ever start caring about v4-vs-v6 default
- // route differences.
- idx := int(rm.Attributes.OutIface)
- if idx == 0 {
- continue
- }
- if iface, err := net.InterfaceByIndex(idx); err == nil {
- d.InterfaceName = iface.Name
- d.InterfaceIndex = idx
- return d, nil
- }
- }
- return d, errNoDefaultRoute
- }
- var zeroRouteBytes = []byte("00000000")
- 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
- var errNoDefaultRoute = errors.New("no default route found")
- func defaultRouteInterfaceProcNetInternal(bufsize int) (string, error) {
- f, err := os.Open(procNetRoutePath)
- if err != nil {
- return "", err
- }
- defer f.Close()
- br := bufio.NewReaderSize(f, bufsize)
- lineNum := 0
- for {
- lineNum++
- line, err := br.ReadSlice('\n')
- if err == io.EOF || lineNum > maxProcNetRouteRead {
- return "", errNoDefaultRoute
- }
- if err != nil {
- return "", err
- }
- if !bytes.Contains(line, zeroRouteBytes) {
- continue
- }
- fields := strings.Fields(string(line))
- ifc := fields[0]
- ip := fields[1]
- netmask := fields[7]
- if strings.HasPrefix(ifc, "tailscale") ||
- strings.HasPrefix(ifc, "wg") {
- continue
- }
- if ip == "00000000" && netmask == "00000000" {
- // default route
- return ifc, nil // interface name
- }
- }
- }
- // returns string interface name and an error.
- // io.EOF: full route table processed, no default route found.
- // other io error: something went wrong reading the route file.
- func defaultRouteInterfaceProcNet() (string, error) {
- rc, err := defaultRouteInterfaceProcNetInternal(128)
- if rc == "" && (errors.Is(err, io.EOF) || err == nil) {
- // https://github.com/google/gvisor/issues/5732
- // On a regular Linux kernel you can read the first 128 bytes of /proc/net/route,
- // then come back later to read the next 128 bytes and so on.
- //
- // In Google Cloud Run, where /proc/net/route comes from gVisor, you have to
- // read it all at once. If you read only the first few bytes then the second
- // read returns 0 bytes no matter how much originally appeared to be in the file.
- //
- // At the time of this writing (Mar 2021) Google Cloud Run has eth0 and eth1
- // with a 384 byte /proc/net/route. We allocate a large buffer to ensure we'll
- // read it all in one call.
- return defaultRouteInterfaceProcNetInternal(4096)
- }
- return rc, err
- }
|