summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJames Tucker <james@tailscale.com>2023-07-19 16:53:38 -0700
committerJames Tucker <james@tailscale.com>2023-07-20 15:00:05 -0700
commitdc59464731fdeb62eb25342a6c67f27a0ff08903 (patch)
treedcdb2904a832fd3752abd88cdd555bd257356b73
parentbec9815f028a06311ea0ed28eb9ae98c167c1a9c (diff)
downloadtailscale-raggi/stunc.tar.xz
tailscale-raggi/stunc.zip
cmd/stunc: add features for imperative debugging of stun servicesraggi/stunc
Add various features more useful for confirmation/diagnosis work. - Probes repeat forever, reporting results one per line - Repetition is approximately isochronous at the given rate - Either a host name or an address can be given - The timeout is tuneable - Keep going after a timeout occurs Updates tailscale/corp#13378 Signed-off-by: James Tucker <james@tailscale.com>
-rw-r--r--cmd/stunc/stunc.go118
1 files changed, 93 insertions, 25 deletions
diff --git a/cmd/stunc/stunc.go b/cmd/stunc/stunc.go
index f140ede19..64e9394a7 100644
--- a/cmd/stunc/stunc.go
+++ b/cmd/stunc/stunc.go
@@ -5,53 +5,121 @@
package main
import (
+ "encoding/json"
+ "flag"
+ "fmt"
"log"
"net"
+ "net/netip"
"os"
+ "time"
"tailscale.com/net/stun"
)
-func main() {
- log.SetFlags(0)
+var (
+ rate = flag.Duration("rate", time.Second, "rate at which to send probes (0 means as fast as possible)")
+ timeout = flag.Duration("timeout", time.Second, "time to wait for a response")
+ reuse = flag.Bool("reuse", true, "reuse the same UDP socket for each probe")
+ jsonout = flag.Bool("json", false, "output in JSON format (default is human-readable)")
+)
- if len(os.Args) != 2 {
- log.Fatalf("usage: %s <hostname>", os.Args[0])
+func main() {
+ flag.Usage = func() {
+ fmt.Printf("usage: %s [flags] <hostname>", os.Args[0])
+ flag.PrintDefaults()
}
- host := os.Args[1]
- uaddr, err := net.ResolveUDPAddr("udp", host+":3478")
- if err != nil {
- log.Fatal(err)
- }
- c, err := net.ListenUDP("udp", nil)
- if err != nil {
- log.Fatal(err)
+ flag.Parse()
+ if flag.NArg() != 1 {
+ flag.Usage()
+ os.Exit(2)
}
- txID := stun.NewTxID()
- req := stun.Request(txID)
+ host := flag.Args()[0]
- _, err = c.WriteToUDP(req, uaddr)
+ naddr, err := net.ResolveIPAddr("ip", host)
if err != nil {
log.Fatal(err)
}
- var buf [1024]byte
- n, raddr, err := c.ReadFromUDPAddrPort(buf[:])
+ nip, err := netip.ParseAddr(naddr.String())
if err != nil {
log.Fatal(err)
}
- tid, saddr, err := stun.ParseResponse(buf[:n])
- if err != nil {
- log.Fatal(err)
+ uaddr := netip.AddrPortFrom(nip, 3478)
+
+ var c *net.UDPConn
+
+ var print = func(result map[string]string) {
+ r := result["status"]
+ if result["status"] == "ok" {
+ r = fmt.Sprintf("%s; %s < %s in %s", result["status"], result["from"], result["stun"], result["dur"])
+ }
+ fmt.Printf("%s > %s; %s\n", result["local"], result["to"], r)
}
- if tid != txID {
- log.Fatalf("txid mismatch: got %v, want %v", tid, txID)
+ if *jsonout {
+ j := json.NewEncoder(os.Stdout)
+ print = func(result map[string]string) {
+ if err := j.Encode(result); err != nil {
+ log.Fatal(err)
+ }
+ }
}
- log.Printf("sent addr: %v", uaddr)
- log.Printf("from addr: %v", raddr)
- log.Printf("stun addr: %v", saddr)
+ for {
+ if c == nil || !*reuse {
+ if c != nil {
+ c.Close()
+ }
+
+ c, err = net.ListenUDP("udp", nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+
+ result := map[string]string{}
+ result["to"] = uaddr.String()
+ result["local"] = c.LocalAddr().String()
+
+ txID := stun.NewTxID()
+ req := stun.Request(txID)
+
+ t0 := time.Now()
+ result["at"] = t0.Format(time.RFC3339Nano)
+ _, err = c.WriteToUDPAddrPort(req, uaddr)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ c.SetReadDeadline(t0.Add(*timeout))
+ var buf [1024]byte
+ n, raddr, err := c.ReadFromUDPAddrPort(buf[:])
+ if err != nil {
+ if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
+ result["status"] = "timeout"
+ print(result)
+ continue
+ }
+ log.Fatalf("%#v", err)
+ }
+ result["from"] = raddr.String()
+ result["dur"] = time.Since(t0).String()
+
+ tid, saddr, err := stun.ParseResponse(buf[:n])
+ if err != nil {
+ log.Fatal(err)
+ }
+ result["stun"] = saddr.String()
+ if tid != txID {
+ result["status"] = "badtxid"
+ } else {
+ result["status"] = "ok"
+ }
+
+ print(result)
+ time.Sleep(time.Until(t0.Add(*rate)))
+ }
}