|
|
@@ -26,6 +26,7 @@ import (
|
|
|
"runtime"
|
|
|
"strings"
|
|
|
"sync"
|
|
|
+ "sync/atomic"
|
|
|
"time"
|
|
|
|
|
|
"go4.org/mem"
|
|
|
@@ -64,6 +65,12 @@ type Client struct {
|
|
|
ctx context.Context // closed via cancelCtx in Client.Close
|
|
|
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
|
|
|
preferred bool
|
|
|
canAckPings bool
|
|
|
@@ -193,6 +200,32 @@ func (c *Client) urlString(node *tailcfg.DERPNode) string {
|
|
|
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.
|
|
|
var dialWebsocketFunc func(ctx context.Context, urlStr string) (net.Conn, error)
|
|
|
|
|
|
@@ -583,6 +616,18 @@ func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, e
|
|
|
startDial := func(dstPrimary, proto string) {
|
|
|
nwait++
|
|
|
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
|
|
|
if dst == "" {
|
|
|
dst = n.HostName
|