Merge f2a5a1aa1420fc58da87fa5f4af1ba807d87f67e into b3455fa99a5e8d07133d5140017ec7c49f032a07

This commit is contained in:
Mike O'Driscoll 2025-03-24 18:10:37 -04:00 committed by GitHub
commit 353f77b3e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 85 additions and 16 deletions

View File

@ -5,13 +5,18 @@
package main package main
import ( import (
"context"
"flag" "flag"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"os"
"path"
"path/filepath"
"sort" "sort"
"time" "time"
"github.com/tailscale/setec/client/setec"
"tailscale.com/prober" "tailscale.com/prober"
"tailscale.com/tsweb" "tailscale.com/tsweb"
"tailscale.com/version" "tailscale.com/version"
@ -20,7 +25,15 @@ import (
_ "tailscale.com/tsweb/promvarz" _ "tailscale.com/tsweb/promvarz"
) )
const meshKeyEnvVar = "TAILSCALE_DERPER_MESH_KEY"
const setecMeshKeyName = "meshkey"
func defaultSetecCacheDir() string {
return filepath.Join(os.Getenv("HOME"), ".cache", "derper-secrets")
}
var ( var (
dev = flag.Bool("dev", false, "run in localhost development mode")
derpMapURL = flag.String("derp-map", "https://login.tailscale.com/derpmap/default", "URL to DERP map (https:// or file://) or 'local' to use the local tailscaled's DERP map") derpMapURL = flag.String("derp-map", "https://login.tailscale.com/derpmap/default", "URL to DERP map (https:// or file://) or 'local' to use the local tailscaled's DERP map")
versionFlag = flag.Bool("version", false, "print version and exit") versionFlag = flag.Bool("version", false, "print version and exit")
listen = flag.String("listen", ":8030", "HTTP listen address") listen = flag.String("listen", ":8030", "HTTP listen address")
@ -36,6 +49,10 @@ var (
qdPacketsPerSecond = flag.Int("qd-packets-per-second", 0, "if greater than 0, queuing delay will be measured continuously using 260 byte packets (approximate size of a CallMeMaybe packet) sent at this rate per second") qdPacketsPerSecond = flag.Int("qd-packets-per-second", 0, "if greater than 0, queuing delay will be measured continuously using 260 byte packets (approximate size of a CallMeMaybe packet) sent at this rate per second")
qdPacketTimeout = flag.Duration("qd-packet-timeout", 5*time.Second, "queuing delay packets arriving after this period of time from being sent are treated like dropped packets and don't count toward queuing delay timings") qdPacketTimeout = flag.Duration("qd-packet-timeout", 5*time.Second, "queuing delay packets arriving after this period of time from being sent are treated like dropped packets and don't count toward queuing delay timings")
regionCodeOrID = flag.String("region-code", "", "probe only this region (e.g. 'lax' or '17'); if left blank, all regions will be probed") regionCodeOrID = flag.String("region-code", "", "probe only this region (e.g. 'lax' or '17'); if left blank, all regions will be probed")
meshPSKFile = flag.String("mesh-psk-file", "", "if non-empty, path to file containing the mesh pre-shared key file. It should contain some hex string; whitespace is trimmed.")
secretsURL = flag.String("secrets-url", "", "SETEC server URL for secrets retrieval of mesh key")
secretPrefix = flag.String("secrets-path-prefix", "prod/derp", "setec path prefix for \""+setecMeshKeyName+"\" secret for DERP mesh key")
secretsCacheDir = flag.String("secrets-cache-dir", defaultSetecCacheDir(), "directory to cache setec secrets in (required if --secrets-url is set)")
) )
func main() { func main() {
@ -51,6 +68,7 @@ func main() {
prober.WithSTUNProbing(*stunInterval), prober.WithSTUNProbing(*stunInterval),
prober.WithTLSProbing(*tlsInterval), prober.WithTLSProbing(*tlsInterval),
prober.WithQueuingDelayProbing(*qdPacketsPerSecond, *qdPacketTimeout), prober.WithQueuingDelayProbing(*qdPacketsPerSecond, *qdPacketTimeout),
prober.WithMeshKey(getMeshKey()),
} }
if *bwInterval > 0 { if *bwInterval > 0 {
opts = append(opts, prober.WithBandwidthProbing(*bwInterval, *bwSize, *bwTUNIPv4Address)) opts = append(opts, prober.WithBandwidthProbing(*bwInterval, *bwSize, *bwTUNIPv4Address))
@ -95,6 +113,49 @@ func main() {
log.Fatal(http.ListenAndServe(*listen, mux)) log.Fatal(http.ListenAndServe(*listen, mux))
} }
func getMeshKey() string {
var meshKey string
if *dev {
meshKey = os.Getenv(meshKeyEnvVar)
if meshKey == "" {
log.Printf("No mesh key specified for dev via %s\n", meshKeyEnvVar)
} else {
log.Printf("Set mesh key from %s\n", meshKeyEnvVar)
}
} else if *secretsURL != "" {
meshKeySecret := path.Join(*secretPrefix, setecMeshKeyName)
fc, err := setec.NewFileCache(*secretsCacheDir)
if err != nil {
log.Fatalf("NewFileCache: %v", err)
}
log.Printf("Setting up setec store from %q", *secretsURL)
st, err := setec.NewStore(context.Background(),
setec.StoreConfig{
Client: setec.Client{Server: *secretsURL},
Secrets: []string{
meshKeySecret,
},
Cache: fc,
})
if err != nil {
log.Fatalf("NewStore: %v", err)
}
meshKey = st.Secret(meshKeySecret).GetString()
log.Println("Got mesh key from setec store")
st.Close()
} else if *meshPSKFile != "" {
b, err := setec.StaticFile(*meshPSKFile)
if err != nil {
log.Fatalf("StaticFile failed to get key: %v", err)
}
log.Println("Got mesh key from static file")
meshKey = b.GetString()
}
return meshKey
}
type overallStatus struct { type overallStatus struct {
good, bad []string good, bad []string
} }

View File

@ -47,6 +47,7 @@ import (
type derpProber struct { type derpProber struct {
p *Prober p *Prober
derpMapURL string // or "local" derpMapURL string // or "local"
meshKey string
udpInterval time.Duration udpInterval time.Duration
meshInterval time.Duration meshInterval time.Duration
tlsInterval time.Duration tlsInterval time.Duration
@ -71,7 +72,7 @@ type derpProber struct {
udpProbeFn func(string, int) ProbeClass udpProbeFn func(string, int) ProbeClass
meshProbeFn func(string, string) ProbeClass meshProbeFn func(string, string) ProbeClass
bwProbeFn func(string, string, int64) ProbeClass bwProbeFn func(string, string, int64) ProbeClass
qdProbeFn func(string, string, int, time.Duration) ProbeClass qdProbeFn func(string, string, int, time.Duration, string) ProbeClass
sync.Mutex sync.Mutex
lastDERPMap *tailcfg.DERPMap lastDERPMap *tailcfg.DERPMap
@ -143,6 +144,12 @@ func WithRegionCodeOrID(regionCode string) DERPOpt {
} }
} }
func WithMeshKey(meshKey string) DERPOpt {
return func(d *derpProber) {
d.meshKey = meshKey
}
}
// DERP creates a new derpProber. // DERP creates a new derpProber.
// //
// If derpMapURL is "local", the DERPMap is fetched via // If derpMapURL is "local", the DERPMap is fetched via
@ -250,7 +257,7 @@ func (d *derpProber) probeMapFn(ctx context.Context) error {
wantProbes[n] = true wantProbes[n] = true
if d.probes[n] == nil { if d.probes[n] == nil {
log.Printf("adding DERP queuing delay probe for %s->%s (%s)", server.Name, to.Name, region.RegionName) log.Printf("adding DERP queuing delay probe for %s->%s (%s)", server.Name, to.Name, region.RegionName)
d.probes[n] = d.p.Run(n, -10*time.Second, labels, d.qdProbeFn(server.Name, to.Name, d.qdPacketsPerSecond, d.qdPacketTimeout)) d.probes[n] = d.p.Run(n, -10*time.Second, labels, d.qdProbeFn(server.Name, to.Name, d.qdPacketsPerSecond, d.qdPacketTimeout, d.meshKey))
} }
} }
} }
@ -284,7 +291,7 @@ func (d *derpProber) probeMesh(from, to string) ProbeClass {
} }
dm := d.lastDERPMap dm := d.lastDERPMap
return derpProbeNodePair(ctx, dm, fromN, toN) return derpProbeNodePair(ctx, dm, fromN, toN, d.meshKey)
}, },
Class: "derp_mesh", Class: "derp_mesh",
Labels: Labels{"derp_path": derpPath}, Labels: Labels{"derp_path": derpPath},
@ -308,7 +315,7 @@ func (d *derpProber) probeBandwidth(from, to string, size int64) ProbeClass {
if err != nil { if err != nil {
return err return err
} }
return derpProbeBandwidth(ctx, d.lastDERPMap, fromN, toN, size, &transferTimeSeconds, &totalBytesTransferred, d.bwTUNIPv4Prefix) return derpProbeBandwidth(ctx, d.lastDERPMap, fromN, toN, size, &transferTimeSeconds, &totalBytesTransferred, d.bwTUNIPv4Prefix, d.meshKey)
}, },
Class: "derp_bw", Class: "derp_bw",
Labels: Labels{ Labels: Labels{
@ -336,7 +343,7 @@ func (d *derpProber) probeBandwidth(from, to string, size int64) ProbeClass {
// to the queuing delay measurement and are recorded as dropped. 'from' and 'to' are // to the queuing delay measurement and are recorded as dropped. 'from' and 'to' are
// expected to be names (DERPNode.Name) of two DERP servers in the same region, // expected to be names (DERPNode.Name) of two DERP servers in the same region,
// and may refer to the same server. // and may refer to the same server.
func (d *derpProber) probeQueuingDelay(from, to string, packetsPerSecond int, packetTimeout time.Duration) ProbeClass { func (d *derpProber) probeQueuingDelay(from, to string, packetsPerSecond int, packetTimeout time.Duration, meshKey string) ProbeClass {
derpPath := "mesh" derpPath := "mesh"
if from == to { if from == to {
derpPath = "single" derpPath = "single"
@ -349,7 +356,7 @@ func (d *derpProber) probeQueuingDelay(from, to string, packetsPerSecond int, pa
if err != nil { if err != nil {
return err return err
} }
return derpProbeQueuingDelay(ctx, d.lastDERPMap, fromN, toN, packetsPerSecond, packetTimeout, &packetsDropped, qdh) return derpProbeQueuingDelay(ctx, d.lastDERPMap, fromN, toN, packetsPerSecond, packetTimeout, &packetsDropped, qdh, meshKey)
}, },
Class: "derp_qd", Class: "derp_qd",
Labels: Labels{"derp_path": derpPath}, Labels: Labels{"derp_path": derpPath},
@ -368,15 +375,15 @@ func (d *derpProber) probeQueuingDelay(from, to string, packetsPerSecond int, pa
// derpProbeQueuingDelay continuously sends data between two local DERP clients // derpProbeQueuingDelay continuously sends data between two local DERP clients
// connected to two DERP servers in order to measure queuing delays. From and to // connected to two DERP servers in order to measure queuing delays. From and to
// can be the same server. // can be the same server.
func derpProbeQueuingDelay(ctx context.Context, dm *tailcfg.DERPMap, from, to *tailcfg.DERPNode, packetsPerSecond int, packetTimeout time.Duration, packetsDropped *expvar.Float, qdh *histogram) (err error) { func derpProbeQueuingDelay(ctx context.Context, dm *tailcfg.DERPMap, from, to *tailcfg.DERPNode, packetsPerSecond int, packetTimeout time.Duration, packetsDropped *expvar.Float, qdh *histogram, meshKey string) (err error) {
// This probe uses clients with isProber=false to avoid spamming the derper // This probe uses clients with isProber=false to avoid spamming the derper
// logs with every packet sent by the queuing delay probe. // logs with every packet sent by the queuing delay probe.
fromc, err := newConn(ctx, dm, from, false) fromc, err := newConn(ctx, dm, from, false, meshKey)
if err != nil { if err != nil {
return err return err
} }
defer fromc.Close() defer fromc.Close()
toc, err := newConn(ctx, dm, to, false) toc, err := newConn(ctx, dm, to, false, meshKey)
if err != nil { if err != nil {
return err return err
} }
@ -662,15 +669,15 @@ func derpProbeUDP(ctx context.Context, ipStr string, port int) error {
// DERP clients connected to two DERP servers.If tunIPv4Address is specified, // DERP clients connected to two DERP servers.If tunIPv4Address is specified,
// probes will use a TCP connection over a TUN device at this address in order // probes will use a TCP connection over a TUN device at this address in order
// to exercise TCP-in-TCP in similar fashion to TCP over Tailscale via DERP. // to exercise TCP-in-TCP in similar fashion to TCP over Tailscale via DERP.
func derpProbeBandwidth(ctx context.Context, dm *tailcfg.DERPMap, from, to *tailcfg.DERPNode, size int64, transferTimeSeconds, totalBytesTransferred *expvar.Float, tunIPv4Prefix *netip.Prefix) (err error) { func derpProbeBandwidth(ctx context.Context, dm *tailcfg.DERPMap, from, to *tailcfg.DERPNode, size int64, transferTimeSeconds, totalBytesTransferred *expvar.Float, tunIPv4Prefix *netip.Prefix, meshKey string) (err error) {
// This probe uses clients with isProber=false to avoid spamming the derper logs with every packet // This probe uses clients with isProber=false to avoid spamming the derper logs with every packet
// sent by the bandwidth probe. // sent by the bandwidth probe.
fromc, err := newConn(ctx, dm, from, false) fromc, err := newConn(ctx, dm, from, false, meshKey)
if err != nil { if err != nil {
return err return err
} }
defer fromc.Close() defer fromc.Close()
toc, err := newConn(ctx, dm, to, false) toc, err := newConn(ctx, dm, to, false, meshKey)
if err != nil { if err != nil {
return err return err
} }
@ -700,13 +707,13 @@ func derpProbeBandwidth(ctx context.Context, dm *tailcfg.DERPMap, from, to *tail
// derpProbeNodePair sends a small packet between two local DERP clients // derpProbeNodePair sends a small packet between two local DERP clients
// connected to two DERP servers. // connected to two DERP servers.
func derpProbeNodePair(ctx context.Context, dm *tailcfg.DERPMap, from, to *tailcfg.DERPNode) (err error) { func derpProbeNodePair(ctx context.Context, dm *tailcfg.DERPMap, from, to *tailcfg.DERPNode, meshKey string) (err error) {
fromc, err := newConn(ctx, dm, from, true) fromc, err := newConn(ctx, dm, from, true, meshKey)
if err != nil { if err != nil {
return err return err
} }
defer fromc.Close() defer fromc.Close()
toc, err := newConn(ctx, dm, to, true) toc, err := newConn(ctx, dm, to, true, meshKey)
if err != nil { if err != nil {
return err return err
} }
@ -1104,7 +1111,7 @@ func derpProbeBandwidthTUN(ctx context.Context, transferTimeSeconds, totalBytesT
return nil return nil
} }
func newConn(ctx context.Context, dm *tailcfg.DERPMap, n *tailcfg.DERPNode, isProber bool) (*derphttp.Client, error) { func newConn(ctx context.Context, dm *tailcfg.DERPMap, n *tailcfg.DERPNode, isProber bool, meshKey string) (*derphttp.Client, error) {
// To avoid spamming the log with regular connection messages. // To avoid spamming the log with regular connection messages.
l := logger.Filtered(log.Printf, func(s string) bool { l := logger.Filtered(log.Printf, func(s string) bool {
return !strings.Contains(s, "derphttp.Client.Connect: connecting to") return !strings.Contains(s, "derphttp.Client.Connect: connecting to")
@ -1120,6 +1127,7 @@ func newConn(ctx context.Context, dm *tailcfg.DERPMap, n *tailcfg.DERPNode, isPr
} }
}) })
dc.IsProber = isProber dc.IsProber = isProber
dc.MeshKey = meshKey
err := dc.Connect(ctx) err := dc.Connect(ctx)
if err != nil { if err != nil {
return nil, err return nil, err