353 lines
6.8 KiB
Go
353 lines
6.8 KiB
Go
package tendrils
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"os/signal"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
func getInterfaceIPv4(iface net.Interface) (srcIP, broadcast net.IP) {
|
|
addrs, err := iface.Addrs()
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
for _, addr := range addrs {
|
|
if ipnet, ok := addr.(*net.IPNet); ok && ipnet.IP.To4() != nil {
|
|
srcIP = ipnet.IP.To4()
|
|
mask := ipnet.Mask
|
|
broadcast = make(net.IP, 4)
|
|
for i := 0; i < 4; i++ {
|
|
broadcast[i] = srcIP[i] | ^mask[i]
|
|
}
|
|
return srcIP, broadcast
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
type Tendrils struct {
|
|
activeInterfaces map[string]context.CancelFunc
|
|
nodes *Nodes
|
|
artnet *ArtNetNodes
|
|
artnetConn *net.UDPConn
|
|
danteFlows *DanteFlows
|
|
errors *ErrorTracker
|
|
ping *PingManager
|
|
broadcast *BroadcastStats
|
|
config *Config
|
|
|
|
sseSubsMu sync.RWMutex
|
|
sseSubsNext int
|
|
sseSubs map[int]chan struct{}
|
|
|
|
Interface string
|
|
ConfigFile string
|
|
DisableARP bool
|
|
DisableLLDP bool
|
|
DisableSNMP bool
|
|
DisableIGMP bool
|
|
DisableMDNS bool
|
|
DisableArtNet bool
|
|
DisableDante bool
|
|
DisableBMD bool
|
|
DisableShure bool
|
|
DisableYamaha bool
|
|
LogEvents bool
|
|
LogNodes bool
|
|
DebugARP bool
|
|
DebugLLDP bool
|
|
DebugSNMP bool
|
|
DebugIGMP bool
|
|
DebugMDNS bool
|
|
DebugArtNet bool
|
|
DebugDante bool
|
|
DebugBMD bool
|
|
DebugShure bool
|
|
DebugYamaha bool
|
|
DebugBroadcast bool
|
|
}
|
|
|
|
func New() *Tendrils {
|
|
t := &Tendrils{
|
|
activeInterfaces: map[string]context.CancelFunc{},
|
|
artnet: NewArtNetNodes(),
|
|
danteFlows: NewDanteFlows(),
|
|
ping: NewPingManager(),
|
|
sseSubs: map[int]chan struct{}{},
|
|
}
|
|
t.nodes = NewNodes(t)
|
|
t.errors = NewErrorTracker(t)
|
|
t.broadcast = NewBroadcastStats(t)
|
|
return t
|
|
}
|
|
|
|
func (t *Tendrils) NotifyUpdate() {
|
|
t.sseSubsMu.RLock()
|
|
defer t.sseSubsMu.RUnlock()
|
|
|
|
for _, ch := range t.sseSubs {
|
|
select {
|
|
case ch <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *Tendrils) subscribeSSE() (int, chan struct{}) {
|
|
t.sseSubsMu.Lock()
|
|
defer t.sseSubsMu.Unlock()
|
|
|
|
t.sseSubsNext++
|
|
id := t.sseSubsNext
|
|
ch := make(chan struct{}, 1)
|
|
t.sseSubs[id] = ch
|
|
return id, ch
|
|
}
|
|
|
|
func (t *Tendrils) unsubscribeSSE(id int) {
|
|
t.sseSubsMu.Lock()
|
|
defer t.sseSubsMu.Unlock()
|
|
|
|
if ch, ok := t.sseSubs[id]; ok {
|
|
close(ch)
|
|
delete(t.sseSubs, id)
|
|
}
|
|
}
|
|
|
|
func (t *Tendrils) Run() {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
sigUsr1Ch := make(chan os.Signal, 1)
|
|
signal.Notify(sigUsr1Ch, syscall.SIGUSR1)
|
|
go func() {
|
|
for range sigUsr1Ch {
|
|
t.nodes.LogAll()
|
|
}
|
|
}()
|
|
|
|
sigHupCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigHupCh, syscall.SIGHUP)
|
|
go func() {
|
|
for range sigHupCh {
|
|
cfg, err := LoadConfig(t.ConfigFile)
|
|
if err != nil {
|
|
log.Printf("[ERROR] failed to reload config: %v", err)
|
|
continue
|
|
}
|
|
t.config = cfg
|
|
log.Printf("reloaded config from %s", t.ConfigFile)
|
|
}
|
|
}()
|
|
|
|
cfg, err := LoadConfig(t.ConfigFile)
|
|
if err != nil {
|
|
log.Fatalf("[ERROR] failed to load config: %v", err)
|
|
}
|
|
t.config = cfg
|
|
|
|
t.populateLocalAddresses()
|
|
t.startHTTPServer()
|
|
|
|
if !t.DisableARP {
|
|
t.readARPTable()
|
|
go t.pollARP(ctx)
|
|
}
|
|
|
|
if !t.DisableArtNet {
|
|
go t.startArtNetListener(ctx)
|
|
}
|
|
|
|
ticker := time.NewTicker(1 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
interfaces := t.listInterfaces()
|
|
t.updateInterfaces(interfaces)
|
|
<-ticker.C
|
|
}
|
|
}
|
|
|
|
func (t *Tendrils) populateLocalAddresses() {
|
|
interfaces, err := net.Interfaces()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
hostname, _ := os.Hostname()
|
|
|
|
var target *Node
|
|
for _, netIface := range interfaces {
|
|
if len(netIface.HardwareAddr) == 0 {
|
|
continue
|
|
}
|
|
|
|
var ips []net.IP
|
|
addrs, err := netIface.Addrs()
|
|
if err == nil {
|
|
for _, addr := range addrs {
|
|
if ipnet, ok := addr.(*net.IPNet); ok {
|
|
if ipnet.IP.To4() != nil && !ipnet.IP.IsLoopback() {
|
|
ips = append(ips, ipnet.IP)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
t.nodes.Update(target, netIface.HardwareAddr, ips, netIface.Name, hostname, "local")
|
|
if target == nil {
|
|
target = t.nodes.GetByMAC(netIface.HardwareAddr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *Tendrils) getLocalNode() *Node {
|
|
interfaces, err := net.Interfaces()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
for _, iface := range interfaces {
|
|
if len(iface.HardwareAddr) > 0 {
|
|
if node := t.nodes.GetByMAC(iface.HardwareAddr); node != nil {
|
|
return node
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *Tendrils) listInterfaces() []net.Interface {
|
|
interfaces, err := net.Interfaces()
|
|
if err != nil {
|
|
log.Printf("[ERROR] error getting interfaces: %v", err)
|
|
return nil
|
|
}
|
|
|
|
var validInterfaces []net.Interface
|
|
for _, iface := range interfaces {
|
|
if t.Interface != "" && iface.Name != t.Interface {
|
|
continue
|
|
}
|
|
if iface.Flags&net.FlagUp == 0 {
|
|
continue
|
|
}
|
|
if iface.Flags&net.FlagLoopback != 0 {
|
|
continue
|
|
}
|
|
if iface.Flags&net.FlagPointToPoint != 0 {
|
|
continue
|
|
}
|
|
if iface.Flags&net.FlagBroadcast == 0 {
|
|
continue
|
|
}
|
|
if len(iface.HardwareAddr) == 0 {
|
|
continue
|
|
}
|
|
|
|
addrs, err := iface.Addrs()
|
|
if err != nil || len(addrs) == 0 {
|
|
continue
|
|
}
|
|
|
|
validInterfaces = append(validInterfaces, iface)
|
|
}
|
|
|
|
return validInterfaces
|
|
}
|
|
|
|
func (t *Tendrils) updateInterfaces(interfaces []net.Interface) {
|
|
current := map[string]bool{}
|
|
for _, iface := range interfaces {
|
|
current[iface.Name] = true
|
|
}
|
|
|
|
for name, cancel := range t.activeInterfaces {
|
|
if !current[name] {
|
|
log.Printf("[iface] remove: %s", name)
|
|
cancel()
|
|
delete(t.activeInterfaces, name)
|
|
}
|
|
}
|
|
|
|
for _, iface := range interfaces {
|
|
if _, exists := t.activeInterfaces[iface.Name]; !exists {
|
|
log.Printf("[iface] add: %s", iface.Name)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.activeInterfaces[iface.Name] = cancel
|
|
t.startInterface(ctx, iface)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *Tendrils) startInterface(ctx context.Context, iface net.Interface) {
|
|
go t.pingBroadcast(ctx, iface)
|
|
go t.listenBroadcast(ctx, iface)
|
|
|
|
if !t.DisableLLDP {
|
|
go t.listenLLDP(ctx, iface)
|
|
}
|
|
if !t.DisableIGMP {
|
|
go t.listenIGMP(ctx, iface)
|
|
}
|
|
if !t.DisableMDNS {
|
|
go t.listenMDNS(ctx, iface)
|
|
}
|
|
if !t.DisableArtNet {
|
|
go t.startArtNetPoller(ctx, iface)
|
|
}
|
|
if !t.DisableDante {
|
|
go t.listenDante(ctx, iface)
|
|
}
|
|
if !t.DisableBMD {
|
|
go t.listenBMD(ctx, iface)
|
|
}
|
|
if !t.DisableShure {
|
|
go t.listenShure(ctx, iface)
|
|
}
|
|
}
|
|
|
|
func (t *Tendrils) pollNode(node *Node) {
|
|
t.nodes.mu.RLock()
|
|
var ips []net.IP
|
|
for _, iface := range node.Interfaces {
|
|
for ipStr := range iface.IPs {
|
|
ip := net.ParseIP(ipStr)
|
|
if ip != nil && ip.To4() != nil {
|
|
ips = append(ips, ip)
|
|
}
|
|
}
|
|
}
|
|
nodeName := node.DisplayName()
|
|
t.nodes.mu.RUnlock()
|
|
|
|
if !t.DisableSNMP {
|
|
for _, ip := range ips {
|
|
t.querySNMPDevice(node, ip)
|
|
}
|
|
}
|
|
|
|
if !t.DisableBMD && nodeName == "" {
|
|
for _, ip := range ips {
|
|
t.probeBMDDevice(ip)
|
|
}
|
|
}
|
|
|
|
if !t.DisableYamaha && nodeName == "" {
|
|
for _, ip := range ips {
|
|
t.probeYamahaDevice(ip)
|
|
}
|
|
}
|
|
|
|
if !t.DisableDante {
|
|
for _, ip := range ips {
|
|
t.probeDanteDevice(ip)
|
|
}
|
|
}
|
|
}
|