net/interfaces: remove mutating methods, add EqualFiltered instead

Now callers (wgengine/monitor) don't need to mutate the state to remove
boring interfaces before calling State.Equal. Instead, the methods
to remove boring interfaces from the State are removed, as is
the reflect-using Equal method itself, and in their place is
a new EqualFiltered method that takes a func predicate to match
interfaces to compare.

And then the FilterInteresting predicate is added for use
with EqualFiltered to do the job that that wgengine/monitor
previously wanted.

Now wgengine/monitor can keep the full interface state around,
including the "boring" interfaces, which we'll need for peerapi on
macOS/iOS to bind to the interface index of the utunN device.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/1593/head
Brad Fitzpatrick 4 years ago
parent 5a62aa8047
commit 7f174e84e6

@ -6,10 +6,10 @@
package interfaces package interfaces
import ( import (
"bytes"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"reflect"
"runtime" "runtime"
"sort" "sort"
"strings" "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) fn(Interface{iface}, pfxs)
} }
return nil return nil
@ -286,50 +289,76 @@ func (s *State) String() string {
return sb.String() return sb.String()
} }
func (s *State) Equal(s2 *State) bool { // EqualFiltered reports whether s and s2 are equal,
return reflect.DeepEqual(s, s2) // 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 {
func (s *State) HasPAC() bool { return s != nil && s.PAC != "" } return true
// 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.Interface {
ips := s.InterfaceIPs[ifName]
keep := ips[:0]
for _, pfx := range ips {
if isInterestingIP(pfx.IP) {
keep = append(keep, pfx)
} }
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
} }
if len(keep) == 0 { for iname, i := range s.Interface {
delete(s.Interface, ifName) ips := s.InterfaceIPs[iname]
delete(s.InterfaceIPs, ifName) if !filter(i, ips) {
continue continue
} }
if len(keep) < len(ips) { i2, ok := s2.Interface[iname]
s.InterfaceIPs[ifName] = keep 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 func prefixesEqual(a, b []netaddr.IPPrefix) bool {
// are owned by this process. (TODO: make this true; currently it if len(a) != len(b) {
// uses some heuristics) return false
func (s *State) RemoveTailscaleInterfaces() { }
for name, pfxs := range s.InterfaceIPs { for i, v := range a {
if isTailscaleInterface(name, pfxs) { if b[i] != v {
delete(s.InterfaceIPs, name) return false
delete(s.Interface, name)
} }
} }
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 { func hasTailscaleIP(pfxs []netaddr.IPPrefix) bool {

@ -5,6 +5,7 @@
package interfaces package interfaces
import ( import (
"encoding/json"
"testing" "testing"
) )
@ -13,7 +14,11 @@ func TestGetState(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) 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) t.Logf("As string: %s", st)
st2, err := GetState() st2, err := GetState()
@ -21,14 +26,13 @@ func TestGetState(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if !st.Equal(st2) { if !st.EqualFiltered(st2, FilterAll) {
// let's assume nobody was changing the system network interfaces between // let's assume nobody was changing the system network interfaces between
// the two GetState calls. // the two GetState calls.
t.Fatal("two States back-to-back were not equal") t.Fatal("two States back-to-back were not equal")
} }
st.RemoveTailscaleInterfaces() t.Logf("As string:\n\t%s", st)
t.Logf("As string without Tailscale:\n\t%s", st)
} }
func TestLikelyHomeRouterIP(t *testing.T) { func TestLikelyHomeRouterIP(t *testing.T) {

@ -101,12 +101,7 @@ func (m *Mon) InterfaceState() *interfaces.State {
} }
func (m *Mon) interfaceStateUncached() (*interfaces.State, error) { func (m *Mon) interfaceStateUncached() (*interfaces.State, error) {
s, err := interfaces.GetState() return interfaces.GetState()
if s != nil {
s.RemoveTailscaleInterfaces()
s.RemoveUninterestingInterfacesAndAddresses()
}
return s, err
} }
// GatewayAndSelfIP returns the current network's default gateway, and // GatewayAndSelfIP returns the current network's default gateway, and
@ -233,7 +228,7 @@ func (m *Mon) debounce() {
} else { } else {
m.mu.Lock() m.mu.Lock()
oldState := m.ifState oldState := m.ifState
changed := !curState.Equal(oldState) changed := !curState.EqualFiltered(oldState, interfaces.FilterInteresting)
if changed { if changed {
m.gwValid = false m.gwValid = false
m.ifState = curState m.ifState = curState

Loading…
Cancel
Save