feat(probe): add diagnostic probe for mDNS advertisement and WebSocket communication
This commit is contained in:
92
cmd/probe/main.go
Normal file
92
cmd/probe/main.go
Normal file
@@ -0,0 +1,92 @@
|
||||
// Diagnostic probe: advertise _sendspin._tcp via mDNS, listen on /sendspin,
|
||||
// and log exactly what the server (Music Assistant) sends after it dials us —
|
||||
// WITHOUT sending anything first. Reveals whether MA expects to speak first
|
||||
// (role flip) or waits for our client/hello.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Sendspin/sendspin-go/pkg/discovery"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func main() {
|
||||
name := flag.String("name", hostname(), "advertised name")
|
||||
port := flag.Int("port", 8928, "listen + mDNS port")
|
||||
speakFirst := flag.Bool("speak-first", false, "send a server/hello before reading (test role-flip hypothesis)")
|
||||
flag.Parse()
|
||||
|
||||
disc := discovery.NewManager(discovery.Config{ServiceName: *name, Port: *port})
|
||||
if err := disc.Advertise(); err != nil {
|
||||
log.Printf("mdns advertise: %v", err)
|
||||
} else {
|
||||
defer disc.Stop()
|
||||
}
|
||||
|
||||
upgrader := websocket.Upgrader{CheckOrigin: func(*http.Request) bool { return true }}
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/sendspin", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("=== incoming dial from %s (headers: subproto=%q origin=%q UA=%q) ===",
|
||||
r.RemoteAddr, r.Header.Get("Sec-WebSocket-Protocol"), r.Header.Get("Origin"), r.Header.Get("User-Agent"))
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Printf("upgrade failed: %v", err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if *speakFirst {
|
||||
hello := `{"type":"server/hello","payload":{"server_id":"probe","name":"probe","version":1,"active_roles":["player@v1"],"connection_reason":"discovery"}}`
|
||||
log.Printf("-> sending server/hello: %s", hello)
|
||||
if err := conn.WriteMessage(websocket.TextMessage, []byte(hello)); err != nil {
|
||||
log.Printf("write failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("listening for first message from server (10s)...")
|
||||
for {
|
||||
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
mt, data, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Printf("read ended: %v", err)
|
||||
if ce, ok := err.(*websocket.CloseError); ok {
|
||||
log.Printf("CLOSE code=%d text=%q", ce.Code, ce.Text)
|
||||
}
|
||||
return
|
||||
}
|
||||
if mt == websocket.BinaryMessage {
|
||||
log.Printf("<- SERVER binary %d bytes", len(data))
|
||||
} else {
|
||||
log.Printf("<- SERVER text: %s", string(data))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
srv := &http.Server{Addr: fmt.Sprintf(":%d", *port), Handler: mux}
|
||||
go func() {
|
||||
log.Printf("probe listening on :%d/sendspin as %q", *port, *name)
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("listen: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
|
||||
<-sig
|
||||
}
|
||||
|
||||
func hostname() string {
|
||||
h, _ := os.Hostname()
|
||||
if h == "" {
|
||||
return "sendspin-probe"
|
||||
}
|
||||
return h + "-probe"
|
||||
}
|
||||
Reference in New Issue
Block a user