package dnsmasq import ( "fmt" "net/netip" "os" "strconv" "strings" "time" ) type Lease struct { ExpireDate time.Time Mac string IP netip.Addr Hostname string ClientID string } func (l Lease) String() string { return fmt.Sprintf("[expire on %v]: host %s IP %s (mac %s - clientID %s)", l.ExpireDate, l.Hostname, l.IP, l.Mac, l.ClientID) } func parseLeaseLine(rawLine string) (*Lease, error) { lineValues := strings.Split(rawLine, " ") if len(lineValues) != 5 { return nil, fmt.Errorf("unexpected number of values, expected 5, got %d", len(lineValues)) } expireTimeInt, err := strconv.Atoi(lineValues[0]) if err != nil { return nil, fmt.Errorf("unexpected unix timestamp value %s", lineValues[0]) } ip, err := netip.ParseAddr(lineValues[2]) if err != nil { return nil, fmt.Errorf("unexpected IP address: %s: %w", lineValues[2], err) } return &Lease{ ExpireDate: time.Unix(int64(expireTimeInt), 0), Mac: lineValues[1], IP: ip, Hostname: lineValues[3], ClientID: lineValues[4], }, nil } func GetLeases(leasesPath string) ([]*Lease, error) { fileContent, err := os.ReadFile(leasesPath) if err != nil { return nil, fmt.Errorf("failed to read leases file: %w", err) } leases := []*Lease{} for i, l := range strings.Split(string(fileContent), "\n") { if l == "" { continue } lease, err := parseLeaseLine(l) if err != nil { return nil, fmt.Errorf("failed to parse line %d: %w", i, err) } leases = append(leases, lease) } return leases, nil } func GetDynamicLeases(leasesPath string, dhcpStart, dhcpEnd netip.Addr) ([]*Lease, error) { allLeases, err := GetLeases(leasesPath) if err != nil { return nil, err } filteredLeases := []*Lease{} for _, l := range allLeases { if l.IP.Less(dhcpEnd) && dhcpStart.Less(l.IP) { filteredLeases = append(filteredLeases, l) } } return filteredLeases, nil }