mirror of
https://github.com/tailscale/tailscale.git
synced 2025-10-24 01:31:55 +00:00
magicsock, wgengine, ipn, controlclient: plumb regular netchecks to map poll
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
4cf5ac3060
commit
b27d4c017a
@@ -11,7 +11,9 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -28,9 +30,12 @@ import (
|
||||
"tailscale.com/derp"
|
||||
"tailscale.com/derp/derphttp"
|
||||
"tailscale.com/interfaces"
|
||||
"tailscale.com/netcheck"
|
||||
"tailscale.com/stun"
|
||||
"tailscale.com/stunner"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
@@ -66,6 +71,10 @@ type Conn struct {
|
||||
// Its Loaded value is always non-nil.
|
||||
stunReceiveFunc atomic.Value // of func(p []byte, fromAddr *net.UDPAddr)
|
||||
|
||||
netInfoMu sync.Mutex
|
||||
netInfoFunc func(*tailcfg.NetInfo) // nil until set
|
||||
netInfoLast *tailcfg.NetInfo
|
||||
|
||||
udpRecvCh chan udpReadResult
|
||||
derpRecvCh chan derpReadResult
|
||||
|
||||
@@ -204,15 +213,17 @@ func (c *Conn) epUpdate(ctx context.Context) {
|
||||
|
||||
go func() {
|
||||
defer close(lastDone)
|
||||
nearestDerp, endpoints, err := c.determineEndpoints(epCtx)
|
||||
|
||||
c.updateNetInfo() // best effort
|
||||
|
||||
endpoints, err := c.determineEndpoints(epCtx)
|
||||
if err != nil {
|
||||
c.logf("magicsock.Conn: endpoint update failed: %v", err)
|
||||
// TODO(crawshaw): are there any conditions under which
|
||||
// we should trigger a retry based on the error here?
|
||||
return
|
||||
}
|
||||
derpChanged := c.setNearestDerp(nearestDerp)
|
||||
if stringsEqual(endpoints, lastEndpoints) && !derpChanged {
|
||||
if stringsEqual(endpoints, lastEndpoints) {
|
||||
return
|
||||
}
|
||||
lastEndpoints = endpoints
|
||||
@@ -222,6 +233,98 @@ func (c *Conn) epUpdate(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) updateNetInfo() {
|
||||
logf := logger.WithPrefix(c.logf, "updateNetInfo: ")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
report, err := netcheck.GetReport(ctx, logf)
|
||||
if err != nil {
|
||||
logf("GetReport: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
ni := &tailcfg.NetInfo{
|
||||
DERPLatency: map[string]float64{},
|
||||
MappingVariesByDestIP: report.MappingVariesByDestIP,
|
||||
HairPinning: report.HairPinning,
|
||||
}
|
||||
for server, d := range report.DERPLatency {
|
||||
ni.DERPLatency[server] = d.Seconds()
|
||||
}
|
||||
ni.WorkingIPv6.Set(report.IPv6)
|
||||
ni.WorkingUDP.Set(report.UDP)
|
||||
ni.PreferredDERP = report.PreferredDERP
|
||||
|
||||
if ni.PreferredDERP == 0 {
|
||||
// Perhaps UDP is blocked. Pick a deterministic but arbitrary
|
||||
// one.
|
||||
ni.PreferredDERP = c.pickDERPFallback()
|
||||
}
|
||||
c.setNearestDerp(ni.PreferredDERP)
|
||||
|
||||
// TODO: set link type
|
||||
|
||||
c.callNetInfoCallback(ni)
|
||||
}
|
||||
|
||||
var processStartUnixNano = time.Now().UnixNano()
|
||||
|
||||
// pickDERPFallback returns a non-zero but deterministic DERP node to
|
||||
// connect to. This is only used if netcheck couldn't find the
|
||||
// nearest one (for instance, if UDP is blocked and thus STUN latency
|
||||
// checks aren't working).
|
||||
func (c *Conn) pickDERPFallback() int {
|
||||
c.derpMu.Lock()
|
||||
defer c.derpMu.Unlock()
|
||||
|
||||
if c.myDerp != 0 {
|
||||
// If we already had one in the past, stay on it.
|
||||
return c.myDerp
|
||||
}
|
||||
|
||||
if len(derpNodeID) == 0 {
|
||||
// No DERP nodes registered.
|
||||
return 0
|
||||
}
|
||||
|
||||
h := fnv.New64()
|
||||
h.Write([]byte(fmt.Sprintf("%p/%d", c, processStartUnixNano))) // arbitrary
|
||||
return derpNodeID[rand.New(rand.NewSource(int64(h.Sum64()))).Intn(len(derpNodeID))]
|
||||
}
|
||||
|
||||
// callNetInfoCallback calls the NetInfo callback (if previously
|
||||
// registered with SetNetInfoCallback) if ni has substantially changed
|
||||
// since the last state.
|
||||
//
|
||||
// callNetInfoCallback takes ownership of ni.
|
||||
func (c *Conn) callNetInfoCallback(ni *tailcfg.NetInfo) {
|
||||
c.netInfoMu.Lock()
|
||||
defer c.netInfoMu.Unlock()
|
||||
if ni.BasicallyEqual(c.netInfoLast) {
|
||||
return
|
||||
}
|
||||
c.netInfoLast = ni
|
||||
if c.netInfoFunc != nil {
|
||||
c.logf("netInfo update: %+v", ni)
|
||||
go c.netInfoFunc(ni)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) SetNetInfoCallback(fn func(*tailcfg.NetInfo)) {
|
||||
if fn == nil {
|
||||
panic("nil NetInfoCallback")
|
||||
}
|
||||
c.netInfoMu.Lock()
|
||||
last := c.netInfoLast
|
||||
c.netInfoFunc = fn
|
||||
c.netInfoMu.Unlock()
|
||||
|
||||
if last != nil {
|
||||
fn(last)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) setNearestDerp(derpNum int) (changed bool) {
|
||||
c.derpMu.Lock()
|
||||
defer c.derpMu.Unlock()
|
||||
@@ -236,8 +339,7 @@ func (c *Conn) setNearestDerp(derpNum int) (changed bool) {
|
||||
|
||||
// determineEndpoints returns the machine's endpoint addresses. It
|
||||
// does a STUN lookup to determine its public address.
|
||||
func (c *Conn) determineEndpoints(ctx context.Context) (nearestDerp int, ipPorts []string, err error) {
|
||||
nearestDerp = derpNYC // for now
|
||||
func (c *Conn) determineEndpoints(ctx context.Context) (ipPorts []string, err error) {
|
||||
var (
|
||||
alreadyMu sync.Mutex
|
||||
already = make(map[string]bool) // endpoint -> true
|
||||
@@ -265,7 +367,7 @@ func (c *Conn) determineEndpoints(ctx context.Context) (nearestDerp int, ipPorts
|
||||
c.stunReceiveFunc.Store(s.Receive)
|
||||
|
||||
if err := s.Run(ctx); err != nil {
|
||||
return 0, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.ignoreSTUNPackets()
|
||||
@@ -273,7 +375,7 @@ func (c *Conn) determineEndpoints(ctx context.Context) (nearestDerp int, ipPorts
|
||||
if localAddr := c.pconn.LocalAddr(); localAddr.IP.IsUnspecified() {
|
||||
ips, loopback, err := interfaces.LocalAddresses()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
return nil, err
|
||||
}
|
||||
reason := "localAddresses"
|
||||
if len(ips) == 0 {
|
||||
@@ -303,7 +405,7 @@ func (c *Conn) determineEndpoints(ctx context.Context) (nearestDerp int, ipPorts
|
||||
// The STUN address(es) are always first so that legacy wireguard
|
||||
// can use eps[0] as its only known endpoint address (although that's
|
||||
// obviously non-ideal).
|
||||
return nearestDerp, eps, nil
|
||||
return eps, nil
|
||||
}
|
||||
|
||||
func stringsEqual(x, y []string) bool {
|
||||
|
||||
Reference in New Issue
Block a user