Add broadcast packet tracking with rate monitoring
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
140
broadcast.go
140
broadcast.go
@@ -4,12 +4,152 @@ import (
|
||||
"context"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/pcap"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
type BroadcastSample struct {
|
||||
Time time.Time
|
||||
Packets uint64
|
||||
Bytes uint64
|
||||
}
|
||||
|
||||
type BroadcastStats struct {
|
||||
mu sync.RWMutex
|
||||
samples []BroadcastSample
|
||||
totalPackets uint64
|
||||
totalBytes uint64
|
||||
windowSize time.Duration
|
||||
lastNotify time.Time
|
||||
notifyMinRate time.Duration
|
||||
t *Tendrils
|
||||
}
|
||||
|
||||
type BroadcastStatsResponse struct {
|
||||
TotalPackets uint64 `json:"total_packets"`
|
||||
TotalBytes uint64 `json:"total_bytes"`
|
||||
PacketsPerS float64 `json:"packets_per_s"`
|
||||
BytesPerS float64 `json:"bytes_per_s"`
|
||||
WindowSecs float64 `json:"window_secs"`
|
||||
}
|
||||
|
||||
func NewBroadcastStats(t *Tendrils) *BroadcastStats {
|
||||
return &BroadcastStats{
|
||||
samples: []BroadcastSample{},
|
||||
windowSize: 60 * time.Second,
|
||||
notifyMinRate: 1 * time.Second,
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BroadcastStats) Record(packets, bytes uint64) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
b.samples = append(b.samples, BroadcastSample{
|
||||
Time: now,
|
||||
Packets: packets,
|
||||
Bytes: bytes,
|
||||
})
|
||||
b.totalPackets += packets
|
||||
b.totalBytes += bytes
|
||||
|
||||
cutoff := now.Add(-b.windowSize)
|
||||
for len(b.samples) > 0 && b.samples[0].Time.Before(cutoff) {
|
||||
b.samples = b.samples[1:]
|
||||
}
|
||||
|
||||
if now.Sub(b.lastNotify) >= b.notifyMinRate {
|
||||
b.lastNotify = now
|
||||
b.t.NotifyUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BroadcastStats) GetStats() BroadcastStatsResponse {
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
|
||||
now := time.Now()
|
||||
cutoff := now.Add(-b.windowSize)
|
||||
|
||||
var windowPackets, windowBytes uint64
|
||||
var oldestTime time.Time
|
||||
|
||||
for _, s := range b.samples {
|
||||
if s.Time.After(cutoff) {
|
||||
if oldestTime.IsZero() || s.Time.Before(oldestTime) {
|
||||
oldestTime = s.Time
|
||||
}
|
||||
windowPackets += s.Packets
|
||||
windowBytes += s.Bytes
|
||||
}
|
||||
}
|
||||
|
||||
var windowSecs float64
|
||||
if !oldestTime.IsZero() {
|
||||
windowSecs = now.Sub(oldestTime).Seconds()
|
||||
}
|
||||
if windowSecs < 1 {
|
||||
windowSecs = 1
|
||||
}
|
||||
|
||||
return BroadcastStatsResponse{
|
||||
TotalPackets: b.totalPackets,
|
||||
TotalBytes: b.totalBytes,
|
||||
PacketsPerS: float64(windowPackets) / windowSecs,
|
||||
BytesPerS: float64(windowBytes) / windowSecs,
|
||||
WindowSecs: windowSecs,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tendrils) listenBroadcast(ctx context.Context, iface net.Interface) {
|
||||
handle, err := pcap.OpenLive(iface.Name, 65536, true, 5*time.Second)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] broadcast: failed to open interface %s: %v", iface.Name, err)
|
||||
return
|
||||
}
|
||||
defer handle.Close()
|
||||
|
||||
if err := handle.SetBPFFilter("ether broadcast"); err != nil {
|
||||
log.Printf("[ERROR] broadcast: 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.handleBroadcastPacket(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tendrils) handleBroadcastPacket(packet gopacket.Packet) {
|
||||
if t.broadcast == nil {
|
||||
return
|
||||
}
|
||||
|
||||
packetLen := uint64(len(packet.Data()))
|
||||
t.broadcast.Record(1, packetLen)
|
||||
|
||||
if t.DebugBroadcast {
|
||||
log.Printf("[broadcast] packet: %d bytes", packetLen)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tendrils) pingBroadcast(ctx context.Context, iface net.Interface) {
|
||||
_, broadcast := getInterfaceIPv4(iface)
|
||||
if broadcast == nil {
|
||||
|
||||
Reference in New Issue
Block a user