|
|
|
|
@ -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 |
|
|
|
|
|