2023-01-27 21:37:20 +00:00
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
2021-07-13 05:04:16 +00:00
// The derpprobe binary probes derpers.
2023-01-27 14:49:50 +00:00
package main
2021-07-13 05:04:16 +00:00
import (
"flag"
"fmt"
"html"
"io"
"log"
"net/http"
"sort"
"time"
2023-01-27 14:49:50 +00:00
"tailscale.com/prober"
"tailscale.com/tsweb"
2024-04-02 18:55:33 +00:00
"tailscale.com/version"
2021-07-13 05:04:16 +00:00
)
var (
2024-06-06 19:30:54 +00:00
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" )
2024-04-02 18:55:33 +00:00
versionFlag = flag . Bool ( "version" , false , "print version and exit" )
2024-02-28 20:27:44 +00:00
listen = flag . String ( "listen" , ":8030" , "HTTP listen address" )
probeOnce = flag . Bool ( "once" , false , "probe once and print results, then exit; ignores the listen flag" )
spread = flag . Bool ( "spread" , true , "whether to spread probing over time" )
interval = flag . Duration ( "interval" , 15 * time . Second , "probe interval" )
meshInterval = flag . Duration ( "mesh-interval" , 15 * time . Second , "mesh probe interval" )
stunInterval = flag . Duration ( "stun-interval" , 15 * time . Second , "STUN probe interval" )
tlsInterval = flag . Duration ( "tls-interval" , 15 * time . Second , "TLS probe interval" )
bwInterval = flag . Duration ( "bw-interval" , 0 , "bandwidth probe interval (0 = no bandwidth probing)" )
bwSize = flag . Int64 ( "bw-probe-size-bytes" , 1_000_000 , "bandwidth probe size" )
2021-07-13 05:04:16 +00:00
)
func main ( ) {
flag . Parse ( )
2024-04-02 18:55:33 +00:00
if * versionFlag {
fmt . Println ( version . Long ( ) )
return
}
2022-03-01 04:13:33 +00:00
2023-04-03 10:35:12 +00:00
p := prober . New ( ) . WithSpread ( * spread ) . WithOnce ( * probeOnce ) . WithMetricNamespace ( "derpprobe" )
2024-02-28 20:27:44 +00:00
opts := [ ] prober . DERPOpt {
prober . WithMeshProbing ( * meshInterval ) ,
prober . WithSTUNProbing ( * stunInterval ) ,
prober . WithTLSProbing ( * tlsInterval ) ,
}
if * bwInterval > 0 {
opts = append ( opts , prober . WithBandwidthProbing ( * bwInterval , * bwSize ) )
}
dp , err := prober . DERP ( p , * derpMapURL , opts ... )
2023-01-27 14:49:50 +00:00
if err != nil {
log . Fatal ( err )
}
p . Run ( "derpmap-probe" , * interval , nil , dp . ProbeMap )
2022-03-01 04:13:33 +00:00
2022-11-24 00:09:24 +00:00
if * probeOnce {
2023-01-27 14:49:50 +00:00
log . Printf ( "Waiting for all probes (may take up to 1m)" )
p . Wait ( )
st := getOverallStatus ( p )
2022-11-24 00:09:24 +00:00
for _ , s := range st . good {
log . Printf ( "good: %s" , s )
}
for _ , s := range st . bad {
log . Printf ( "bad: %s" , s )
}
return
}
2023-01-27 14:49:50 +00:00
mux := http . NewServeMux ( )
tsweb . Debugger ( mux )
mux . HandleFunc ( "/" , http . HandlerFunc ( serveFunc ( p ) ) )
2024-02-28 20:27:44 +00:00
log . Printf ( "Listening on %s" , * listen )
2023-01-27 14:49:50 +00:00
log . Fatal ( http . ListenAndServe ( * listen , mux ) )
2022-01-26 19:58:56 +00:00
}
2021-07-13 05:04:16 +00:00
type overallStatus struct {
good , bad [ ] string
}
2022-03-16 23:27:57 +00:00
func ( st * overallStatus ) addBadf ( format string , a ... any ) {
2021-07-13 05:04:16 +00:00
st . bad = append ( st . bad , fmt . Sprintf ( format , a ... ) )
}
2022-03-16 23:27:57 +00:00
func ( st * overallStatus ) addGoodf ( format string , a ... any ) {
2021-07-13 05:04:16 +00:00
st . good = append ( st . good , fmt . Sprintf ( format , a ... ) )
}
2023-01-27 14:49:50 +00:00
func getOverallStatus ( p * prober . Prober ) ( o overallStatus ) {
for p , i := range p . ProbeInfo ( ) {
if i . End . IsZero ( ) {
// Do not show probes that have not finished yet.
2022-01-26 19:58:56 +00:00
continue
}
2023-01-27 14:49:50 +00:00
if i . Result {
o . addGoodf ( "%s: %s" , p , i . Latency )
} else {
o . addBadf ( "%s: %s" , p , i . Error )
2022-01-26 19:58:56 +00:00
}
}
2023-01-27 14:49:50 +00:00
sort . Strings ( o . bad )
sort . Strings ( o . good )
2021-07-13 05:04:16 +00:00
return
}
2023-01-27 14:49:50 +00:00
func serveFunc ( p * prober . Prober ) func ( w http . ResponseWriter , r * http . Request ) {
return func ( w http . ResponseWriter , r * http . Request ) {
st := getOverallStatus ( p )
summary := "All good"
if ( float64 ( len ( st . bad ) ) / float64 ( len ( st . bad ) + len ( st . good ) ) ) > 0.25 {
// Returning a 500 allows monitoring this server externally and configuring
// an alert on HTTP response code.
w . WriteHeader ( 500 )
summary = fmt . Sprintf ( "%d problems" , len ( st . bad ) )
2021-07-13 05:04:16 +00:00
}
2022-01-26 19:58:56 +00:00
2023-01-27 14:49:50 +00:00
io . WriteString ( w , "<html><head><style>.bad { font-weight: bold; color: #700; }</style></head>\n" )
fmt . Fprintf ( w , "<body><h1>derp probe</h1>\n%s:<ul>" , summary )
for _ , s := range st . bad {
fmt . Fprintf ( w , "<li class=bad>%s</li>\n" , html . EscapeString ( s ) )
2021-07-13 05:04:16 +00:00
}
2023-01-27 14:49:50 +00:00
for _ , s := range st . good {
fmt . Fprintf ( w , "<li>%s</li>\n" , html . EscapeString ( s ) )
2021-07-13 05:04:16 +00:00
}
2023-01-27 14:49:50 +00:00
io . WriteString ( w , "</ul></body></html>\n" )
2021-07-13 05:04:16 +00:00
}
}