Files

133 lines
2.9 KiB
Go
Raw Permalink Normal View History

package main
import (
"flag"
"fmt"
"log/slog"
"net"
"os"
"regexp"
)
const (
DefaultArtNetPort = 6454 // 0x1936
)
var (
verbose bool
nameFilter *regexp.Regexp
)
func main() {
port := flag.Int("port", DefaultArtNetPort, "UDP port to listen on (default: 6454/0x1936)")
bindAddr := flag.String("bind", "0.0.0.0", "IP address to bind to")
namePattern := flag.String("name", "", "Filter by node name (regex)")
flag.BoolVar(&verbose, "v", false, "Verbose output (show all field details)")
flag.Parse()
if *namePattern != "" {
var err error
nameFilter, err = regexp.Compile(*namePattern)
if err != nil {
fmt.Fprintf(os.Stderr, "Invalid name regex: %v\n", err)
os.Exit(1)
}
}
ip := net.ParseIP(*bindAddr)
if ip == nil {
fmt.Fprintf(os.Stderr, "Invalid bind address: %s\n", *bindAddr)
os.Exit(1)
}
addr := net.UDPAddr{
Port: *port,
IP: ip,
}
conn, err := net.ListenUDP("udp4", &addr)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to listen on UDP port %d: %v\n", *port, err)
fmt.Fprintf(os.Stderr, "You may need to run with sudo or use setcap cap_net_bind_service=+ep\n")
os.Exit(1)
}
defer conn.Close()
slog.Info("listening", "addr", fmt.Sprintf("%s:%d", *bindAddr, *port))
if *port != DefaultArtNetPort {
slog.Warn("non-standard port", "expected", DefaultArtNetPort)
}
buf := make([]byte, 65535)
for {
n, remoteAddr, err := conn.ReadFromUDP(buf)
if err != nil {
slog.Error("read failed", "error", err)
continue
}
packet := make([]byte, n)
copy(packet, buf[:n])
processPacket(packet, remoteAddr)
}
}
func processPacket(data []byte, src *net.UDPAddr) {
result := ValidatePacket(data, src)
// Apply name filter
if nameFilter != nil {
name := result.getField("PortName")
if !nameFilter.MatchString(name) {
return
}
}
// Build log attributes
attrs := []any{
"src", src.IP.String(),
"size", len(data),
"type", result.PacketType,
}
// Add summary fields based on packet type
for _, kv := range result.SummaryAttrs() {
attrs = append(attrs, kv.Key, kv.Value)
}
// Add error/warning counts if any
if len(result.Errors) > 0 {
attrs = append(attrs, "errors", len(result.Errors))
}
if len(result.Warnings) > 0 {
attrs = append(attrs, "warnings", len(result.Warnings))
}
// Log the packet
if len(result.Errors) > 0 {
slog.Error("packet", attrs...)
} else if len(result.Warnings) > 0 {
slog.Warn("packet", attrs...)
} else {
slog.Info("packet", attrs...)
}
// Always log individual warnings
for _, w := range result.Warnings {
slog.Warn("validation", "src", src.IP.String(), "warning", w)
}
// Always log individual errors
for _, e := range result.Errors {
slog.Error("validation", "src", src.IP.String(), "error", e)
}
// Verbose: log all fields
if verbose && len(result.Fields) > 0 {
for _, f := range result.Fields {
slog.Debug("field", "src", src.IP.String(), "name", f.Name, "value", f.Value)
}
}
}