diff --git a/ipn/local.go b/ipn/local.go index 7422483e4..a639b5c22 100644 --- a/ipn/local.go +++ b/ipn/local.go @@ -167,8 +167,10 @@ func (b *LocalBackend) Start(opts Options) error { b.notify = opts.Notify b.netMapCache = nil persist := b.prefs.Persist + wantDERP := !b.prefs.DisableDERP b.mu.Unlock() + b.e.SetDERPEnabled(wantDERP) b.updateFilter(nil) var err error diff --git a/ipn/prefs.go b/ipn/prefs.go index 3bd05384d..d5aa63ffd 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -53,6 +53,9 @@ type Prefs struct { // TODO(danderson): remove? NotepadURLs bool + // DisableDERP prevents DERP from being used. + DisableDERP bool + // The Persist field is named 'Config' in the file for backward // compatibility with earlier versions. // TODO(apenwarr): We should move this out of here, it's not a pref. @@ -99,6 +102,7 @@ func (p *Prefs) Equals(p2 *Prefs) bool { p.CorpDNS == p2.CorpDNS && p.WantRunning == p2.WantRunning && p.NotepadURLs == p2.NotepadURLs && + p.DisableDERP == p2.DisableDERP && p.UsePacketFilter == p2.UsePacketFilter && compareIPNets(p.AdvertiseRoutes, p2.AdvertiseRoutes) && p.Persist.Equals(p2.Persist) diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go index 4c4e88d4a..2bfcb8f82 100644 --- a/ipn/prefs_test.go +++ b/ipn/prefs_test.go @@ -20,7 +20,7 @@ func fieldsOf(t reflect.Type) (fields []string) { } func TestPrefsEqual(t *testing.T) { - prefsHandles := []string{"ControlURL", "RouteAll", "AllowSingleHosts", "CorpDNS", "WantRunning", "UsePacketFilter", "AdvertiseRoutes", "NotepadURLs", "Persist"} + prefsHandles := []string{"ControlURL", "RouteAll", "AllowSingleHosts", "CorpDNS", "WantRunning", "UsePacketFilter", "AdvertiseRoutes", "NotepadURLs", "DisableDERP", "Persist"} if have := fieldsOf(reflect.TypeOf(Prefs{})); !reflect.DeepEqual(have, prefsHandles) { t.Errorf("Prefs.Equal check might be out of sync\nfields: %q\nhandled: %q\n", have, prefsHandles) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index c68abaad8..ca001b2d6 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -80,6 +80,7 @@ type Conn struct { derpRecvCh chan derpReadResult derpMu sync.Mutex + wantDerp bool privateKey key.Private myDerp int // nearest DERP server; 0 means none/unknown derpConn map[int]*derphttp.Client // magic derp port (see derpmap.go) to its client @@ -160,6 +161,7 @@ func Listen(opts Options) (*Conn, error) { epFunc: opts.endpointsFunc(), logf: log.Printf, addrsByUDP: make(map[udpAddr]*AddrSet), + wantDerp: true, derpRecvCh: make(chan derpReadResult), udpRecvCh: make(chan udpReadResult), } @@ -263,7 +265,9 @@ func (c *Conn) updateNetInfo() { // one. ni.PreferredDERP = c.pickDERPFallback() } - c.setNearestDerp(ni.PreferredDERP) + if !c.setNearestDERP(ni.PreferredDERP) { + ni.PreferredDERP = 0 + } // TODO: set link type @@ -327,16 +331,19 @@ func (c *Conn) SetNetInfoCallback(fn func(*tailcfg.NetInfo)) { } } -func (c *Conn) setNearestDerp(derpNum int) (changed bool) { +func (c *Conn) setNearestDERP(derpNum int) (wantDERP bool) { c.derpMu.Lock() defer c.derpMu.Unlock() - changed = c.myDerp != derpNum - if changed && derpNum != 0 { + if !c.wantDerp { + c.myDerp = 0 + return false + } + if derpNum != 0 && derpNum != c.myDerp { // On change, start connecting to it: go c.derpWriteChanOfAddr(&net.UDPAddr{IP: derpMagicIP, Port: derpNum}) } c.myDerp = derpNum - return changed + return true } // determineEndpoints returns the machine's endpoint addresses. It @@ -560,25 +567,30 @@ func (c *Conn) Send(b []byte, ep conn.Endpoint) error { // or a fake UDP address representing a DERP server (see derpmap.go). // The provided public key identifies the recipient. func (c *Conn) sendAddr(addr *net.UDPAddr, pubKey key.Public, b []byte) error { - if ch := c.derpWriteChanOfAddr(addr); ch != nil { - errc := make(chan error, 1) + if !addr.IP.Equal(derpMagicIP) { + _, err := c.pconn.WriteTo(b, addr) + return err + } + + ch := c.derpWriteChanOfAddr(addr) + if ch == nil { + return nil + } + errc := make(chan error, 1) + select { + case <-c.donec(): + return errConnClosed + case ch <- derpWriteRequest{addr, pubKey, b, errc}: select { case <-c.donec(): return errConnClosed - case ch <- derpWriteRequest{addr, pubKey, b, errc}: - select { - case <-c.donec(): - return errConnClosed - case err := <-errc: - return err // usually nil - } - default: - // Too many writes queued. Drop packet. - return errDropDerpPacket + case err := <-errc: + return err // usually nil } + default: + // Too many writes queued. Drop packet. + return errDropDerpPacket } - _, err := c.pconn.WriteTo(b, addr) - return err } // bufferedDerpWritesBeforeDrop is how many packets writes can be @@ -597,6 +609,9 @@ func (c *Conn) derpWriteChanOfAddr(addr *net.UDPAddr) chan<- derpWriteRequest { } c.derpMu.Lock() defer c.derpMu.Unlock() + if !c.wantDerp { + return nil + } if c.privateKey.IsZero() { c.logf("DERP lookup of %v with no private key; ignoring", addr.IP) return nil @@ -864,6 +879,18 @@ func (c *Conn) SetPrivateKey(privateKey wgcfg.PrivateKey) error { return nil } +// SetDERPEnabled controls whether DERP is used. +// New connections have it enabled by default. +func (c *Conn) SetDERPEnabled(wantDerp bool) { + c.derpMu.Lock() + defer c.derpMu.Unlock() + + c.wantDerp = wantDerp + if !wantDerp { + c.closeAllDerpLocked() + } +} + // c.derpMu must be held. func (c *Conn) closeAllDerpLocked() { for _, c := range c.derpConn { diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 66daabcd1..d2805e020 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -604,3 +604,7 @@ func (e *userspaceEngine) LinkChange(isExpensive bool) { func (e *userspaceEngine) SetNetInfoCallback(cb NetInfoCallback) { e.magicConn.SetNetInfoCallback(cb) } + +func (e *userspaceEngine) SetDERPEnabled(v bool) { + e.magicConn.SetDERPEnabled(v) +} diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index d6a89abe8..90bfc2344 100644 --- a/wgengine/watchdog.go +++ b/wgengine/watchdog.go @@ -78,6 +78,9 @@ func (e *watchdogEngine) RequestStatus() { func (e *watchdogEngine) LinkChange(isExpensive bool) { e.watchdog("LinkChange", func() { e.wrap.LinkChange(isExpensive) }) } +func (e *watchdogEngine) SetDERPEnabled(v bool) { + e.watchdog("SetDERPEnabled", func() { e.wrap.SetDERPEnabled(v) }) +} func (e *watchdogEngine) Close() { e.watchdog("Close", e.wrap.Close) } diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index ab6b5747b..6d54bb790 100644 --- a/wgengine/wgengine.go +++ b/wgengine/wgengine.go @@ -127,6 +127,10 @@ type Engine interface { // such as mobile data on a phone. LinkChange(isExpensive bool) + // SetDERPEnabled controls whether DERP is enabled. + // It starts enabled by default. + SetDERPEnabled(bool) + // SetNetInfoCallback sets the function to call when a // new NetInfo summary is available. SetNetInfoCallback(NetInfoCallback)