136 lines
3.6 KiB
Go
136 lines
3.6 KiB
Go
package tendrils
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"log"
|
|
"net"
|
|
"regexp"
|
|
"time"
|
|
)
|
|
|
|
var acnUACNRegex = regexp.MustCompile(`\(acn-uacn=([^)]+)\)`)
|
|
|
|
const (
|
|
shureMulticastAddr = "239.255.254.253:8427"
|
|
)
|
|
|
|
func (t *Tendrils) listenShure(ctx context.Context, iface net.Interface) {
|
|
addr, err := net.ResolveUDPAddr("udp4", shureMulticastAddr)
|
|
if err != nil {
|
|
log.Printf("[ERROR] failed to resolve shure address: %v", err)
|
|
return
|
|
}
|
|
|
|
conn, err := net.ListenMulticastUDP("udp4", &iface, addr)
|
|
if err != nil {
|
|
if t.DebugShure {
|
|
log.Printf("[shure] %s: failed to listen: %v", iface.Name, err)
|
|
}
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
go t.sendShureDiscovery(ctx, iface.Name, conn)
|
|
|
|
buf := make([]byte, 4096)
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
}
|
|
|
|
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
|
n, src, err := conn.ReadFromUDP(buf)
|
|
if err != nil {
|
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
|
continue
|
|
}
|
|
continue
|
|
}
|
|
|
|
t.handleShurePacket(iface.Name, src.IP, buf[:n])
|
|
}
|
|
}
|
|
|
|
func (t *Tendrils) sendShureDiscovery(ctx context.Context, ifaceName string, conn *net.UDPConn) {
|
|
dest := &net.UDPAddr{IP: net.IPv4(239, 255, 254, 253), Port: 8427}
|
|
|
|
ticker := time.NewTicker(30 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
t.sendShureQuery(ifaceName, conn, dest)
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
t.sendShureQuery(ifaceName, conn, dest)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *Tendrils) sendShureQuery(ifaceName string, conn *net.UDPConn, dest *net.UDPAddr) {
|
|
// SLP Service Type Request (SrvTypeRqst)
|
|
// This asks "what service types are available?"
|
|
langTag := []byte("en")
|
|
|
|
// Build SLP v2 header + SrvTypeRqst body
|
|
// Header: version(1) + function(1) + length(3) + flags(2) + next-ext(3) + xid(2) + lang-len(2) + lang
|
|
// SrvTypeRqst body: PR list len(2) + PR list + naming auth len(2) + naming auth + scope list len(2) + scope list
|
|
|
|
headerLen := 14 + len(langTag)
|
|
bodyLen := 2 + 0 + 2 + 0 + 2 + 7 // empty PR list, empty naming auth, "default" scope
|
|
totalLen := headerLen + bodyLen
|
|
|
|
pkt := make([]byte, totalLen)
|
|
pkt[0] = 0x02 // SLP version 2
|
|
pkt[1] = 0x09 // Function: SrvTypeRqst (9)
|
|
pkt[2] = byte(totalLen >> 16) // Length (3 bytes)
|
|
pkt[3] = byte(totalLen >> 8)
|
|
pkt[4] = byte(totalLen)
|
|
pkt[5] = 0x00 // Flags (2 bytes) - multicast
|
|
pkt[6] = 0x20
|
|
pkt[7] = 0x00 // Next ext offset (3 bytes)
|
|
pkt[8] = 0x00
|
|
pkt[9] = 0x00
|
|
binary.BigEndian.PutUint16(pkt[10:12], 0x0001) // XID
|
|
binary.BigEndian.PutUint16(pkt[12:14], uint16(len(langTag)))
|
|
copy(pkt[14:], langTag)
|
|
|
|
offset := 14 + len(langTag)
|
|
binary.BigEndian.PutUint16(pkt[offset:], 0) // PR list length (0)
|
|
offset += 2
|
|
binary.BigEndian.PutUint16(pkt[offset:], 0) // Naming authority length (0 = IANA)
|
|
offset += 2
|
|
binary.BigEndian.PutUint16(pkt[offset:], 7) // Scope list length
|
|
offset += 2
|
|
copy(pkt[offset:], "default")
|
|
|
|
conn.WriteToUDP(pkt, dest)
|
|
|
|
if t.DebugShure {
|
|
log.Printf("[shure] %s: sent slp discovery query", ifaceName)
|
|
}
|
|
}
|
|
|
|
func (t *Tendrils) handleShurePacket(ifaceName string, srcIP net.IP, data []byte) {
|
|
if t.DebugShure {
|
|
log.Printf("[shure] %s: packet from %s (%d bytes): %q", ifaceName, srcIP, len(data), data)
|
|
}
|
|
|
|
match := acnUACNRegex.FindSubmatch(data)
|
|
if match == nil {
|
|
return
|
|
}
|
|
|
|
name := string(match[1])
|
|
if t.DebugShure {
|
|
log.Printf("[shure] %s: found device %s at %s", ifaceName, name, srcIP)
|
|
}
|
|
|
|
t.nodes.Update(nil, nil, []net.IP{srcIP}, "", name, "shure")
|
|
}
|