Only select dynamic ip addresses

This commit is contained in:
Melora Hugues 2024-10-03 18:30:23 +02:00
parent ac040ac6f7
commit a484431d13
4 changed files with 157 additions and 7 deletions

View file

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/netip"
"os" "os"
) )
@ -13,9 +14,39 @@ type HealthchecksConfig struct {
PingKey string `json:"ping_key"` PingKey string `json:"ping_key"`
} }
type DNSMasqConfig struct {
DHCPRangeStart netip.Addr
DHCPRangeEnd netip.Addr
LeasesPath string
}
type Config struct { type Config struct {
HealthchecksConfig `json:"healthchecks"` HealthchecksConfig `json:"healthchecks"`
LeasesPath string `json:"leases_path"` DNSMasqConfigFile string `json:"dnsmasq_config"`
DNSMasqConfig
}
func (c *Config) parseDNSMasqConf() error {
dnsMasqRootContent, err := os.ReadFile(c.DNSMasqConfigFile)
if err != nil {
return fmt.Errorf("failed to read dnsmasq config: %w", err)
}
dnsmasqRawConf, err := parseDNSMasqOptions(string(dnsMasqRootContent))
if err != nil {
return fmt.Errorf("failed to parse dnsmasq config: %w", err)
}
c.LeasesPath = dnsmasqRawConf.leaseFile
c.DHCPRangeStart, err = netip.ParseAddr(dnsmasqRawConf.rangeStart)
if err != nil {
return fmt.Errorf("failed to parse dhcp range start: %w", err)
}
c.DHCPRangeEnd, err = netip.ParseAddr(dnsmasqRawConf.rangeEnd)
if err != nil {
return fmt.Errorf("failed to parse dhcp range end: %w", err)
}
return nil
} }
func defaultConfig() *Config { func defaultConfig() *Config {
@ -23,7 +54,7 @@ func defaultConfig() *Config {
HealthchecksConfig: HealthchecksConfig{ HealthchecksConfig: HealthchecksConfig{
Enabled: false, Enabled: false,
}, },
LeasesPath: "/var/lib/misc/dnsmasq.leases", DNSMasqConfigFile: "etc/dnsmasq.conf",
} }
} }
@ -31,7 +62,8 @@ func New(path string) (*Config, error) {
fileContent, err := os.ReadFile(path) fileContent, err := os.ReadFile(path)
if err != nil { if err != nil {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
return defaultConfig(), nil conf := defaultConfig()
return conf, conf.parseDNSMasqConf()
} }
return nil, fmt.Errorf("failed to read config file %w", err) return nil, fmt.Errorf("failed to read config file %w", err)
} }
@ -40,5 +72,5 @@ func New(path string) (*Config, error) {
if err := json.Unmarshal(fileContent, &c); err != nil { if err := json.Unmarshal(fileContent, &c); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err) return nil, fmt.Errorf("failed to parse config file: %w", err)
} }
return &c, nil return &c, c.parseDNSMasqConf()
} }

View file

@ -0,0 +1,96 @@
package config
import (
"fmt"
"os"
"path"
"strings"
)
const (
leaseFileKey = "dhcp-leasefile"
rangeKey = "dhcp-range"
confFileKey = "conf-file"
confDirKey = "conf-dir"
)
func tryParseLineOption(line, key string) (string, bool) {
if !strings.HasPrefix(line, key) {
return "", false
}
_, val, found := strings.Cut(line, "=")
return val, found
}
type dnsmasqConfOptions struct {
leaseFile string
rangeStart string
rangeEnd string
}
func defaultDNSMasqOptions() map[string]string {
return map[string]string{
confDirKey: "/etc/dnsmasq.d",
confFileKey: "",
leaseFileKey: "/var/lib/misc/dnsmasq.leases",
rangeKey: "",
}
}
func parseDNSMasqConfFile(content string, options map[string]string) {
for _, l := range strings.Split(content, "\n") {
for key := range options {
newVal, found := tryParseLineOption(l, key)
if found {
options[key] = newVal
}
}
}
}
func parseDNSMasqOptions(rootFileContent string) (dnsmasqConfOptions, error) {
rawOptions := defaultDNSMasqOptions()
// start with parsing the root file
parseDNSMasqConfFile(rootFileContent, rawOptions)
// get all additional conf files in the provided directory
confDir := rawOptions[confDirKey]
if confDir != "" {
entries, err := os.ReadDir(confDir)
if err != nil {
return dnsmasqConfOptions{}, fmt.Errorf("failed to get list of additional config files: %w", err)
}
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".conf") {
continue
}
fileContent, err := os.ReadFile(path.Join(confDir, e.Name()))
if err != nil {
return dnsmasqConfOptions{}, fmt.Errorf("failed to read sub config file: %w", err)
}
parseDNSMasqConfFile(string(fileContent), rawOptions)
}
}
// get any additional config file
if additionalFile := rawOptions[confFileKey]; additionalFile != "" {
filecontent, err := os.ReadFile(additionalFile)
if err != nil {
return dnsmasqConfOptions{}, fmt.Errorf("failed to read additional config file: %w", err)
}
parseDNSMasqConfFile(string(filecontent), rawOptions)
}
dhcpRange := strings.Split(rawOptions[rangeKey], ",")
if len(dhcpRange) < 2 {
return dnsmasqConfOptions{}, fmt.Errorf("invalid value for DHCP range: %s", rawOptions[rangeKey])
}
return dnsmasqConfOptions{
leaseFile: rawOptions[leaseFileKey],
rangeStart: dhcpRange[0],
rangeEnd: dhcpRange[1],
}, nil
}

View file

@ -2,6 +2,7 @@ package dnsmasq
import ( import (
"fmt" "fmt"
"net/netip"
"os" "os"
"strconv" "strconv"
"strings" "strings"
@ -11,7 +12,7 @@ import (
type Lease struct { type Lease struct {
ExpireDate time.Time ExpireDate time.Time
Mac string Mac string
IP string IP netip.Addr
Hostname string Hostname string
ClientID string ClientID string
} }
@ -31,10 +32,15 @@ func parseLeaseLine(rawLine string) (*Lease, error) {
return nil, fmt.Errorf("unexpected unix timestamp value %s", lineValues[0]) 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{ return &Lease{
ExpireDate: time.Unix(int64(expireTimeInt), 0), ExpireDate: time.Unix(int64(expireTimeInt), 0),
Mac: lineValues[1], Mac: lineValues[1],
IP: lineValues[2], IP: ip,
Hostname: lineValues[3], Hostname: lineValues[3],
ClientID: lineValues[4], ClientID: lineValues[4],
}, nil }, nil
@ -59,3 +65,19 @@ func GetLeases(leasesPath string) ([]*Lease, error) {
} }
return leases, nil 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
}

View file

@ -31,7 +31,7 @@ func main() {
fmt.Fprintf(os.Stderr, "Failed to notify process start: %s\n", err) fmt.Fprintf(os.Stderr, "Failed to notify process start: %s\n", err)
} }
leases, err := dnsmasq.GetLeases(conf.LeasesPath) leases, err := dnsmasq.GetDynamicLeases(conf.LeasesPath, conf.DHCPRangeStart, conf.DHCPRangeEnd)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse leases: %s\n", err) fmt.Fprintf(os.Stderr, "Failed to parse leases: %s\n", err)
if err := healthchecks.Failure(context.Background(), conf.HealthchecksConfig, healtchecksClt, check, "failed to parse lease file"); err != nil { if err := healthchecks.Failure(context.Background(), conf.HealthchecksConfig, healtchecksClt, check, "failed to parse lease file"); err != nil {