mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 13:05:46 +00:00
derp/derphttp, wgengine/magicsock: prefer IPv6 to DERPs when IPv6 works
Fixes #3838 Change-Id: Ie47a2a30c7e8e431512824798d2355006d72fb6a Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
f5ec916214
commit
730aa1c89c
@ -26,6 +26,7 @@
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go4.org/mem"
|
"go4.org/mem"
|
||||||
@ -64,6 +65,12 @@ type Client struct {
|
|||||||
ctx context.Context // closed via cancelCtx in Client.Close
|
ctx context.Context // closed via cancelCtx in Client.Close
|
||||||
cancelCtx context.CancelFunc
|
cancelCtx context.CancelFunc
|
||||||
|
|
||||||
|
// addrFamSelAtomic is the last AddressFamilySelector set
|
||||||
|
// by SetAddressFamilySelector. It's an atomic because it needs
|
||||||
|
// to be accessed by multiple racing routines started while
|
||||||
|
// Client.conn holds mu.
|
||||||
|
addrFamSelAtomic atomic.Value // of AddressFamilySelector
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
preferred bool
|
preferred bool
|
||||||
canAckPings bool
|
canAckPings bool
|
||||||
@ -193,6 +200,32 @@ func (c *Client) urlString(node *tailcfg.DERPNode) string {
|
|||||||
return fmt.Sprintf("https://%s/derp", node.HostName)
|
return fmt.Sprintf("https://%s/derp", node.HostName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddressFamilySelector decides whethers IPv6 is preferred for
|
||||||
|
// outbound dials.
|
||||||
|
type AddressFamilySelector interface {
|
||||||
|
// PreferIPv6 reports whether IPv4 dials should be slightly
|
||||||
|
// delayed to give IPv6 a better chance of winning dial races.
|
||||||
|
// Implementations should only return true if IPv6 is expected
|
||||||
|
// to succeed. (otherwise delaying IPv4 will delay the
|
||||||
|
// connection overall)
|
||||||
|
PreferIPv6() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAddressFamilySelector sets the AddressFamilySelector that this
|
||||||
|
// connection will use. It should be called before any dials.
|
||||||
|
// The value must not be nil. If called more than once, s must
|
||||||
|
// be the same concrete type as any prior calls.
|
||||||
|
func (c *Client) SetAddressFamilySelector(s AddressFamilySelector) {
|
||||||
|
c.addrFamSelAtomic.Store(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) preferIPv6() bool {
|
||||||
|
if s, ok := c.addrFamSelAtomic.Load().(AddressFamilySelector); ok {
|
||||||
|
return s.PreferIPv6()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// dialWebsocketFunc is non-nil (set by websocket.go's init) when compiled in.
|
// dialWebsocketFunc is non-nil (set by websocket.go's init) when compiled in.
|
||||||
var dialWebsocketFunc func(ctx context.Context, urlStr string) (net.Conn, error)
|
var dialWebsocketFunc func(ctx context.Context, urlStr string) (net.Conn, error)
|
||||||
|
|
||||||
@ -583,6 +616,18 @@ type res struct {
|
|||||||
startDial := func(dstPrimary, proto string) {
|
startDial := func(dstPrimary, proto string) {
|
||||||
nwait++
|
nwait++
|
||||||
go func() {
|
go func() {
|
||||||
|
if proto == "tcp4" && c.preferIPv6() {
|
||||||
|
t := time.NewTimer(200 * time.Millisecond)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
// Either user canceled original context,
|
||||||
|
// it timed out, or the v6 dial succeeded.
|
||||||
|
t.Stop()
|
||||||
|
return
|
||||||
|
case <-t.C:
|
||||||
|
// Start v4 dial
|
||||||
|
}
|
||||||
|
}
|
||||||
dst := dstPrimary
|
dst := dstPrimary
|
||||||
if dst == "" {
|
if dst == "" {
|
||||||
dst = n.HostName
|
dst = n.HostName
|
||||||
|
@ -305,6 +305,8 @@ type Conn struct {
|
|||||||
// lock ordering deadlocks. See issue 3726 and mu field docs.
|
// lock ordering deadlocks. See issue 3726 and mu field docs.
|
||||||
derpMapAtomic atomic.Value // of *tailcfg.DERPMap
|
derpMapAtomic atomic.Value // of *tailcfg.DERPMap
|
||||||
|
|
||||||
|
lastNetCheckReport atomic.Value // of *netcheck.Report
|
||||||
|
|
||||||
// port is the preferred port from opts.Port; 0 means auto.
|
// port is the preferred port from opts.Port; 0 means auto.
|
||||||
port syncs.AtomicUint32
|
port syncs.AtomicUint32
|
||||||
|
|
||||||
@ -741,6 +743,7 @@ func (c *Conn) updateNetInfo(ctx context.Context) (*netcheck.Report, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.lastNetCheckReport.Store(report)
|
||||||
c.noV4.Set(!report.IPv4)
|
c.noV4.Set(!report.IPv4)
|
||||||
c.noV6.Set(!report.IPv6)
|
c.noV6.Set(!report.IPv6)
|
||||||
c.noV4Send.Set(!report.IPv4CanSend)
|
c.noV4Send.Set(!report.IPv4CanSend)
|
||||||
@ -1380,6 +1383,7 @@ func (c *Conn) derpWriteChanOfAddr(addr netaddr.IPPort, peer key.NodePublic) cha
|
|||||||
|
|
||||||
dc.SetCanAckPings(true)
|
dc.SetCanAckPings(true)
|
||||||
dc.NotePreferred(c.myDerp == regionID)
|
dc.NotePreferred(c.myDerp == regionID)
|
||||||
|
dc.SetAddressFamilySelector(derpAddrFamSelector{c})
|
||||||
dc.DNSCache = dnscache.Get()
|
dc.DNSCache = dnscache.Get()
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(c.connCtx)
|
ctx, cancel := context.WithCancel(c.connCtx)
|
||||||
@ -4125,6 +4129,22 @@ func (di *discoInfo) setNodeKey(nk key.NodePublic) {
|
|||||||
di.lastNodeKeyTime = time.Now()
|
di.lastNodeKeyTime = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// derpAddrFamSelector is the derphttp.AddressFamilySelector we pass
|
||||||
|
// to derphttp.Client.SetAddressFamilySelector.
|
||||||
|
//
|
||||||
|
// It provides the hint as to whether in an IPv4-vs-IPv6 race that
|
||||||
|
// IPv4 should be held back a bit to give IPv6 a better-than-50/50
|
||||||
|
// chance of winning. We only return true when we believe IPv6 will
|
||||||
|
// work anyway, so we don't artificially delay the connection speed.
|
||||||
|
type derpAddrFamSelector struct{ c *Conn }
|
||||||
|
|
||||||
|
func (s derpAddrFamSelector) PreferIPv6() bool {
|
||||||
|
if r, ok := s.c.lastNetCheckReport.Load().(*netcheck.Report); ok {
|
||||||
|
return r.IPv6
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
metricNumPeers = clientmetric.NewGauge("magicsock_netmap_num_peers")
|
metricNumPeers = clientmetric.NewGauge("magicsock_netmap_num_peers")
|
||||||
metricNumDERPConns = clientmetric.NewGauge("magicsock_num_derp_conns")
|
metricNumDERPConns = clientmetric.NewGauge("magicsock_num_derp_conns")
|
||||||
|
Loading…
Reference in New Issue
Block a user