Fix ARP incomplete entries and Art-Net broadcast reception

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ian Gulliver
2026-01-27 22:57:53 -08:00
parent 25f8f410c1
commit ccd6956d6a
3 changed files with 48 additions and 36 deletions

View File

@@ -36,6 +36,11 @@ func (t *Tendrils) parseARPTable() []arpEntry {
continue continue
} }
flags := fields[2]
if flags == "0x0" {
continue
}
macStr := fields[3] macStr := fields[3]
if macStr == "00:00:00:00:00:00" { if macStr == "00:00:00:00:00:00" {
continue continue

View File

@@ -30,20 +30,15 @@ type ArtNetNode struct {
LastSeen time.Time `json:"last_seen"` LastSeen time.Time `json:"last_seen"`
} }
func (t *Tendrils) listenArtNet(ctx context.Context, iface net.Interface) { func (t *Tendrils) startArtNetListener(ctx context.Context) {
srcIP, _ := getInterfaceIPv4(iface) conn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: artNetPort})
if srcIP == nil {
return
}
conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: srcIP, Port: artNetPort})
if err != nil { if err != nil {
log.Printf("[ERROR] failed to listen artnet on %s: %v", iface.Name, err) log.Printf("[ERROR] failed to listen artnet: %v", err)
return return
} }
defer conn.Close() defer conn.Close()
go t.runArtNetPoller(ctx, iface, conn) t.artnetConn = conn
buf := make([]byte, 65536) buf := make([]byte, 65536)
for { for {
@@ -62,11 +57,39 @@ func (t *Tendrils) listenArtNet(ctx context.Context, iface net.Interface) {
continue continue
} }
t.handleArtNetPacket(iface.Name, src.IP, buf[:n]) t.handleArtNetPacket(src.IP, buf[:n])
} }
} }
func (t *Tendrils) handleArtNetPacket(ifaceName string, srcIP net.IP, data []byte) { func (t *Tendrils) startArtNetPoller(ctx context.Context, iface net.Interface) {
srcIP, broadcast := getInterfaceIPv4(iface)
if srcIP == nil || broadcast == nil {
return
}
sendConn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: srcIP, Port: 0})
if err != nil {
log.Printf("[ERROR] failed to create artnet send socket on %s: %v", iface.Name, err)
return
}
defer sendConn.Close()
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
t.sendArtPoll(sendConn, broadcast, iface.Name)
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
t.sendArtPoll(sendConn, broadcast, iface.Name)
}
}
}
func (t *Tendrils) handleArtNetPacket(srcIP net.IP, data []byte) {
if len(data) < 12 { if len(data) < 12 {
return return
} }
@@ -79,11 +102,11 @@ func (t *Tendrils) handleArtNetPacket(ifaceName string, srcIP net.IP, data []byt
switch opcode { switch opcode {
case opPollReply: case opPollReply:
t.handleArtPollReply(ifaceName, srcIP, data) t.handleArtPollReply(srcIP, data)
} }
} }
func (t *Tendrils) handleArtPollReply(ifaceName string, srcIP net.IP, data []byte) { func (t *Tendrils) handleArtPollReply(srcIP net.IP, data []byte) {
if len(data) < 207 { if len(data) < 207 {
return return
} }
@@ -123,7 +146,7 @@ func (t *Tendrils) handleArtPollReply(ifaceName string, srcIP net.IP, data []byt
} }
if t.DebugArtNet { if t.DebugArtNet {
log.Printf("[artnet] %s: %s %s short=%q long=%q numPorts=%d portTypes=%v in=%v out=%v", ifaceName, ip, mac, shortName, longName, numPorts, data[174:178], inputs, outputs) log.Printf("[artnet] %s %s short=%q long=%q numPorts=%d portTypes=%v in=%v out=%v", ip, mac, shortName, longName, numPorts, data[174:178], inputs, outputs)
} }
name := longName name := longName
@@ -146,27 +169,6 @@ func (t *Tendrils) handleArtPollReply(ifaceName string, srcIP net.IP, data []byt
} }
} }
func (t *Tendrils) runArtNetPoller(ctx context.Context, iface net.Interface, conn *net.UDPConn) {
_, broadcast := getInterfaceIPv4(iface)
if broadcast == nil {
return
}
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
t.sendArtPoll(conn, broadcast, iface.Name)
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
t.sendArtPoll(conn, broadcast, iface.Name)
}
}
}
func (t *Tendrils) sendArtPoll(conn *net.UDPConn, broadcast net.IP, ifaceName string) { func (t *Tendrils) sendArtPoll(conn *net.UDPConn, broadcast net.IP, ifaceName string) {
packet := make([]byte, 14) packet := make([]byte, 14)
copy(packet[0:8], artNetID) copy(packet[0:8], artNetID)

View File

@@ -34,6 +34,7 @@ type Tendrils struct {
activeInterfaces map[string]context.CancelFunc activeInterfaces map[string]context.CancelFunc
nodes *Nodes nodes *Nodes
artnet *ArtNetNodes artnet *ArtNetNodes
artnetConn *net.UDPConn
danteFlows *DanteFlows danteFlows *DanteFlows
errors *ErrorTracker errors *ErrorTracker
ping *PingManager ping *PingManager
@@ -158,6 +159,10 @@ func (t *Tendrils) Run() {
go t.pollARP(ctx) go t.pollARP(ctx)
} }
if !t.DisableArtNet {
go t.startArtNetListener(ctx)
}
ticker := time.NewTicker(1 * time.Second) ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop() defer ticker.Stop()
@@ -294,7 +299,7 @@ func (t *Tendrils) startInterface(ctx context.Context, iface net.Interface) {
go t.listenMDNS(ctx, iface) go t.listenMDNS(ctx, iface)
} }
if !t.DisableArtNet { if !t.DisableArtNet {
go t.listenArtNet(ctx, iface) go t.startArtNetPoller(ctx, iface)
} }
if !t.DisableDante { if !t.DisableDante {
go t.listenDante(ctx, iface) go t.listenDante(ctx, iface)