summaryrefslogtreecommitdiffhomepage
path: root/net/interfaces
diff options
context:
space:
mode:
authorNaman Sood <mail@nsood.in>2021-03-29 14:28:08 -0400
committerNaman Sood <mail@nsood.in>2021-03-29 14:28:08 -0400
commitc0a88a0129ebf0f9886b93b1f4e4f04a7c3bb86f (patch)
tree57d5aef2985e3424e5bb6f4c810628aa3ccbf5d0 /net/interfaces
parent47bd3c4cf5543fd7ecb049302c37c1001fa9f2d6 (diff)
parenta4c679e64691a3f0ba41ad9078312ca67e5e67fd (diff)
downloadtailscale-naman/netstack-subnet-routing.tar.xz
tailscale-naman/netstack-subnet-routing.zip
Signed-off-by: Naman Sood <mail@nsood.in>
Diffstat (limited to 'net/interfaces')
-rw-r--r--net/interfaces/interfaces.go117
-rw-r--r--net/interfaces/interfaces_test.go12
-rw-r--r--net/interfaces/interfaces_windows.go108
3 files changed, 145 insertions, 92 deletions
diff --git a/net/interfaces/interfaces.go b/net/interfaces/interfaces.go
index 3a0ffeb0b..f2988af34 100644
--- a/net/interfaces/interfaces.go
+++ b/net/interfaces/interfaces.go
@@ -6,10 +6,10 @@
package interfaces
import (
+ "bytes"
"fmt"
"net"
"net/http"
- "reflect"
"runtime"
"sort"
"strings"
@@ -190,6 +190,9 @@ func ForeachInterface(fn func(Interface, []netaddr.IPPrefix)) error {
}
}
}
+ sort.Slice(pfxs, func(i, j int) bool {
+ return pfxs[i].IP.Less(pfxs[j].IP)
+ })
fn(Interface{iface}, pfxs)
}
return nil
@@ -204,7 +207,7 @@ type State struct {
// IPPrefix, where the IP is the interface IP address and Bits is
// the subnet mask.
InterfaceIPs map[string][]netaddr.IPPrefix
- InterfaceUp map[string]bool
+ Interface map[string]Interface
// HaveV6Global is whether this machine has an IPv6 global address
// on some non-Tailscale interface that's up.
@@ -235,14 +238,14 @@ type State struct {
func (s *State) String() string {
var sb strings.Builder
fmt.Fprintf(&sb, "interfaces.State{defaultRoute=%v ifs={", s.DefaultRouteInterface)
- ifs := make([]string, 0, len(s.InterfaceUp))
- for k := range s.InterfaceUp {
+ ifs := make([]string, 0, len(s.Interface))
+ for k := range s.Interface {
if anyInterestingIP(s.InterfaceIPs[k]) {
ifs = append(ifs, k)
}
}
sort.Slice(ifs, func(i, j int) bool {
- upi, upj := s.InterfaceUp[ifs[i]], s.InterfaceUp[ifs[j]]
+ upi, upj := s.Interface[ifs[i]].IsUp(), s.Interface[ifs[j]].IsUp()
if upi != upj {
// Up sorts before down.
return upi
@@ -253,7 +256,7 @@ func (s *State) String() string {
if i > 0 {
sb.WriteString(" ")
}
- if s.InterfaceUp[ifName] {
+ if s.Interface[ifName].IsUp() {
fmt.Fprintf(&sb, "%s:[", ifName)
needSpace := false
for _, pfx := range s.InterfaceIPs[ifName] {
@@ -286,50 +289,76 @@ func (s *State) String() string {
return sb.String()
}
-func (s *State) Equal(s2 *State) bool {
- return reflect.DeepEqual(s, s2)
-}
-
-func (s *State) HasPAC() bool { return s != nil && s.PAC != "" }
-
-// AnyInterfaceUp reports whether any interface seems like it has Internet access.
-func (s *State) AnyInterfaceUp() bool {
- return s != nil && (s.HaveV4 || s.HaveV6Global)
-}
-
-// RemoveUninterestingInterfacesAndAddresses removes uninteresting IPs
-// from InterfaceIPs, also removing from both the InterfaceIPs and
-// InterfaceUp map any interfaces that don't have any interesting IPs.
-func (s *State) RemoveUninterestingInterfacesAndAddresses() {
- for ifName := range s.InterfaceUp {
- ips := s.InterfaceIPs[ifName]
- keep := ips[:0]
- for _, pfx := range ips {
- if isInterestingIP(pfx.IP) {
- keep = append(keep, pfx)
- }
- }
- if len(keep) == 0 {
- delete(s.InterfaceUp, ifName)
- delete(s.InterfaceIPs, ifName)
+// EqualFiltered reports whether s and s2 are equal,
+// considering only interfaces in s for which filter returns true.
+func (s *State) EqualFiltered(s2 *State, filter func(i Interface, ips []netaddr.IPPrefix) bool) bool {
+ if s == nil && s2 == nil {
+ return true
+ }
+ if s == nil || s2 == nil {
+ return false
+ }
+ if s.HaveV6Global != s2.HaveV6Global ||
+ s.HaveV4 != s2.HaveV4 ||
+ s.IsExpensive != s2.IsExpensive ||
+ s.DefaultRouteInterface != s2.DefaultRouteInterface ||
+ s.HTTPProxy != s2.HTTPProxy ||
+ s.PAC != s2.PAC {
+ return false
+ }
+ for iname, i := range s.Interface {
+ ips := s.InterfaceIPs[iname]
+ if !filter(i, ips) {
continue
}
- if len(keep) < len(ips) {
- s.InterfaceIPs[ifName] = keep
+ i2, ok := s2.Interface[iname]
+ if !ok {
+ return false
+ }
+ ips2, ok := s2.InterfaceIPs[iname]
+ if !ok {
+ return false
+ }
+ if !interfacesEqual(i, i2) || !prefixesEqual(ips, ips2) {
+ return false
}
}
+ return true
+}
+
+func interfacesEqual(a, b Interface) bool {
+ return a.Index == b.Index &&
+ a.MTU == b.MTU &&
+ a.Name == b.Name &&
+ a.Flags == b.Flags &&
+ bytes.Equal([]byte(a.HardwareAddr), []byte(b.HardwareAddr))
}
-// RemoveTailscaleInterfaces modifes s to remove any interfaces that
-// are owned by this process. (TODO: make this true; currently it
-// uses some heuristics)
-func (s *State) RemoveTailscaleInterfaces() {
- for name, pfxs := range s.InterfaceIPs {
- if isTailscaleInterface(name, pfxs) {
- delete(s.InterfaceIPs, name)
- delete(s.InterfaceUp, name)
+func prefixesEqual(a, b []netaddr.IPPrefix) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i, v := range a {
+ if b[i] != v {
+ return false
}
}
+ return true
+}
+
+// FilterInteresting reports whether i is an interesting non-Tailscale interface.
+func FilterInteresting(i Interface, ips []netaddr.IPPrefix) bool {
+ return !isTailscaleInterface(i.Name, ips) && anyInterestingIP(ips)
+}
+
+// FilterAll always returns true, to use EqualFiltered against all interfaces.
+func FilterAll(i Interface, ips []netaddr.IPPrefix) bool { return true }
+
+func (s *State) HasPAC() bool { return s != nil && s.PAC != "" }
+
+// AnyInterfaceUp reports whether any interface seems like it has Internet access.
+func (s *State) AnyInterfaceUp() bool {
+ return s != nil && (s.HaveV4 || s.HaveV6Global)
}
func hasTailscaleIP(pfxs []netaddr.IPPrefix) bool {
@@ -364,11 +393,11 @@ var getPAC func() string
func GetState() (*State, error) {
s := &State{
InterfaceIPs: make(map[string][]netaddr.IPPrefix),
- InterfaceUp: make(map[string]bool),
+ Interface: make(map[string]Interface),
}
if err := ForeachInterface(func(ni Interface, pfxs []netaddr.IPPrefix) {
ifUp := ni.IsUp()
- s.InterfaceUp[ni.Name] = ifUp
+ s.Interface[ni.Name] = ni
s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], pfxs...)
if !ifUp || isTailscaleInterface(ni.Name, pfxs) {
return
diff --git a/net/interfaces/interfaces_test.go b/net/interfaces/interfaces_test.go
index 88948b579..ab0ee3734 100644
--- a/net/interfaces/interfaces_test.go
+++ b/net/interfaces/interfaces_test.go
@@ -5,6 +5,7 @@
package interfaces
import (
+ "encoding/json"
"testing"
)
@@ -13,7 +14,11 @@ func TestGetState(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- t.Logf("Got: %#v", st)
+ j, err := json.MarshalIndent(st, "", "\t")
+ if err != nil {
+ t.Errorf("JSON: %v", err)
+ }
+ t.Logf("Got: %s", j)
t.Logf("As string: %s", st)
st2, err := GetState()
@@ -21,14 +26,13 @@ func TestGetState(t *testing.T) {
t.Fatal(err)
}
- if !st.Equal(st2) {
+ if !st.EqualFiltered(st2, FilterAll) {
// let's assume nobody was changing the system network interfaces between
// the two GetState calls.
t.Fatal("two States back-to-back were not equal")
}
- st.RemoveTailscaleInterfaces()
- t.Logf("As string without Tailscale:\n\t%s", st)
+ t.Logf("As string:\n\t%s", st)
}
func TestLikelyHomeRouterIP(t *testing.T) {
diff --git a/net/interfaces/interfaces_windows.go b/net/interfaces/interfaces_windows.go
index 19e9b48b4..91f679c97 100644
--- a/net/interfaces/interfaces_windows.go
+++ b/net/interfaces/interfaces_windows.go
@@ -7,17 +7,19 @@ package interfaces
import (
"fmt"
"log"
+ "net"
"net/url"
- "os/exec"
"syscall"
"unsafe"
- "go4.org/mem"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"inet.af/netaddr"
"tailscale.com/tsconst"
- "tailscale.com/util/lineread"
+)
+
+const (
+ fallbackInterfaceMetric = uint32(0) // Used if we cannot get the actual interface metric
)
func init() {
@@ -25,58 +27,76 @@ func init() {
getPAC = getPACWindows
}
-/*
-Parse out 10.0.0.1 from:
-
-Z:\>route print -4
-===========================================================================
-Interface List
- 15...aa 15 48 ff 1c 72 ......Red Hat VirtIO Ethernet Adapter
- 5...........................Tailscale Tunnel
- 1...........................Software Loopback Interface 1
-===========================================================================
-
-IPv4 Route Table
-===========================================================================
-Active Routes:
-Network Destination Netmask Gateway Interface Metric
- 0.0.0.0 0.0.0.0 10.0.0.1 10.0.28.63 5
- 10.0.0.0 255.255.0.0 On-link 10.0.28.63 261
- 10.0.28.63 255.255.255.255 On-link 10.0.28.63 261
- 10.0.42.0 255.255.255.0 100.103.42.106 100.103.42.106 5
- 10.0.255.255 255.255.255.255 On-link 10.0.28.63 261
- 34.193.248.174 255.255.255.255 100.103.42.106 100.103.42.106 5
-
-*/
func likelyHomeRouterIPWindows() (ret netaddr.IP, ok bool) {
- cmd := exec.Command("route", "print", "-4")
- cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
- stdout, err := cmd.StdoutPipe()
+ rs, err := winipcfg.GetIPForwardTable2(windows.AF_INET)
if err != nil {
+ log.Printf("routerIP/GetIPForwardTable2 error: %v", err)
return
}
- if err := cmd.Start(); err != nil {
+
+ var ifaceMetricCache map[winipcfg.LUID]uint32
+
+ getIfaceMetric := func(luid winipcfg.LUID) (metric uint32) {
+ if ifaceMetricCache == nil {
+ ifaceMetricCache = make(map[winipcfg.LUID]uint32)
+ } else if m, ok := ifaceMetricCache[luid]; ok {
+ return m
+ }
+
+ if iface, err := luid.IPInterface(windows.AF_INET); err == nil {
+ metric = iface.Metric
+ } else {
+ log.Printf("routerIP/luid.IPInterface error: %v", err)
+ metric = fallbackInterfaceMetric
+ }
+
+ ifaceMetricCache[luid] = metric
return
}
- defer cmd.Wait()
- var f []mem.RO
- lineread.Reader(stdout, func(lineb []byte) error {
- line := mem.B(lineb)
- if !mem.Contains(line, mem.S("0.0.0.0")) {
- return nil
+ unspec := net.IPv4(0, 0, 0, 0)
+ var best *winipcfg.MibIPforwardRow2 // best (lowest metric) found so far, or nil
+
+ for i := range rs {
+ r := &rs[i]
+ if r.Loopback || r.DestinationPrefix.PrefixLength != 0 || !r.DestinationPrefix.Prefix.IP().Equal(unspec) {
+ // Not a default route, so skip
+ continue
}
- f = mem.AppendFields(f[:0], line)
- if len(f) < 3 || !f[0].EqualString("0.0.0.0") || !f[1].EqualString("0.0.0.0") {
- return nil
+
+ ip, ok := netaddr.FromStdIP(r.NextHop.IP())
+ if !ok {
+ // Not a valid gateway, so skip (won't happen though)
+ continue
}
- ipm := f[2]
- ip, err := netaddr.ParseIP(string(mem.Append(nil, ipm)))
- if err == nil && isPrivateIP(ip) {
+
+ if best == nil {
+ best = r
ret = ip
+ continue
}
- return nil
- })
+
+ // We can get here only if there are multiple default gateways defined (rare case),
+ // in which case we need to calculate the effective metric.
+ // Effective metric is sum of interface metric and route metric offset
+ if ifaceMetricCache == nil {
+ // If we're here it means that previous route still isn't updated, so update it
+ best.Metric += getIfaceMetric(best.InterfaceLUID)
+ }
+ r.Metric += getIfaceMetric(r.InterfaceLUID)
+
+ if best.Metric > r.Metric || best.Metric == r.Metric && ret.Compare(ip) > 0 {
+ // Pick the route with lower metric, or lower IP if metrics are equal
+ best = r
+ ret = ip
+ }
+ }
+
+ if !ret.IsZero() && !isPrivateIP(ret) {
+ // Default route has a non-private gateway
+ return netaddr.IP{}, false
+ }
+
return ret, !ret.IsZero()
}