mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-20 17:46:19 +00:00
303 lines
7.5 KiB
Go
303 lines
7.5 KiB
Go
package discovery
|
|
|
|
import (
|
|
"log"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
func TestDNSDiscovery_Interception(t *testing.T) {
|
|
serviceIP := "192.168.1.100"
|
|
upstreamDNS := "8.8.8.8"
|
|
d := NewDNSDiscovery(upstreamDNS, serviceIP)
|
|
|
|
// Test intercepting Bose service
|
|
m := new(dns.Msg)
|
|
m.SetQuestion("api.bose.com.", dns.TypeA)
|
|
|
|
rw := &mockResponseWriter{}
|
|
d.ServeDNS(rw, m)
|
|
|
|
if rw.msg == nil {
|
|
t.Fatal("Expected a response message, got nil")
|
|
}
|
|
|
|
if len(rw.msg.Answer) == 0 {
|
|
t.Fatal("Expected an answer in the response")
|
|
}
|
|
|
|
if a, ok := rw.msg.Answer[0].(*dns.A); ok {
|
|
if a.A.String() != serviceIP {
|
|
t.Errorf("Expected intercepted IP %s, got %s", serviceIP, a.A.String())
|
|
}
|
|
} else {
|
|
t.Errorf("Expected A record, got %T", rw.msg.Answer[0])
|
|
}
|
|
|
|
// Test aftertouch.test
|
|
m2 := new(dns.Msg)
|
|
m2.SetQuestion("aftertouch.test.", dns.TypeA)
|
|
rw2 := &mockResponseWriter{}
|
|
d.ServeDNS(rw2, m2)
|
|
|
|
if rw2.msg == nil || len(rw2.msg.Answer) == 0 {
|
|
t.Fatal("Expected response for aftertouch.test")
|
|
}
|
|
|
|
if a, ok := rw2.msg.Answer[0].(*dns.A); ok {
|
|
if a.A.String() != serviceIP {
|
|
t.Errorf("Expected intercepted IP %s for aftertouch.test, got %s", serviceIP, a.A.String())
|
|
}
|
|
} else {
|
|
t.Errorf("Expected A record for aftertouch.test, got %T", rw2.msg.Answer[0])
|
|
}
|
|
}
|
|
|
|
func TestDNSDiscovery_Forwarding(t *testing.T) {
|
|
// This test is harder because it needs a real upstream or a mock.
|
|
// For now, let's just test that it calls forward and record.
|
|
serviceIP := "192.168.1.100"
|
|
upstreamDNS := "127.0.0.1:5353" // Use a port that is likely closed or we can mock
|
|
d := NewDNSDiscovery(upstreamDNS, serviceIP)
|
|
|
|
m := new(dns.Msg)
|
|
m.SetQuestion("google.com.", dns.TypeA)
|
|
|
|
rw := &mockResponseWriter{}
|
|
|
|
// Start a mock upstream DNS server
|
|
mux := dns.NewServeMux()
|
|
mux.HandleFunc("google.com.", func(w dns.ResponseWriter, r *dns.Msg) {
|
|
m := new(dns.Msg)
|
|
m.SetReply(r)
|
|
_ = w.WriteMsg(m)
|
|
})
|
|
ts := &dns.Server{Addr: "127.0.0.1:5353", Net: "udp", Handler: mux, ReadTimeout: 100 * time.Millisecond, WriteTimeout: 100 * time.Millisecond}
|
|
go func() {
|
|
_ = ts.ListenAndServe()
|
|
}()
|
|
defer func() { _ = ts.Shutdown() }()
|
|
|
|
// Give it a moment to start
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// We expect forward to succeed
|
|
d.ServeDNS(rw, m)
|
|
|
|
d.mu.RLock()
|
|
host, exists := d.discovered["google.com"]
|
|
d.mu.RUnlock()
|
|
|
|
if !exists {
|
|
t.Error("Expected google.com to be recorded in discovery")
|
|
}
|
|
if host.IsBoseService {
|
|
t.Error("google.com should not be identified as a Bose service")
|
|
}
|
|
}
|
|
|
|
func TestDNSDiscovery_StartTCP(t *testing.T) {
|
|
serviceIP := "192.168.1.100"
|
|
upstreamDNS := "8.8.8.8"
|
|
d := NewDNSDiscovery(upstreamDNS, serviceIP)
|
|
|
|
addr := "127.0.0.1:5354"
|
|
go func() {
|
|
_ = d.Start(addr)
|
|
}()
|
|
|
|
// Give it a moment to start
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Test TCP resolution
|
|
m := new(dns.Msg)
|
|
m.SetQuestion("api.bose.com.", dns.TypeA)
|
|
|
|
c := new(dns.Client)
|
|
c.Net = "tcp"
|
|
in, _, err := c.Exchange(m, addr)
|
|
if err != nil {
|
|
t.Fatalf("Failed to exchange via TCP: %v", err)
|
|
}
|
|
|
|
if len(in.Answer) == 0 {
|
|
t.Fatal("Expected answer in TCP response")
|
|
}
|
|
|
|
if a, ok := in.Answer[0].(*dns.A); ok {
|
|
if a.A.String() != serviceIP {
|
|
t.Errorf("Expected intercepted IP %s via TCP, got %s", serviceIP, a.A.String())
|
|
}
|
|
} else {
|
|
t.Errorf("Expected A record via TCP, got %T", in.Answer[0])
|
|
}
|
|
|
|
// Test Shutdown
|
|
err = d.Shutdown()
|
|
if err != nil {
|
|
t.Errorf("Shutdown failed: %v", err)
|
|
}
|
|
|
|
// Verify it's really shut down by trying to connect
|
|
_, _, err = c.Exchange(m, addr)
|
|
if err == nil {
|
|
t.Error("Expected error after shutdown, but could still exchange")
|
|
}
|
|
}
|
|
|
|
func TestDNSDiscovery_IsRunning(t *testing.T) {
|
|
serviceIP := "192.168.1.100"
|
|
upstreamDNS := "8.8.8.8"
|
|
d := NewDNSDiscovery(upstreamDNS, serviceIP)
|
|
|
|
addr := "127.0.0.1:5355"
|
|
|
|
if d.IsRunning(addr) {
|
|
t.Error("Expected IsRunning to be false before Start")
|
|
}
|
|
|
|
go func() {
|
|
_ = d.Start(addr)
|
|
}()
|
|
|
|
// Give it a moment to start
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
if !d.IsRunning(addr) {
|
|
t.Error("Expected IsRunning to be true after Start")
|
|
}
|
|
|
|
if d.IsRunning("127.0.0.1:9999") {
|
|
t.Error("Expected IsRunning to be false for wrong address")
|
|
}
|
|
|
|
_ = d.Shutdown()
|
|
|
|
if d.IsRunning(addr) {
|
|
t.Error("Expected IsRunning to be false after Shutdown")
|
|
}
|
|
}
|
|
|
|
type mockResponseWriter struct {
|
|
msg *dns.Msg
|
|
}
|
|
|
|
func (m *mockResponseWriter) LocalAddr() net.Addr { return nil }
|
|
func (m *mockResponseWriter) RemoteAddr() net.Addr { return nil }
|
|
func (m *mockResponseWriter) WriteMsg(msg *dns.Msg) error { m.msg = msg; return nil }
|
|
func (m *mockResponseWriter) Write([]byte) (int, error) { return 0, nil }
|
|
func (m *mockResponseWriter) Close() error { return nil }
|
|
func (m *mockResponseWriter) TsigStatus() error { return nil }
|
|
func (m *mockResponseWriter) TsigTimersOnly(bool) {}
|
|
func (m *mockResponseWriter) Hijack() {}
|
|
|
|
func TestDNSDiscovery_LogThrottling(t *testing.T) {
|
|
d := NewDNSDiscovery("8.8.8.8", "192.168.1.100")
|
|
|
|
// Capture log output
|
|
var logBuf strings.Builder
|
|
oldOutput := log.Writer()
|
|
log.SetOutput(&logBuf)
|
|
defer log.SetOutput(oldOutput)
|
|
|
|
msg := "Test log message"
|
|
d.throttledLog(msg)
|
|
d.throttledLog(msg)
|
|
d.throttledLog(msg)
|
|
|
|
count := strings.Count(logBuf.String(), msg)
|
|
if count != 1 {
|
|
t.Errorf("Expected log message to appear once due to throttling, but appeared %d times", count)
|
|
}
|
|
|
|
// Advance time by 11 seconds to bypass throttling
|
|
d.lastLogMu.Lock()
|
|
d.lastLog[msg] = time.Now().Add(-11 * time.Second)
|
|
d.lastLogMu.Unlock()
|
|
|
|
d.throttledLog(msg)
|
|
count = strings.Count(logBuf.String(), msg)
|
|
if count != 2 {
|
|
t.Errorf("Expected log message to appear twice after advancing time, but appeared %d times", count)
|
|
}
|
|
}
|
|
|
|
func TestDNSDiscovery_LoopPrevention(t *testing.T) {
|
|
serviceIP := "192.168.1.100"
|
|
bindAddr := "127.0.0.1:53"
|
|
upstreamDNS := "127.0.0.1:53"
|
|
d := NewDNSDiscovery(upstreamDNS, serviceIP)
|
|
d.bindAddr = bindAddr
|
|
|
|
// Capture log output to avoid panic if it's being throttled/logged
|
|
var logBuf strings.Builder
|
|
oldOutput := log.Writer()
|
|
log.SetOutput(&logBuf)
|
|
defer log.SetOutput(oldOutput)
|
|
|
|
m := new(dns.Msg)
|
|
m.SetQuestion("google.com.", dns.TypeA)
|
|
|
|
rw := &mockResponseWriter{}
|
|
d.forward(rw, m)
|
|
|
|
if rw.msg == nil {
|
|
t.Fatal("Expected a response message")
|
|
}
|
|
|
|
if rw.msg.Rcode != dns.RcodeServerFailure {
|
|
t.Errorf("Expected RcodeServerFailure (2), got %d", rw.msg.Rcode)
|
|
}
|
|
}
|
|
|
|
func TestDNSDiscovery_EmptyUpstream(t *testing.T) {
|
|
serviceIP := "192.168.1.100"
|
|
upstreamDNS := "" // Empty upstream
|
|
d := NewDNSDiscovery(upstreamDNS, serviceIP)
|
|
d.bindAddr = ":53"
|
|
|
|
m := new(dns.Msg)
|
|
m.SetQuestion("google.com.", dns.TypeA)
|
|
|
|
rw := &mockResponseWriter{}
|
|
d.ServeDNS(rw, m)
|
|
|
|
if rw.msg == nil {
|
|
t.Fatal("Expected a response message, got nil")
|
|
}
|
|
|
|
if rw.msg.Rcode != dns.RcodeServerFailure {
|
|
t.Errorf("Expected RcodeServerFailure (2) for empty upstream, got %d", rw.msg.Rcode)
|
|
}
|
|
|
|
// Verify log message (optional, but good to check it's the simplified one)
|
|
}
|
|
|
|
func TestDNSDiscovery_ForwardTimeout(t *testing.T) {
|
|
serviceIP := "192.168.1.100"
|
|
// Use an IP that is unroutable or doesn't exist on the network to ensure timeout
|
|
upstreamDNS := "192.0.2.1:53" // TEST-NET-1, usually non-routable
|
|
d := NewDNSDiscovery(upstreamDNS, serviceIP)
|
|
|
|
m := new(dns.Msg)
|
|
m.SetQuestion("google.com.", dns.TypeA)
|
|
|
|
rw := &mockResponseWriter{}
|
|
|
|
start := time.Now()
|
|
d.forward(rw, m)
|
|
duration := time.Since(start)
|
|
|
|
if duration < 2*time.Second {
|
|
t.Errorf("Expected forward to take at least 2 seconds (timeout), but took %v", duration)
|
|
}
|
|
|
|
if rw.msg == nil || rw.msg.Rcode != dns.RcodeServerFailure {
|
|
t.Errorf("Expected RcodeServerFailure after timeout")
|
|
}
|
|
}
|