diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index a1a4b6616..9b489a282 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -5920,19 +5920,24 @@ func (b *LocalBackend) SuggestExitNode() (*tailcfg.StableNodeID, error) { return nil, errors.New("no netmap") } peers := netMap.Peers - lastReport := b.MagicConn().GetLastNetcheckReport() + lastReport := b.MagicConn().GetLastNetcheckReport(b.ctx) var fastestRegionLatency = time.Duration(math.MaxInt64) var preferredExitNodeID tailcfg.StableNodeID peerRegionMap := make(map[int][]tailcfg.NodeView) + b.logf("preferred self node derp %v", lastReport.PreferredDERP) + b.logf("preferred self node derp name %v", netMap.DERPMap.Regions[lastReport.PreferredDERP]) + var mullvadCandidates []*tailcfg.NodeView for _, peer := range peers { if online := peer.Online(); online != nil && !*online { continue } - if peer.Hostinfo().Location() != nil { - b.logf("location %v %v", peer.Hostinfo().Location().Longitude, peer.Hostinfo().Location().Latitude) - } if tsaddr.ContainsExitRoutes(peer.AllowedIPs()) { ipp, _ := netip.ParseAddrPort(peer.DERP()) + if peer.DERP() == "" { + if peer.Hostinfo().Location().Country == "USA" { + mullvadCandidates = append(mullvadCandidates, &peer) + } + } regionID := int(ipp.Port()) regionLatency, ok := lastReport.RegionLatency[regionID] peerRegionMap[regionID] = append(peerRegionMap[regionID], peer) @@ -5948,6 +5953,8 @@ func (b *LocalBackend) SuggestExitNode() (*tailcfg.StableNodeID, error) { } } b.logf("self derp %v", b.netMap.SelfNode.DERP()) + result := b.MagicConn().MeasureNodeICMPLatency(b.ctx, mullvadCandidates) + b.logf("result %v", result) ipp, _ := netip.ParseAddrPort(netMap.SelfNode.DERP()) selfDerpRegionID := int(ipp.Port()) b.logf("self derp region id %v", selfDerpRegionID) diff --git a/net/netcheck/netcheck.go b/net/netcheck/netcheck.go index 0cea345c3..8b1a4ae85 100644 --- a/net/netcheck/netcheck.go +++ b/net/netcheck/netcheck.go @@ -1730,3 +1730,48 @@ var ( metricSTUNRecv6 = clientmetric.NewCounter("netcheck_stun_recv_ipv6") metricHTTPSend = clientmetric.NewCounter("netcheck_https_measure") ) + +func (c *Client) MeasureICMPLatency(ctx context.Context, candidates []*tailcfg.NodeView) map[*tailcfg.NodeView]time.Duration { + p := ping.New(ctx, c.logf, netns.Listener(c.logf, c.NetMon)) + defer p.Close() + var wg sync.WaitGroup + var results map[*tailcfg.NodeView]time.Duration + wg.Add(len(candidates)) + for _, candidate := range candidates { + go func(candidate *tailcfg.NodeView) { + defer wg.Done() + if d, err := c.measureNodeICMPLatency(ctx, candidate, p); err != nil { + c.logf("error %v node %v", err, candidate.Name()) + } else { + results[candidate] = d + c.logf("results %v", results) + } + }(candidate) + } + + wg.Wait() + return results +} + +func (c *Client) measureNodeICMPLatency(ctx context.Context, node *tailcfg.NodeView, p *ping.Pinger) (time.Duration, error) { + // Get the IPAddr by asking for the UDP address that we would use for + // STUN and then using that IP. + // + // TODO(andrew-d): this is a bit ugly + var nodeAddr netip.Addr + for i := range node.Addresses().LenIter() { + p := node.Addresses().At(i) + if p.Addr().Is4() { + nodeAddr = p.Addr() + break + } + } + + addr := &net.IPAddr{ + IP: net.IP(nodeAddr.AsSlice()), + Zone: nodeAddr.Zone(), + } + // Use the unique node.Name field as the packet data to reduce the + // likelihood that we get a mismatched echo response. + return p.Send(ctx, addr, []byte(node.Name())) +} diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 7912e6256..aca47c584 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -3009,7 +3009,15 @@ func getPeerMTUsProbedMetric(mtu tstun.WireMTU) *clientmetric.Metric { return mm } -func (c *Conn) GetLastNetcheckReport() *netcheck.Report { - report, _ := c.updateNetInfo(c.connCtx) - return report +func (c *Conn) GetLastNetcheckReport(ctx context.Context) *netcheck.Report { + nr, err := c.updateNetInfo(ctx) + if err != nil { + c.logf("magicsock.Conn.determineEndpoints: updateNetInfo: %v", err) + return nil + } + return nr +} + +func (c *Conn) MeasureNodeICMPLatency(ctx context.Context, candidates []*tailcfg.NodeView) map[*tailcfg.NodeView]time.Duration { + return c.netChecker.MeasureICMPLatency(ctx, candidates) }