Skip to content

Commit 2c94e3c

Browse files
committed
wgengine/magicsock: don't unconditionally close DERP connections on rebind
Only if the source address isn't on the currently active interface or a ping of the DERP server fails. Updates tailscale#3619 Change-Id: I6bf06503cff4d781f518b437c8744ac29577acc8 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
1 parent 04c2c5b commit 2c94e3c

File tree

3 files changed

+86
-9
lines changed

3 files changed

+86
-9
lines changed

derp/derphttp/derphttp_client.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,21 @@ func (c *Client) SendPing(data [8]byte) error {
774774
return client.SendPing(data)
775775
}
776776

777+
// LocalAddr reports c's local TCP address, without any implicit
778+
// connect or reconnect.
779+
func (c *Client) LocalAddr() (netaddr.IPPort, error) {
780+
c.mu.Lock()
781+
closed, client := c.closed, c.client
782+
c.mu.Unlock()
783+
if closed {
784+
return netaddr.IPPort{}, ErrClientClosed
785+
}
786+
if client == nil {
787+
return netaddr.IPPort{}, errors.New("client not connected")
788+
}
789+
return client.LocalAddr()
790+
}
791+
777792
func (c *Client) ForwardPacket(from, to key.NodePublic, b []byte) error {
778793
client, _, err := c.connect(context.TODO(), "derphttp.Client.ForwardPacket")
779794
if err != nil {

net/tsaddr/tsaddr.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,16 @@ func PrefixesContainsFunc(ipp []netaddr.IPPrefix, f func(netaddr.IPPrefix) bool)
176176
return false
177177
}
178178

179+
// PrefixesContainsIP reports whether any prefix in ipp contains ip.
180+
func PrefixesContainsIP(ipp []netaddr.IPPrefix, ip netaddr.IP) bool {
181+
for _, r := range ipp {
182+
if r.Contains(ip) {
183+
return true
184+
}
185+
}
186+
return false
187+
}
188+
179189
// IPsContainsFunc reports whether f is true for any IP in ips.
180190
func IPsContainsFunc(ips []netaddr.IP, f func(netaddr.IP) bool) bool {
181191
for _, v := range ips {

wgengine/magicsock/magicsock.go

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
"tailscale.com/net/netns"
4343
"tailscale.com/net/portmapper"
4444
"tailscale.com/net/stun"
45+
"tailscale.com/net/tsaddr"
4546
"tailscale.com/syncs"
4647
"tailscale.com/tailcfg"
4748
"tailscale.com/tstime"
@@ -233,6 +234,7 @@ type Conn struct {
233234
idleFunc func() time.Duration // nil means unknown
234235
testOnlyPacketListener nettype.PacketListener
235236
noteRecvActivity func(key.NodePublic) // or nil, see Options.NoteRecvActivity
237+
linkMon *monitor.Mon // or nil
236238

237239
// ================================================================
238240
// No locking required to access these fields, either because
@@ -549,6 +551,7 @@ func NewConn(opts Options) (*Conn, error) {
549551
if opts.LinkMonitor != nil {
550552
c.portMapper.SetGatewayLookupFunc(opts.LinkMonitor.GatewayAndSelfIP)
551553
}
554+
c.linkMon = opts.LinkMonitor
552555

553556
if err := c.initialBind(); err != nil {
554557
return nil, err
@@ -2393,14 +2396,61 @@ func (c *Conn) closeAllDerpLocked(why string) {
23932396
c.logActiveDerpLocked()
23942397
}
23952398

2399+
// maybeCloseDERPsOnRebind, in response to a rebind, closes all
2400+
// DERP connections that don't have a local address in okayLocalIPs
2401+
// and pings all those that do.
2402+
func (c *Conn) maybeCloseDERPsOnRebind(okayLocalIPs []netaddr.IPPrefix) {
2403+
c.mu.Lock()
2404+
defer c.mu.Unlock()
2405+
for regionID, ad := range c.activeDerp {
2406+
la, err := ad.c.LocalAddr()
2407+
if err != nil {
2408+
c.closeOrReconectDERPLocked(regionID, "rebind-no-localaddr")
2409+
continue
2410+
}
2411+
if !tsaddr.PrefixesContainsIP(okayLocalIPs, la.IP()) {
2412+
c.closeOrReconectDERPLocked(regionID, "rebind-default-route-change")
2413+
continue
2414+
}
2415+
regionID := regionID
2416+
dc := ad.c
2417+
go func() {
2418+
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
2419+
defer cancel()
2420+
if err := dc.Ping(ctx); err != nil {
2421+
c.mu.Lock()
2422+
defer c.mu.Unlock()
2423+
c.closeOrReconectDERPLocked(regionID, "rebind-ping-fail")
2424+
return
2425+
}
2426+
c.logf("post-rebind ping of DERP region %d okay", regionID)
2427+
}()
2428+
}
2429+
c.logActiveDerpLocked()
2430+
}
2431+
2432+
// closeOrReconectDERPLocked closes the DERP connection to the
2433+
// provided regionID and starts reconnecting it if it's our current
2434+
// home DERP.
2435+
//
2436+
// why is a reason for logging.
2437+
//
2438+
// c.mu must be held.
2439+
func (c *Conn) closeOrReconectDERPLocked(regionID int, why string) {
2440+
c.closeDerpLocked(regionID, why)
2441+
if !c.privateKey.IsZero() && c.myDerp == regionID {
2442+
c.startDerpHomeConnectLocked()
2443+
}
2444+
}
2445+
23962446
// c.mu must be held.
23972447
// It is the responsibility of the caller to call logActiveDerpLocked after any set of closes.
2398-
func (c *Conn) closeDerpLocked(node int, why string) {
2399-
if ad, ok := c.activeDerp[node]; ok {
2400-
c.logf("magicsock: closing connection to derp-%v (%v), age %v", node, why, time.Since(ad.createTime).Round(time.Second))
2448+
func (c *Conn) closeDerpLocked(regionID int, why string) {
2449+
if ad, ok := c.activeDerp[regionID]; ok {
2450+
c.logf("magicsock: closing connection to derp-%v (%v), age %v", regionID, why, time.Since(ad.createTime).Round(time.Second))
24012451
go ad.c.Close()
24022452
ad.cancel()
2403-
delete(c.activeDerp, node)
2453+
delete(c.activeDerp, regionID)
24042454
metricNumDERPConns.Set(int64(len(c.activeDerp)))
24052455
}
24062456
}
@@ -2831,13 +2881,15 @@ func (c *Conn) Rebind() {
28312881
return
28322882
}
28332883

2834-
c.mu.Lock()
2835-
c.closeAllDerpLocked("rebind")
2836-
if !c.privateKey.IsZero() {
2837-
c.startDerpHomeConnectLocked()
2884+
var ifIPs []netaddr.IPPrefix
2885+
if c.linkMon != nil {
2886+
st := c.linkMon.InterfaceState()
2887+
defIf := st.DefaultRouteInterface
2888+
ifIPs = st.InterfaceIPs[defIf]
2889+
c.logf("Rebind; defIf=%q, ips=%v", defIf, ifIPs)
28382890
}
2839-
c.mu.Unlock()
28402891

2892+
c.maybeCloseDERPsOnRebind(ifIPs)
28412893
c.resetEndpointStates()
28422894
}
28432895

0 commit comments

Comments
 (0)