From aa291320f5638ab411cc5580524caf5f8531cf14 Mon Sep 17 00:00:00 2001 From: Deivid Soto Date: Wed, 6 May 2026 09:40:37 +0200 Subject: [PATCH] test(wstracker-probe): standalone Go binary to verify WSS tracker reachability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tiny `go run ./cmd/wstracker-probe` that spins up an anacrolix/torrent Client with WebRTC enabled, advertises a random info_hash to the given WSS tracker, and reports via Callbacks.StatusUpdated whether the announce round-trip succeeded. Used as the production smoke for unarr ↔ wss://tracker.torrentclaw.com: $ /tmp/wstracker-probe -tracker wss://tracker.torrentclaw.com -timeout 30s [probe] tracker=wss://tracker.torrentclaw.com info_hash=e978df8d... timeout=30s [probe] tracker connected: wss://tracker.torrentclaw.com [probe] tracker announce OK: wss://tracker.torrentclaw.com ih=e978df8d... [probe] OK — tracker announce succeeded Disables TCP/uTP/DHT/IPv6/UPnP — only the WS tracker path matters here. Exit codes: 0 success, 1 announce error, 2 timeout. --- cmd/wstracker-probe/main.go | 117 ++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 cmd/wstracker-probe/main.go diff --git a/cmd/wstracker-probe/main.go b/cmd/wstracker-probe/main.go new file mode 100644 index 0000000..660e297 --- /dev/null +++ b/cmd/wstracker-probe/main.go @@ -0,0 +1,117 @@ +// wstracker-probe — connects to a WebSocket BitTorrent tracker, advertises +// a fake info_hash, and reports whether the announce succeeds. +// +// Usage: +// +// go run ./cmd/wstracker-probe -tracker wss://tracker.torrentclaw.com +// +// Exit code 0 on TrackerAnnounceSuccessful, 1 on timeout/error. +package main + +import ( + "context" + "crypto/rand" + "flag" + "fmt" + "log" + "os" + "time" + + alog "github.com/anacrolix/log" + "github.com/anacrolix/torrent" + "github.com/anacrolix/torrent/storage" + "github.com/pion/webrtc/v4" +) + +func main() { + tracker := flag.String("tracker", "wss://tracker.torrentclaw.com", "WSS tracker URL to probe") + timeout := flag.Duration("timeout", 30*time.Second, "max wait for successful announce") + flag.Parse() + + tmp, err := os.MkdirTemp("", "wstracker-probe-*") + if err != nil { + log.Fatalf("temp dir: %v", err) + } + defer os.RemoveAll(tmp) + + cfg := torrent.NewDefaultClientConfig() + cfg.DataDir = tmp + cfg.DefaultStorage = storage.NewMMap(tmp) + cfg.Seed = false + cfg.NoUpload = false + cfg.DisableTCP = true + cfg.DisableUTP = true + cfg.DisableIPv6 = true + cfg.NoDHT = true + cfg.NoDefaultPortForwarding = true + cfg.ListenPort = 0 + cfg.Logger = alog.Default.FilterLevel(alog.Critical) + cfg.DisableWebtorrent = false + cfg.ICEServerList = []webrtc.ICEServer{ + {URLs: []string{"stun:stun.l.google.com:19302"}}, + } + + annSuccess := make(chan struct{}, 1) + annError := make(chan error, 1) + cfg.Callbacks.StatusUpdated = append( + cfg.Callbacks.StatusUpdated, + func(e torrent.StatusUpdatedEvent) { + switch e.Event { //nolint:exhaustive // peer events are noise for tracker probe + case torrent.TrackerConnected: + if e.Error != nil { + fmt.Printf("[probe] tracker connect FAILED: %v\n", e.Error) + } else { + fmt.Printf("[probe] tracker connected: %s\n", e.Url) + } + case torrent.TrackerAnnounceSuccessful: + fmt.Printf("[probe] tracker announce OK: %s ih=%s\n", e.Url, e.InfoHash) + select { + case annSuccess <- struct{}{}: + default: + } + case torrent.TrackerAnnounceError: + fmt.Printf("[probe] tracker announce ERROR: %s ih=%s err=%v\n", e.Url, e.InfoHash, e.Error) + select { + case annError <- e.Error: + default: + } + case torrent.TrackerDisconnected: + fmt.Printf("[probe] tracker disconnected: %s err=%v\n", e.Url, e.Error) + } + }, + ) + + client, err := torrent.NewClient(cfg) + if err != nil { + log.Fatalf("create torrent client: %v", err) + } + defer client.Close() + + var ih [20]byte + if _, err := rand.Read(ih[:]); err != nil { + log.Fatalf("random info_hash: %v", err) + } + magnet := fmt.Sprintf("magnet:?xt=urn:btih:%x&tr=%s", ih, *tracker) + fmt.Printf("[probe] tracker=%s info_hash=%x timeout=%s\n", *tracker, ih, *timeout) + + t, err := client.AddMagnet(magnet) + if err != nil { + log.Fatalf("add magnet: %v", err) + } + defer t.Drop() + + ctx, cancel := context.WithTimeout(context.Background(), *timeout) + defer cancel() + + select { + case <-annSuccess: + fmt.Println("[probe] OK — tracker announce succeeded") + os.Exit(0) + case err := <-annError: + fmt.Printf("[probe] FAIL — tracker announce error: %v\n", err) + os.Exit(1) + case <-ctx.Done(): + fmt.Printf("[probe] FAIL — timeout after %s\n", *timeout) + os.Exit(2) + } +}