177 lines
3.9 KiB
Go
177 lines
3.9 KiB
Go
package arp
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/nemunaire/repeater/internal/station/backend"
|
|
)
|
|
|
|
// Backend implements StationBackend using ARP table discovery
|
|
type Backend struct {
|
|
arpTablePath string
|
|
lastStations map[string]backend.Station // Key: MAC address
|
|
callbacks backend.EventCallbacks
|
|
stopChan chan struct{}
|
|
mu sync.RWMutex
|
|
running bool
|
|
}
|
|
|
|
// NewBackend creates a new ARP backend
|
|
func NewBackend() *Backend {
|
|
return &Backend{
|
|
lastStations: make(map[string]backend.Station),
|
|
stopChan: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// Initialize initializes the ARP backend
|
|
func (b *Backend) Initialize(config backend.BackendConfig) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
b.arpTablePath = config.ARPTablePath
|
|
if b.arpTablePath == "" {
|
|
b.arpTablePath = "/proc/net/arp"
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close cleans up backend resources
|
|
func (b *Backend) Close() error {
|
|
b.StopEventMonitoring()
|
|
return nil
|
|
}
|
|
|
|
// GetStations returns all connected stations from ARP table
|
|
func (b *Backend) GetStations() ([]backend.Station, error) {
|
|
b.mu.RLock()
|
|
arpTablePath := b.arpTablePath
|
|
b.mu.RUnlock()
|
|
|
|
arpEntries, err := parseARPTable(arpTablePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var stations []backend.Station
|
|
for _, entry := range arpEntries {
|
|
// Only include entries with valid flags (2 = COMPLETE, 6 = COMPLETE|PERM)
|
|
if entry.Flags == 2 || entry.Flags == 6 {
|
|
st := backend.Station{
|
|
MAC: entry.HWAddress.String(),
|
|
IP: entry.IP.String(),
|
|
Hostname: "", // No hostname available from ARP
|
|
Type: backend.GuessDeviceType("", entry.HWAddress.String()),
|
|
Signal: 0, // Not available from ARP
|
|
RxBytes: 0, // Not available from ARP
|
|
TxBytes: 0, // Not available from ARP
|
|
ConnectedAt: time.Now(),
|
|
}
|
|
stations = append(stations, st)
|
|
}
|
|
}
|
|
|
|
return stations, nil
|
|
}
|
|
|
|
// StartEventMonitoring starts monitoring for station events via polling
|
|
func (b *Backend) StartEventMonitoring(callbacks backend.EventCallbacks) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
if b.running {
|
|
return nil
|
|
}
|
|
|
|
b.callbacks = callbacks
|
|
b.running = true
|
|
|
|
// Start polling goroutine
|
|
go b.pollLoop()
|
|
|
|
return nil
|
|
}
|
|
|
|
// StopEventMonitoring stops event monitoring
|
|
func (b *Backend) StopEventMonitoring() {
|
|
b.mu.Lock()
|
|
if !b.running {
|
|
b.mu.Unlock()
|
|
return
|
|
}
|
|
b.running = false
|
|
b.mu.Unlock()
|
|
|
|
close(b.stopChan)
|
|
}
|
|
|
|
// SupportsRealTimeEvents returns false (ARP is polling-based)
|
|
func (b *Backend) SupportsRealTimeEvents() bool {
|
|
return false
|
|
}
|
|
|
|
// pollLoop polls the ARP table and simulates events
|
|
func (b *Backend) pollLoop() {
|
|
ticker := time.NewTicker(10 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
b.checkForChanges()
|
|
case <-b.stopChan:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// checkForChanges compares current state with last state and triggers callbacks
|
|
func (b *Backend) checkForChanges() {
|
|
// Get current stations
|
|
current, err := b.GetStations()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Build map of current stations
|
|
currentMap := make(map[string]backend.Station)
|
|
for _, station := range current {
|
|
currentMap[station.MAC] = station
|
|
}
|
|
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
// Check for new stations (connected)
|
|
for mac, station := range currentMap {
|
|
if _, exists := b.lastStations[mac]; !exists {
|
|
// New station connected
|
|
if b.callbacks.OnStationConnected != nil {
|
|
go b.callbacks.OnStationConnected(station)
|
|
}
|
|
} else {
|
|
// Check for updates (IP change, etc.)
|
|
oldStation := b.lastStations[mac]
|
|
if oldStation.IP != station.IP || oldStation.Hostname != station.Hostname {
|
|
if b.callbacks.OnStationUpdated != nil {
|
|
go b.callbacks.OnStationUpdated(station)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for disconnected stations
|
|
for mac := range b.lastStations {
|
|
if _, exists := currentMap[mac]; !exists {
|
|
// Station disconnected
|
|
if b.callbacks.OnStationDisconnected != nil {
|
|
go b.callbacks.OnStationDisconnected(mac)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update last state
|
|
b.lastStations = currentMap
|
|
}
|