Split LLDP code into separate file and refactor interface tracking

This commit is contained in:
Ian Gulliver
2025-11-28 15:44:28 -08:00
parent 1073d0a267
commit b85925f644
3 changed files with 70 additions and 58 deletions

View File

@@ -3,3 +3,5 @@
- Use all-lowercase log messages - Use all-lowercase log messages
- Prepend log messages with [ERROR] if applicable - Prepend log messages with [ERROR] if applicable
- Don't mention claude in commit messages. Keep them to a single, short, descriptive sentence - Don't mention claude in commit messages. Keep them to a single, short, descriptive sentence
- Always push after commiting
- Use git add -A so you don't miss files when committing

56
lldp.go Normal file
View File

@@ -0,0 +1,56 @@
package tendrils
import (
"context"
"log"
"net"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
)
func (t *Tendrils) listenLLDP(ctx context.Context, iface net.Interface) {
handle, err := pcap.OpenLive(iface.Name, 65536, true, 5*time.Second)
if err != nil {
log.Printf("[ERROR] failed to open interface %s: %v", iface.Name, err)
return
}
defer handle.Close()
if err := handle.SetBPFFilter("ether proto 0x88cc"); err != nil {
log.Printf("[ERROR] failed to set BPF filter on %s: %v", iface.Name, err)
return
}
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
packets := packetSource.Packets()
for {
select {
case <-ctx.Done():
return
case packet, ok := <-packets:
if !ok {
return
}
t.handleLLDPPacket(iface.Name, packet)
}
}
}
func (t *Tendrils) handleLLDPPacket(ifaceName string, packet gopacket.Packet) {
lldpLayer := packet.Layer(layers.LayerTypeLinkLayerDiscovery)
if lldpLayer == nil {
return
}
lldp, ok := lldpLayer.(*layers.LinkLayerDiscovery)
if !ok {
return
}
log.Printf("[%s] lldp packet received: ChassisID=%x PortID=%s TTL=%d",
ifaceName, lldp.ChassisID.ID, lldp.PortID.ID, lldp.TTL)
}

View File

@@ -2,23 +2,18 @@ package tendrils
import ( import (
"context" "context"
"fmt"
"log" "log"
"net" "net"
"time" "time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
) )
type Tendrils struct { type Tendrils struct {
goroutines map[string]context.CancelFunc activeInterfaces map[string]context.CancelFunc
} }
func New() *Tendrils { func New() *Tendrils {
return &Tendrils{ return &Tendrils{
goroutines: map[string]context.CancelFunc{}, activeInterfaces: map[string]context.CancelFunc{},
} }
} }
@@ -28,7 +23,7 @@ func (t *Tendrils) Run() {
for { for {
interfaces := t.listInterfaces() interfaces := t.listInterfaces()
t.updateGoroutines(interfaces) t.updateInterfaces(interfaces)
<-ticker.C <-ticker.C
} }
} }
@@ -69,71 +64,30 @@ func (t *Tendrils) listInterfaces() []net.Interface {
return validInterfaces return validInterfaces
} }
func (t *Tendrils) updateGoroutines(interfaces []net.Interface) { func (t *Tendrils) updateInterfaces(interfaces []net.Interface) {
current := map[string]bool{} current := map[string]bool{}
for _, iface := range interfaces { for _, iface := range interfaces {
current[iface.Name] = true current[iface.Name] = true
} }
for name, cancel := range t.goroutines { for name, cancel := range t.activeInterfaces {
if !current[name] { if !current[name] {
log.Printf("interface removed: %s", name) log.Printf("interface removed: %s", name)
cancel() cancel()
delete(t.goroutines, name) delete(t.activeInterfaces, name)
} }
} }
for _, iface := range interfaces { for _, iface := range interfaces {
if _, exists := t.goroutines[iface.Name]; !exists { if _, exists := t.activeInterfaces[iface.Name]; !exists {
log.Printf("interface added: %s", iface.Name) log.Printf("interface added: %s", iface.Name)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
t.goroutines[iface.Name] = cancel t.activeInterfaces[iface.Name] = cancel
go t.handleInterface(ctx, iface) t.startInterface(ctx, iface)
} }
} }
} }
func (t *Tendrils) handleInterface(ctx context.Context, iface net.Interface) { func (t *Tendrils) startInterface(ctx context.Context, iface net.Interface) {
handle, err := pcap.OpenLive(iface.Name, 65536, true, 5*time.Second) go t.listenLLDP(ctx, iface)
if err != nil {
log.Printf("[ERROR] failed to open interface %s: %v", iface.Name, err)
return
}
defer handle.Close()
bpfFilter := fmt.Sprintf("ether proto 0x88cc")
if err := handle.SetBPFFilter(bpfFilter); err != nil {
log.Printf("[ERROR] failed to set BPF filter on %s: %v", iface.Name, err)
return
}
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
packets := packetSource.Packets()
for {
select {
case <-ctx.Done():
return
case packet, ok := <-packets:
if !ok {
return
}
t.handleLLDPPacket(iface.Name, packet)
}
}
}
func (t *Tendrils) handleLLDPPacket(ifaceName string, packet gopacket.Packet) {
lldpLayer := packet.Layer(layers.LayerTypeLinkLayerDiscovery)
if lldpLayer == nil {
return
}
lldp, ok := lldpLayer.(*layers.LinkLayerDiscovery)
if !ok {
return
}
log.Printf("[%s] lldp packet received: ChassisID=%x PortID=%s TTL=%d",
ifaceName, lldp.ChassisID.ID, lldp.PortID.ID, lldp.TTL)
} }