diff --git a/server.go b/server.go index 7d01c97..6712f79 100644 --- a/server.go +++ b/server.go @@ -120,41 +120,86 @@ func sanitize(s string) string { }, s)) } -// pinLease appends a dhcp-host line to dnsmasq.conf. +// pinLease appends a dhcp-host line to dnsmasq.conf. If hostname is provided, +// also appends an address=/hostname/IP line so the name resolves network-wide via dnsmasq DNS. func pinLease(mac, ip, hostname string) error { - line := fmt.Sprintf("dhcp-host=%s,%s,%s", sanitize(mac), sanitize(ip), sanitize(hostname)) + safeMAC := sanitize(mac) + safeIP := sanitize(ip) + safeHost := sanitize(hostname) + + dhcpLine := fmt.Sprintf("dhcp-host=%s,%s,%s", safeMAC, safeIP, safeHost) f, err := os.OpenFile(confFile, os.O_APPEND|os.O_WRONLY, 0) if err != nil { return fmt.Errorf("open config for append: %w", err) } defer f.Close() - if _, err := fmt.Fprintln(f, line); err != nil { + if _, err := fmt.Fprintln(f, dhcpLine); err != nil { return fmt.Errorf("write dhcp-host line: %w", err) } + + // If a custom hostname was given, add a DNS A record too. + if safeHost != "" { + dnsLine := fmt.Sprintf("address=/%s/%s", safeHost, safeIP) + if _, err := fmt.Fprintln(f, dnsLine); err != nil { + return fmt.Errorf("write address line: %w", err) + } + } return nil } -// unpinLease removes dhcp-host lines matching the given MAC from dnsmasq.conf. +// addressPattern matches lines like: address=/hostname/IP +var addressPattern = regexp.MustCompile(`^address\s*=\s*/(.+)/(.+)$`) + +// unpinLease removes dhcp-host and associated address= lines matching the given MAC. func unpinLease(mac string) error { macLower := strings.ToLower(mac) + + // First pass: find the IP from the dhcp-host line for this MAC. + var targetIP string f, err := os.Open(confFile) if err != nil { return fmt.Errorf("open config file: %w", err) } + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if matches := dhcpHostPattern.FindStringSubmatch(line); matches != nil { + parts := strings.Split(matches[1], ",") + if len(parts) > 1 && strings.TrimSpace(strings.ToLower(parts[0])) == macLower { + targetIP = strings.TrimSpace(parts[1]) + break + } + } + } + f.Close() + + // Second pass: rebuild config, removing dhcp-host line + matching address= lines. + f, err = os.Open(confFile) + if err != nil { + return fmt.Errorf("open config file: %w", err) + } var kept []string removed := false - scanner := bufio.NewScanner(f) + scanner = bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() trimmed := strings.TrimSpace(line) + // Remove dhcp-host for this MAC. if dhcpHostPattern.MatchString(trimmed) { matchContent := dhcpHostPattern.FindStringSubmatch(trimmed)[1] parts := strings.Split(matchContent, ",") if len(parts) > 0 && strings.TrimSpace(strings.ToLower(parts[0])) == macLower { removed = true - continue // skip this line + continue + } + } + // Remove address= line for the same IP. + if targetIP != "" && addressPattern.MatchString(trimmed) { + addrMatches := addressPattern.FindStringSubmatch(trimmed) + if strings.TrimSpace(addrMatches[2]) == targetIP { + continue } } kept = append(kept, line) diff --git a/web/app.js b/web/app.js index d4d453c..5133ca0 100644 --- a/web/app.js +++ b/web/app.js @@ -47,7 +47,12 @@ function renderLeases(data) { '