Files
tailscale/feature/captiveportal/netcheckhook/netcheckhook.go
Simon Law 0e84b4a3a0 tailcfg: replace int with DERPRegionID for additional type safety (#20646)
Historically, when DERP regions were switched away from strings to
numeric identifiers in PR #14641, tailcfg.Node.HomeDERP was declared
as an int instead of its own type.

This PR declares a new tailcfg.DERPRegionID type, represented by an
int64, and converts the following fields to use this type:

- netcheck.Report.PreferredDERP
- netcheck.Report.RegionLatency
- netcheck.Report.RegionV4Latency
- netcheck.Report.RegionV6Latency
- tailcfg.DERPHomeParams.RegionScore
- tailcfg.DERPMap.Regions
- tailcfg.DERPNode.RegionID
- tailcfg.DERPRegion.RegionID
- tailcfg.NetInfo.PreferredDERP
- tailcfg.Node.HomeDERP
- tailcfg.PeerChange.DERPRegion
- tailcfg.PingResponse.DERPRegionID

Note that the original field was an int, while the new field is backed
by an int64. This change makes DERPRegionID the same size on both
32-bit and 64-bit architectures.

Fixes: #20165

Change-Id: Ic6f795a6d791dd16f756f246d5a02085443e212f

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-08-19 16:25:50 -07:00

68 lines
1.9 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
// Package netcheckhook makes netcheck probe for captive portals during
// full reports. It does so as a side effect of being imported, by
// installing a netcheck hook from init.
package netcheckhook
import (
"context"
"log"
"time"
"tailscale.com/net/captivedetection"
"tailscale.com/net/netcheck"
"tailscale.com/tailcfg"
)
func init() {
netcheck.HookStartCaptivePortalDetection.Set(startCaptivePortalDetection)
}
// captivePortalDelay is the duration to wait after starting a netcheck before
// also probing for a captive portal, to let UDP STUN finish first and avoid
// the probe if it's unnecessary. Chosen semi-arbitrarily.
const captivePortalDelay = 200 * time.Millisecond
func startCaptivePortalDetection(ctx context.Context, c *netcheck.Client, dm *tailcfg.DERPMap, preferredDERP tailcfg.DERPRegionID, setCaptivePortal func(bool)) (done <-chan struct{}, stop func()) {
logf := c.Logf
if logf == nil {
logf = log.Printf
}
// This goroutine can't be tracked by the wait group that
// netcheck.GetReport uses for its probes, since GetReport doesn't
// wait for that group to finish before returning and we'd get a
// data race. Instead, completion is signaled by closing ch, which
// GetReport receives as the done channel.
ch := make(chan struct{})
tmr := time.AfterFunc(captivePortalDelay, func() {
defer close(ch)
d := captivedetection.NewDetector(logf)
found := d.Detect(ctx, c.NetMon, dm, preferredDERP)
setCaptivePortal(found)
})
if c.Verbose {
// Don't cancel our captive portal check if we're
// explicitly doing a verbose netcheck.
return ch, func() {}
}
stop = func() {
if tmr.Stop() {
// Stopped successfully; need to close the
// signal channel ourselves.
close(ch)
return
}
// Did not stop; do nothing and it'll finish by itself
// and close the signal channel.
}
return ch, stop
}