Add per-interface binding and poll backoff to discovery
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
85
discovery.go
85
discovery.go
@@ -29,28 +29,33 @@ type Discovery struct {
|
|||||||
longName string
|
longName string
|
||||||
inputUnivs []Universe
|
inputUnivs []Universe
|
||||||
outputUnivs []Universe
|
outputUnivs []Universe
|
||||||
pollTargets []*net.UDPAddr
|
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
onChange func(*Node)
|
onChange func(*Node)
|
||||||
lastPollHeard time.Time
|
lastPollHeard time.Time
|
||||||
pollMu sync.Mutex
|
pollMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDiscovery(sender *Sender, shortName, longName string, inputUnivs, outputUnivs []Universe, pollTargets []*net.UDPAddr) *Discovery {
|
func NewDiscovery(sender *Sender, localIP, broadcast net.IP, localMAC net.HardwareAddr, shortName, longName string, inputUnivs, outputUnivs []Universe) *Discovery {
|
||||||
return &Discovery{
|
d := &Discovery{
|
||||||
sender: sender,
|
sender: sender,
|
||||||
nodes: map[string]*Node{},
|
nodes: map[string]*Node{},
|
||||||
|
broadcast: broadcast,
|
||||||
shortName: shortName,
|
shortName: shortName,
|
||||||
longName: longName,
|
longName: longName,
|
||||||
inputUnivs: inputUnivs,
|
inputUnivs: inputUnivs,
|
||||||
outputUnivs: outputUnivs,
|
outputUnivs: outputUnivs,
|
||||||
pollTargets: pollTargets,
|
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
if ip4 := localIP.To4(); ip4 != nil {
|
||||||
|
copy(d.localIP[:], ip4)
|
||||||
|
}
|
||||||
|
if len(localMAC) == 6 {
|
||||||
|
copy(d.localMAC[:], localMAC)
|
||||||
|
}
|
||||||
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Discovery) Start() {
|
func (d *Discovery) Start() {
|
||||||
d.detectInterface()
|
|
||||||
go d.pollLoop()
|
go d.pollLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,16 +94,12 @@ func (d *Discovery) pollLoop() {
|
|||||||
|
|
||||||
func (d *Discovery) sendPolls() {
|
func (d *Discovery) sendPolls() {
|
||||||
d.pollMu.Lock()
|
d.pollMu.Lock()
|
||||||
lastHeard := d.lastPollHeard
|
defer d.pollMu.Unlock()
|
||||||
d.pollMu.Unlock()
|
|
||||||
|
|
||||||
if time.Since(lastHeard) < 15*time.Second {
|
if time.Since(d.lastPollHeard) < 15*time.Second {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
d.sender.SendPoll(&net.UDPAddr{IP: d.broadcast, Port: Port})
|
||||||
for _, target := range d.pollTargets {
|
|
||||||
d.sender.SendPoll(target)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Discovery) cleanup() {
|
func (d *Discovery) cleanup() {
|
||||||
@@ -155,12 +156,9 @@ func (d *Discovery) HandlePollReply(src *net.UDPAddr, pkt *PollReplyPacket) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Discovery) HandlePoll(src *net.UDPAddr) {
|
func (d *Discovery) HandlePoll(src *net.UDPAddr) {
|
||||||
localIP := net.IP(d.localIP[:])
|
d.pollMu.Lock()
|
||||||
if !src.IP.Equal(localIP) {
|
d.lastPollHeard = time.Now()
|
||||||
d.pollMu.Lock()
|
d.pollMu.Unlock()
|
||||||
d.lastPollHeard = time.Now()
|
|
||||||
d.pollMu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.receiver == nil {
|
if d.receiver == nil {
|
||||||
return
|
return
|
||||||
@@ -217,57 +215,6 @@ func (d *Discovery) GetAllNodes() []*Node {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Discovery) SetLocalIP(ip net.IP) {
|
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
|
||||||
copy(d.localIP[:], ip4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Discovery) detectInterface() {
|
|
||||||
d.broadcast = net.IPv4bcast
|
|
||||||
|
|
||||||
ifaces, err := net.Interfaces()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, iface := range ifaces {
|
|
||||||
if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err := iface.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range addrs {
|
|
||||||
ipnet, ok := addr.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ip4 := ipnet.IP.To4()
|
|
||||||
if ip4 == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(d.localIP[:], ip4)
|
|
||||||
|
|
||||||
if len(iface.HardwareAddr) == 6 {
|
|
||||||
copy(d.localMAC[:], iface.HardwareAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
bcast := make(net.IP, 4)
|
|
||||||
for i := 0; i < 4; i++ {
|
|
||||||
bcast[i] = ip4[i] | ^ipnet.Mask[i]
|
|
||||||
}
|
|
||||||
d.broadcast = bcast
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func containsUniverse(slice []Universe, val Universe) bool {
|
func containsUniverse(slice []Universe, val Universe) bool {
|
||||||
for _, v := range slice {
|
for _, v := range slice {
|
||||||
if v == val {
|
if v == val {
|
||||||
|
|||||||
25
receiver.go
25
receiver.go
@@ -1,7 +1,9 @@
|
|||||||
package artnet
|
package artnet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,6 +36,29 @@ func NewDefaultReceiver(handler Handler) (*Receiver, error) {
|
|||||||
return NewReceiver(&net.UDPAddr{Port: Port}, handler)
|
return NewReceiver(&net.UDPAddr{Port: Port}, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewInterfaceReceiver(ifaceName string, handler Handler) (*Receiver, error) {
|
||||||
|
lc := net.ListenConfig{
|
||||||
|
Control: func(network, address string, c syscall.RawConn) error {
|
||||||
|
var err error
|
||||||
|
c.Control(func(fd uintptr) {
|
||||||
|
err = syscall.SetsockoptString(int(fd), syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, ifaceName)
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := lc.ListenPacket(context.Background(), "udp4", ":6454")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Receiver{
|
||||||
|
conn: conn.(*net.UDPConn),
|
||||||
|
handler: handler,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Receiver) Start() {
|
func (r *Receiver) Start() {
|
||||||
go r.loop()
|
go r.loop()
|
||||||
}
|
}
|
||||||
|
|||||||
24
sender.go
24
sender.go
@@ -1,8 +1,10 @@
|
|||||||
package artnet
|
package artnet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Sender struct {
|
type Sender struct {
|
||||||
@@ -30,6 +32,28 @@ func NewSenderFromConn(conn *net.UDPConn) *Sender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewInterfaceSender(ifaceName string) (*Sender, error) {
|
||||||
|
lc := net.ListenConfig{
|
||||||
|
Control: func(network, address string, c syscall.RawConn) error {
|
||||||
|
var err error
|
||||||
|
c.Control(func(fd uintptr) {
|
||||||
|
err = syscall.SetsockoptString(int(fd), syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, ifaceName)
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := lc.ListenPacket(context.Background(), "udp4", ":0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Sender{
|
||||||
|
conn: conn.(*net.UDPConn),
|
||||||
|
sequences: map[Universe]uint8{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Sender) SendDMX(addr *net.UDPAddr, universe Universe, data []byte) error {
|
func (s *Sender) SendDMX(addr *net.UDPAddr, universe Universe, data []byte) error {
|
||||||
s.seqMu.Lock()
|
s.seqMu.Lock()
|
||||||
seq := s.sequences[universe]
|
seq := s.sequences[universe]
|
||||||
|
|||||||
Reference in New Issue
Block a user