Files
tendrils/tendrils.go

303 lines
5.8 KiB
Go

package tendrils
import (
"context"
"log"
"net"
"os"
"os/signal"
"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
danteFlows *DanteFlows
config *Config
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
EnableHTTPS bool
}
func New() *Tendrils {
t := &Tendrils{
activeInterfaces: map[string]context.CancelFunc{},
artnet: NewArtNetNodes(),
danteFlows: NewDanteFlows(),
}
t.nodes = NewNodes(t)
return t
}
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.Printf("[ERROR] failed to load config: %v", err)
} else {
t.config = cfg
}
t.populateLocalAddresses()
t.startHTTPServer()
if !t.DisableARP {
t.readARPTable()
go t.pollARP(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)
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.listenArtNet(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)
}
}
}