repeater/internal/station/hostapd/correlation.go

130 lines
2.9 KiB
Go

package hostapd
import (
"bufio"
"log"
"os"
"strings"
"time"
"github.com/nemunaire/repeater/internal/models"
)
// DHCPCorrelator helps correlate hostapd stations with DHCP leases to discover IP addresses
type DHCPCorrelator struct {
backend *Backend
dhcpLeasesPath string
stopChan chan struct{}
running bool
}
// NewDHCPCorrelator creates a new DHCP correlator
func NewDHCPCorrelator(backend *Backend, dhcpLeasesPath string) *DHCPCorrelator {
if dhcpLeasesPath == "" {
dhcpLeasesPath = "/var/lib/dhcp/dhcpd.leases"
}
return &DHCPCorrelator{
backend: backend,
dhcpLeasesPath: dhcpLeasesPath,
stopChan: make(chan struct{}),
}
}
// Start begins periodic correlation of DHCP leases with hostapd stations
func (dc *DHCPCorrelator) Start() {
if dc.running {
return
}
dc.running = true
go dc.correlationLoop()
log.Printf("DHCP correlation started for hostapd backend")
}
// Stop stops the correlation loop
func (dc *DHCPCorrelator) Stop() {
if !dc.running {
return
}
dc.running = false
close(dc.stopChan)
log.Printf("DHCP correlation stopped")
}
// correlationLoop periodically correlates DHCP leases with stations
func (dc *DHCPCorrelator) correlationLoop() {
// Do an initial correlation immediately
dc.correlate()
// Then correlate every 10 seconds
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
dc.correlate()
case <-dc.stopChan:
return
}
}
}
// correlate performs one correlation cycle
func (dc *DHCPCorrelator) correlate() {
// Parse DHCP leases
leases, err := parseDHCPLeases(dc.dhcpLeasesPath)
if err != nil {
log.Printf("Warning: Failed to parse DHCP leases: %v", err)
return
}
// Build MAC -> IP mapping
macToIP := make(map[string]string)
for _, lease := range leases {
macToIP[lease.MAC] = lease.IP
}
// Update backend with IP mappings
dc.backend.UpdateIPMapping(macToIP)
}
// parseDHCPLeases reads and parses DHCP lease file
func parseDHCPLeases(path string) ([]models.DHCPLease, error) {
var leases []models.DHCPLease
file, err := os.Open(path)
if err != nil {
return leases, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
var currentLease models.DHCPLease
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "lease ") {
ip := strings.Fields(line)[1]
currentLease = models.DHCPLease{IP: ip}
} else if strings.Contains(line, "hardware ethernet") {
mac := strings.Fields(line)[2]
mac = strings.TrimSuffix(mac, ";")
currentLease.MAC = mac
} else if strings.Contains(line, "client-hostname") {
hostname := strings.Fields(line)[1]
hostname = strings.Trim(hostname, `";`)
currentLease.Hostname = hostname
} else if line == "}" {
if currentLease.IP != "" && currentLease.MAC != "" {
leases = append(leases, currentLease)
}
currentLease = models.DHCPLease{}
}
}
return leases, scanner.Err()
}