diff --git a/artnet/receiver_pcap.go b/artnet/receiver_pcap.go deleted file mode 100644 index 1659e6b..0000000 --- a/artnet/receiver_pcap.go +++ /dev/null @@ -1,120 +0,0 @@ -package artnet - -import ( - "net" - - "github.com/google/gopacket" - "github.com/google/gopacket/layers" - "github.com/google/gopacket/pcap" -) - -// PcapReceiver listens for ArtNet packets using packet capture -type PcapReceiver struct { - handle *pcap.Handle - handler PacketHandler - done chan struct{} -} - -// NewPcapReceiver creates a new ArtNet receiver using packet capture -// This requires root/admin privileges but avoids port conflicts -func NewPcapReceiver(iface string, handler PacketHandler) (*PcapReceiver, error) { - // Open device for capturing - handle, err := pcap.OpenLive(iface, 1600, true, pcap.BlockForever) - if err != nil { - return nil, err - } - - // Filter for UDP port 6454 (ArtNet) - captures both directions - if err := handle.SetBPFFilter("udp port 6454"); err != nil { - handle.Close() - return nil, err - } - - return &PcapReceiver{ - handle: handle, - handler: handler, - done: make(chan struct{}), - }, nil -} - -// Start begins receiving packets -func (r *PcapReceiver) Start() { - go r.receiveLoop() -} - -// Stop stops the receiver -func (r *PcapReceiver) Stop() { - close(r.done) - r.handle.Close() -} - -func (r *PcapReceiver) receiveLoop() { - packetSource := gopacket.NewPacketSource(r.handle, r.handle.LinkType()) - - for { - select { - case <-r.done: - return - case packet, ok := <-packetSource.Packets(): - if !ok { - return - } - r.handlePacket(packet) - } - } -} - -func (r *PcapReceiver) handlePacket(packet gopacket.Packet) { - // Extract UDP layer - udpLayer := packet.Layer(layers.LayerTypeUDP) - if udpLayer == nil { - return - } - - udp, _ := udpLayer.(*layers.UDP) - if udp == nil { - return - } - - // Extract IP layer for source address - var srcIP, dstIP [4]byte - if ipLayer := packet.Layer(layers.LayerTypeIPv4); ipLayer != nil { - ip, _ := ipLayer.(*layers.IPv4) - if ip != nil { - copy(srcIP[:], ip.SrcIP.To4()) - copy(dstIP[:], ip.DstIP.To4()) - } - } - - // Get payload - data := udp.Payload - if len(data) < 12 { - return - } - - // Parse the ArtNet packet - opCode, pkt, err := ParsePacket(data) - if err != nil { - return - } - - src := &net.UDPAddr{ - IP: net.IP(srcIP[:]), - Port: int(udp.SrcPort), - } - - switch opCode { - case OpDmx: - if dmx, ok := pkt.(*DMXPacket); ok { - r.handler.HandleDMX(src, dmx) - } - case OpPoll: - if poll, ok := pkt.(*PollPacket); ok { - r.handler.HandlePoll(src, poll) - } - case OpPollReply: - if reply, ok := pkt.(*PollReplyPacket); ok { - r.handler.HandlePollReply(src, reply) - } - } -} diff --git a/config.example.toml b/config.example.toml index 999f099..07c99f6 100644 --- a/config.example.toml +++ b/config.example.toml @@ -3,9 +3,7 @@ # # Flags: # --artnet-listen=:6454 ArtNet listen address (empty to disable) -# --artnet-pcap=any Use pcap for ArtNet (requires root, avoids port conflicts) # --artnet-broadcast=auto Broadcast addresses (comma-separated, or 'auto') -# --sacn-pcap=any Use pcap for sACN (requires root, avoids port conflicts) # Target addresses for ArtNet output universes # Each output universe needs a target IP (broadcast or unicast) diff --git a/go.mod b/go.mod index 21dfa3c..6e548ce 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.25.4 require ( github.com/BurntSushi/toml v1.6.0 - github.com/google/gopacket v1.1.19 golang.org/x/net v0.48.0 ) diff --git a/go.sum b/go.sum index 2ed0923..2b7c131 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,6 @@ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/main.go b/main.go index 9a8116b..df32674 100644 --- a/main.go +++ b/main.go @@ -18,26 +18,22 @@ import ( ) type App struct { - cfg *config.Config - artReceiver *artnet.Receiver - artPcapReceiver *artnet.PcapReceiver - sacnReceiver *sacn.Receiver - sacnPcapReceiver *sacn.PcapReceiver - artSender *artnet.Sender - sacnSender *sacn.Sender - discovery *artnet.Discovery - engine *remap.Engine - targets map[artnet.Universe]*net.UDPAddr - broadcasts []*net.UDPAddr - debug bool + cfg *config.Config + artReceiver *artnet.Receiver + sacnReceiver *sacn.Receiver + artSender *artnet.Sender + sacnSender *sacn.Sender + discovery *artnet.Discovery + engine *remap.Engine + targets map[artnet.Universe]*net.UDPAddr + broadcasts []*net.UDPAddr + debug bool } func main() { configPath := flag.String("config", "config.toml", "path to config file") artnetListen := flag.String("artnet-listen", ":6454", "artnet listen address (empty to disable)") - artnetPcap := flag.String("artnet-pcap", "", "use pcap for artnet on interface (e.g. en0, any)") artnetBroadcast := flag.String("artnet-broadcast", "auto", "artnet broadcast addresses (comma-separated, or 'auto')") - sacnPcap := flag.String("sacn-pcap", "", "use pcap for sacn on interface (e.g. en0, any)") debug := flag.Bool("debug", false, "log incoming/outgoing dmx packets") flag.Parse() @@ -131,20 +127,7 @@ func main() { } // Create ArtNet receiver if enabled - if *artnetPcap != "" { - // Use pcap-based receiver (requires root, avoids port conflicts) - iface := *artnetPcap - if iface == "auto" { - iface = "any" - } - pcapReceiver, err := artnet.NewPcapReceiver(iface, app) - if err != nil { - log.Fatalf("artnet pcap error: %v", err) - } - app.artPcapReceiver = pcapReceiver - pcapReceiver.Start() - log.Printf("[artnet] pcap listening iface=%s", iface) - } else if *artnetListen != "" { + if *artnetListen != "" { addr, err := parseListenAddr(*artnetListen) if err != nil { log.Fatalf("artnet listen error: %v", err) @@ -161,29 +144,13 @@ func main() { // Create sACN receiver if needed sacnUniverses := cfg.SACNSourceUniverses() if len(sacnUniverses) > 0 { - if *sacnPcap != "" { - // Use pcap-based receiver (requires root, avoids port conflicts) - iface := *sacnPcap - if iface == "auto" { - iface = sacn.DefaultInterface() - } - pcapReceiver, err := sacn.NewPcapReceiver(iface, sacnUniverses, app.HandleSACN) - if err != nil { - log.Fatalf("sacn pcap error: %v", err) - } - app.sacnPcapReceiver = pcapReceiver - pcapReceiver.Start() - log.Printf("[sacn] pcap listening iface=%s universes=%v", iface, sacnUniverses) - } else { - // Use standard UDP receiver - sacnReceiver, err := sacn.NewReceiver(sacnUniverses, app.HandleSACN) - if err != nil { - log.Fatalf("sacn receiver error: %v", err) - } - app.sacnReceiver = sacnReceiver - sacnReceiver.Start() - log.Printf("[sacn] listening universes=%v", sacnUniverses) + sacnReceiver, err := sacn.NewReceiver(sacnUniverses, app.HandleSACN) + if err != nil { + log.Fatalf("sacn receiver error: %v", err) } + app.sacnReceiver = sacnReceiver + sacnReceiver.Start() + log.Printf("[sacn] listening universes=%v", sacnUniverses) } // Start discovery @@ -198,15 +165,9 @@ func main() { if app.artReceiver != nil { app.artReceiver.Stop() } - if app.artPcapReceiver != nil { - app.artPcapReceiver.Stop() - } if app.sacnReceiver != nil { app.sacnReceiver.Stop() } - if app.sacnPcapReceiver != nil { - app.sacnPcapReceiver.Stop() - } discovery.Stop() } diff --git a/sacn/receiver_pcap.go b/sacn/receiver_pcap.go deleted file mode 100644 index 3af8756..0000000 --- a/sacn/receiver_pcap.go +++ /dev/null @@ -1,148 +0,0 @@ -package sacn - -import ( - "encoding/binary" - "fmt" - - "github.com/google/gopacket" - "github.com/google/gopacket/layers" - "github.com/google/gopacket/pcap" -) - -// PcapReceiver listens for sACN packets using packet capture -type PcapReceiver struct { - handle *pcap.Handle - universes map[uint16]bool - handler DMXHandler - done chan struct{} -} - -// NewPcapReceiver creates a new sACN receiver using packet capture -// This requires root/admin privileges but avoids port conflicts -func NewPcapReceiver(iface string, universes []uint16, handler DMXHandler) (*PcapReceiver, error) { - // Open device for capturing - handle, err := pcap.OpenLive(iface, 1600, true, pcap.BlockForever) - if err != nil { - return nil, fmt.Errorf("pcap open: %w", err) - } - - // Filter for UDP port 5568 (sACN) - captures both directions - if err := handle.SetBPFFilter("udp port 5568"); err != nil { - handle.Close() - return nil, fmt.Errorf("pcap filter: %w", err) - } - - universeMap := make(map[uint16]bool) - for _, u := range universes { - universeMap[u] = true - } - - return &PcapReceiver{ - handle: handle, - universes: universeMap, - handler: handler, - done: make(chan struct{}), - }, nil -} - -// Start begins receiving packets -func (r *PcapReceiver) Start() { - go r.receiveLoop() -} - -// Stop stops the receiver -func (r *PcapReceiver) Stop() { - close(r.done) - r.handle.Close() -} - -func (r *PcapReceiver) receiveLoop() { - packetSource := gopacket.NewPacketSource(r.handle, r.handle.LinkType()) - - for { - select { - case <-r.done: - return - case packet, ok := <-packetSource.Packets(): - if !ok { - return - } - r.handlePacket(packet) - } - } -} - -func (r *PcapReceiver) handlePacket(packet gopacket.Packet) { - // Extract UDP layer - udpLayer := packet.Layer(layers.LayerTypeUDP) - if udpLayer == nil { - return - } - - udp, _ := udpLayer.(*layers.UDP) - if udp == nil { - return - } - - // Get payload - data := udp.Payload - if len(data) < 126 { - return - } - - // Check ACN packet identifier - if data[4] != 0x41 || data[5] != 0x53 || data[6] != 0x43 { - return - } - - // Check root vector (E1.31 data) - rootVector := binary.BigEndian.Uint32(data[18:22]) - if rootVector != VectorRootE131Data { - return - } - - // Check framing vector (DMP data) - framingVector := binary.BigEndian.Uint32(data[40:44]) - if framingVector != VectorE131DataPacket { - return - } - - // Get universe - universe := binary.BigEndian.Uint16(data[113:115]) - - // Check if we care about this universe - if !r.universes[universe] { - return - } - - // Check DMP vector - if data[117] != VectorDMPSetProperty { - return - } - - // Get property count (includes START code) - propCount := binary.BigEndian.Uint16(data[123:125]) - if propCount < 1 { - return - } - - // Skip START code at data[125] - dmxLen := int(propCount) - 1 - if dmxLen > 512 { - dmxLen = 512 - } - - if len(data) < 126+dmxLen { - return - } - - var dmxData [512]byte - copy(dmxData[:], data[126:126+dmxLen]) - - r.handler(universe, dmxData) -} - -// DefaultInterface returns the default interface for capture -func DefaultInterface() string { - return "any" -}