From 1739b7fb875eb773f89e9f2c376d05ef87e5355f Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Fri, 28 Nov 2025 15:18:22 -0800 Subject: [PATCH] Add network interface discovery and monitoring --- cmd/tendrils/main.go | 10 +++++ go.mod | 3 ++ tendrils.go | 93 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 cmd/tendrils/main.go create mode 100644 go.mod create mode 100644 tendrils.go diff --git a/cmd/tendrils/main.go b/cmd/tendrils/main.go new file mode 100644 index 0000000..8d13302 --- /dev/null +++ b/cmd/tendrils/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/gopatchy/tendrils" +) + +func main() { + t := tendrils.New() + t.Run() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..13b8237 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/gopatchy/tendrils + +go 1.24.4 diff --git a/tendrils.go b/tendrils.go new file mode 100644 index 0000000..545051e --- /dev/null +++ b/tendrils.go @@ -0,0 +1,93 @@ +package tendrils + +import ( + "context" + "log" + "net" + "time" +) + +type Tendrils struct { + goroutines map[string]context.CancelFunc +} + +func New() *Tendrils { + return &Tendrils{ + goroutines: map[string]context.CancelFunc{}, + } +} + +func (t *Tendrils) Run() { + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + for { + interfaces := t.listInterfaces() + t.updateGoroutines(interfaces) + <-ticker.C + } +} + +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 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) updateGoroutines(interfaces []net.Interface) { + current := map[string]bool{} + for _, iface := range interfaces { + current[iface.Name] = true + } + + for name, cancel := range t.goroutines { + if !current[name] { + log.Printf("interface removed: %s", name) + cancel() + delete(t.goroutines, name) + } + } + + for _, iface := range interfaces { + if _, exists := t.goroutines[iface.Name]; !exists { + log.Printf("interface added: %s", iface.Name) + ctx, cancel := context.WithCancel(context.Background()) + t.goroutines[iface.Name] = cancel + go t.handleInterface(ctx, iface) + } + } +} + +func (t *Tendrils) handleInterface(ctx context.Context, iface net.Interface) { + <-ctx.Done() +}