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() }