ipn, wgengine, magicsock, tsdns: be quieter and less aggressive when offline

If no interfaces are up, calm down and stop spamming so much. It was
noticed as especially bad on Windows, but probably was bad
everywhere. I just have the best network conditions testing on a
Windows VM.

Updates #604
reviewable/pr792/r1
Brad Fitzpatrick 4 years ago
parent 7616acd118
commit 6ee219a25d

@ -129,6 +129,11 @@ func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
hadPAC := b.prevIfState.HasPAC()
b.prevIfState = ifst
networkUp := ifst.AnyInterfaceUp()
if b.c != nil {
go b.c.SetPaused(b.state == Stopped || !networkUp)
}
// If the PAC-ness of the network changed, reconfig wireguard+route to
// add/remove subnets.
if hadPAC != ifst.HasPAC() {
@ -1197,6 +1202,7 @@ func (b *LocalBackend) enterState(newState State) {
prefs := b.prefs
notify := b.notify
bc := b.c
networkUp := b.prevIfState.AnyInterfaceUp()
b.mu.Unlock()
if state == newState {
@ -1209,7 +1215,7 @@ func (b *LocalBackend) enterState(newState State) {
}
if bc != nil {
bc.SetPaused(newState == Stopped)
bc.SetPaused(newState == Stopped || !networkUp)
}
switch newState {

@ -161,10 +161,11 @@ type State struct {
InterfaceUp map[string]bool
// HaveV6Global is whether this machine has an IPv6 global address
// on some interface.
// on some non-Tailscale interface that's up.
HaveV6Global bool
// HaveV4 is whether the machine has some non-localhost IPv4 address.
// HaveV4 is whether the machine has some non-localhost,
// non-link-local IPv4 address on a non-Tailscale interface that's up.
HaveV4 bool
// IsExpensive is whether the current network interface is
@ -174,6 +175,8 @@ type State struct {
// DefaultRouteInterface is the interface name for the machine's default route.
// It is not yet populated on all OSes.
// Its exact value should not be assumed to be a map key for
// the Interface maps above; it's only used for debugging.
DefaultRouteInterface string
// HTTPProxy is the HTTP proxy to use.
@ -244,20 +247,29 @@ func (s *State) Equal(s2 *State) bool {
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)
}
// RemoveTailscaleInterfaces modifes s to remove any interfaces that
// are owned by this process. (TODO: make this true; currently it
// makes the Linux-only assumption that the interface is named
// /^tailscale/)
func (s *State) RemoveTailscaleInterfaces() {
for name := range s.InterfaceIPs {
if name == "Tailscale" || // as it is on Windows
strings.HasPrefix(name, "tailscale") { // TODO: use --tun flag value, etc; see TODO in method doc
if isTailscaleInterfaceName(name) {
delete(s.InterfaceIPs, name)
delete(s.InterfaceUp, name)
}
}
}
func isTailscaleInterfaceName(name string) bool {
return name == "Tailscale" || // as it is on Windows
strings.HasPrefix(name, "tailscale") // TODO: use --tun flag value, etc; see TODO in method doc
}
// getPAC, if non-nil, returns the current PAC file URL.
var getPAC func() string
@ -270,24 +282,29 @@ func GetState() (*State, error) {
InterfaceUp: make(map[string]bool),
}
if err := ForeachInterfaceAddress(func(ni Interface, ip netaddr.IP) {
ifUp := ni.IsUp()
s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], ip)
s.InterfaceUp[ni.Name] = ni.IsUp()
s.HaveV6Global = s.HaveV6Global || isGlobalV6(ip)
s.HaveV4 = s.HaveV4 || (ip.Is4() && !ip.IsLoopback())
s.InterfaceUp[ni.Name] = ifUp
if ifUp && !ip.IsLoopback() && !ip.IsLinkLocalUnicast() && !isTailscaleInterfaceName(ni.Name) {
s.HaveV6Global = s.HaveV6Global || isGlobalV6(ip)
s.HaveV4 = s.HaveV4 || ip.Is4()
}
}); err != nil {
return nil, err
}
s.DefaultRouteInterface, _ = DefaultRouteInterface()
req, err := http.NewRequest("GET", LoginEndpointForProxyDetermination, nil)
if err != nil {
return nil, err
}
if u, err := tshttpproxy.ProxyFromEnvironment(req); err == nil && u != nil {
s.HTTPProxy = u.String()
}
if getPAC != nil {
s.PAC = getPAC()
if s.AnyInterfaceUp() {
req, err := http.NewRequest("GET", LoginEndpointForProxyDetermination, nil)
if err != nil {
return nil, err
}
if u, err := tshttpproxy.ProxyFromEnvironment(req); err == nil && u != nil {
s.HTTPProxy = u.String()
}
if getPAC != nil {
s.PAC = getPAC()
}
}
return s, nil

@ -208,6 +208,11 @@ type Conn struct {
// necessarily have a netcheck.Report and don't want to skip
// logging.
noV4, noV6 syncs.AtomicBool
// networkUp is whether the network is up (some interface is up
// with IPv4 or IPv6). It's used to suppress log spam and prevent
// new connection that'll fail.
networkUp syncs.AtomicBool
}
// derpRoute is a route entry for a public key, saying that a certain
@ -345,6 +350,7 @@ func newConn() *Conn {
discoOfAddr: make(map[netaddr.IPPort]tailcfg.DiscoKey),
}
c.muCond = sync.NewCond(&c.mu)
c.networkUp.Set(true) // assume up until told otherwise
return c
}
@ -969,8 +975,15 @@ func (as *AddrSet) appendDests(dsts []netaddr.IPPort, b []byte) (_ []netaddr.IPP
}
var errNoDestinations = errors.New("magicsock: no destinations")
var errNetworkDown = errors.New("magicsock: network down")
func (c *Conn) networkDown() bool { return !c.networkUp.Get() }
func (c *Conn) Send(b []byte, ep conn.Endpoint) error {
if c.networkDown() {
return errNetworkDown
}
var as *AddrSet
switch v := ep.(type) {
default:
@ -1111,6 +1124,10 @@ func (c *Conn) derpWriteChanOfAddr(addr netaddr.IPPort, peer key.Public) chan<-
}
regionID := int(addr.Port)
if c.networkDown() {
return nil
}
c.mu.Lock()
defer c.mu.Unlock()
if !c.wantDerpLocked() || c.closed {
@ -1304,15 +1321,19 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netaddr.IPPort, d
for {
msg, err := dc.Recv()
if err == derphttp.ErrClientClosed {
return
}
if err != nil {
// Forget that all these peers have routes.
for peer := range peerPresent {
delete(peerPresent, peer)
c.removeDerpPeerRoute(peer, regionID, dc)
}
if err == derphttp.ErrClientClosed {
return
}
if c.networkDown() {
c.logf("magicsock: derp.Recv(derp-%d): network down, closing", regionID)
return
}
select {
case <-ctx.Done():
return
@ -1691,7 +1712,9 @@ func (c *Conn) sendDiscoMessage(dst netaddr.IPPort, dstKey tailcfg.NodeKey, dstD
} else if err == nil {
// Can't send. (e.g. no IPv6 locally)
} else {
c.logf("magicsock: disco: failed to send %T to %v: %v", m, dst, err)
if !c.networkDown() {
c.logf("magicsock: disco: failed to send %T to %v: %v", m, dst, err)
}
}
return sent, err
}
@ -1956,6 +1979,21 @@ func (c *Conn) sharedDiscoKeyLocked(k tailcfg.DiscoKey) *[32]byte {
return shared
}
func (c *Conn) SetNetworkUp(up bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.networkUp.Get() == up {
return
}
c.logf("magicsock: SetNetworkUp(%v)", up)
c.networkUp.Set(up)
if !up {
c.closeAllDerpLocked("network-down")
}
}
// SetPrivateKey sets the connection's private key.
//
// This is only used to be able prove our identity when connecting to
@ -2282,6 +2320,10 @@ func maxIdleBeforeSTUNShutdown() time.Duration {
}
func (c *Conn) shouldDoPeriodicReSTUN() bool {
if c.networkDown() {
return false
}
c.mu.Lock()
defer c.mu.Unlock()
if len(c.peerSet) == 0 {

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !darwin
// +build !darwin,!windows
package tsdns

@ -0,0 +1,29 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tsdns
import (
"net"
"os"
"golang.org/x/sys/windows"
)
func networkIsDown(err error) bool {
if oe, ok := err.(*net.OpError); ok && oe.Op == "write" {
if se, ok := oe.Err.(*os.SyscallError); ok {
if se.Syscall == "wsasendto" && se.Err == windows.WSAENETUNREACH {
return true
}
}
}
return false
}
func networkIsUnreachable(err error) bool {
// TODO(bradfitz,josharian): something here? what is the
// difference between down and unreachable? Add comments.
return false
}

@ -246,6 +246,7 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
e.tundev.Close()
return nil, fmt.Errorf("wgengine: %v", err)
}
e.magicConn.SetNetworkUp(e.linkState.AnyInterfaceUp())
// flags==0 because logf is already nested in another logger.
// The outer one can display the preferred log prefixes, etc.
@ -1139,12 +1140,17 @@ func (e *userspaceEngine) LinkChange(isExpensive bool) {
cur.IsExpensive = isExpensive
needRebind, linkChangeCallback := e.setLinkState(cur)
if needRebind {
e.logf("LinkChange: major, rebinding. New state: %+v", cur)
up := cur.AnyInterfaceUp()
if !up {
e.logf("LinkChange: all links down; pausing: %v", cur)
} else if needRebind {
e.logf("LinkChange: major, rebinding. New state: %v", cur)
} else {
e.logf("LinkChange: minor")
}
e.magicConn.SetNetworkUp(up)
why := "link-change-minor"
if needRebind {
why = "link-change-major"

Loading…
Cancel
Save