Remove pcap-based packet sniffing receivers
This commit is contained in:
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,9 +3,7 @@
|
|||||||
#
|
#
|
||||||
# Flags:
|
# Flags:
|
||||||
# --artnet-listen=:6454 ArtNet listen address (empty to disable)
|
# --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')
|
# --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
|
# Target addresses for ArtNet output universes
|
||||||
# Each output universe needs a target IP (broadcast or unicast)
|
# Each output universe needs a target IP (broadcast or unicast)
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -4,7 +4,6 @@ go 1.25.4
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.6.0
|
github.com/BurntSushi/toml v1.6.0
|
||||||
github.com/google/gopacket v1.1.19
|
|
||||||
golang.org/x/net v0.48.0
|
golang.org/x/net v0.48.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
14
go.sum
14
go.sum
@@ -1,20 +1,6 @@
|
|||||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
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 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
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 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
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=
|
|
||||||
|
|||||||
73
main.go
73
main.go
@@ -18,26 +18,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
artReceiver *artnet.Receiver
|
artReceiver *artnet.Receiver
|
||||||
artPcapReceiver *artnet.PcapReceiver
|
sacnReceiver *sacn.Receiver
|
||||||
sacnReceiver *sacn.Receiver
|
artSender *artnet.Sender
|
||||||
sacnPcapReceiver *sacn.PcapReceiver
|
sacnSender *sacn.Sender
|
||||||
artSender *artnet.Sender
|
discovery *artnet.Discovery
|
||||||
sacnSender *sacn.Sender
|
engine *remap.Engine
|
||||||
discovery *artnet.Discovery
|
targets map[artnet.Universe]*net.UDPAddr
|
||||||
engine *remap.Engine
|
broadcasts []*net.UDPAddr
|
||||||
targets map[artnet.Universe]*net.UDPAddr
|
debug bool
|
||||||
broadcasts []*net.UDPAddr
|
|
||||||
debug bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
configPath := flag.String("config", "config.toml", "path to config file")
|
configPath := flag.String("config", "config.toml", "path to config file")
|
||||||
artnetListen := flag.String("artnet-listen", ":6454", "artnet listen address (empty to disable)")
|
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')")
|
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")
|
debug := flag.Bool("debug", false, "log incoming/outgoing dmx packets")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@@ -131,20 +127,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create ArtNet receiver if enabled
|
// Create ArtNet receiver if enabled
|
||||||
if *artnetPcap != "" {
|
if *artnetListen != "" {
|
||||||
// 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 != "" {
|
|
||||||
addr, err := parseListenAddr(*artnetListen)
|
addr, err := parseListenAddr(*artnetListen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("artnet listen error: %v", err)
|
log.Fatalf("artnet listen error: %v", err)
|
||||||
@@ -161,29 +144,13 @@ func main() {
|
|||||||
// Create sACN receiver if needed
|
// Create sACN receiver if needed
|
||||||
sacnUniverses := cfg.SACNSourceUniverses()
|
sacnUniverses := cfg.SACNSourceUniverses()
|
||||||
if len(sacnUniverses) > 0 {
|
if len(sacnUniverses) > 0 {
|
||||||
if *sacnPcap != "" {
|
sacnReceiver, err := sacn.NewReceiver(sacnUniverses, app.HandleSACN)
|
||||||
// Use pcap-based receiver (requires root, avoids port conflicts)
|
if err != nil {
|
||||||
iface := *sacnPcap
|
log.Fatalf("sacn receiver error: %v", err)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
app.sacnReceiver = sacnReceiver
|
||||||
|
sacnReceiver.Start()
|
||||||
|
log.Printf("[sacn] listening universes=%v", sacnUniverses)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start discovery
|
// Start discovery
|
||||||
@@ -198,15 +165,9 @@ func main() {
|
|||||||
if app.artReceiver != nil {
|
if app.artReceiver != nil {
|
||||||
app.artReceiver.Stop()
|
app.artReceiver.Stop()
|
||||||
}
|
}
|
||||||
if app.artPcapReceiver != nil {
|
|
||||||
app.artPcapReceiver.Stop()
|
|
||||||
}
|
|
||||||
if app.sacnReceiver != nil {
|
if app.sacnReceiver != nil {
|
||||||
app.sacnReceiver.Stop()
|
app.sacnReceiver.Stop()
|
||||||
}
|
}
|
||||||
if app.sacnPcapReceiver != nil {
|
|
||||||
app.sacnPcapReceiver.Stop()
|
|
||||||
}
|
|
||||||
discovery.Stop()
|
discovery.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user