summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/modules/dnsmasq_dhcp/parse_configuration.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/go/collectors/go.d.plugin/modules/dnsmasq_dhcp/parse_configuration.go384
1 files changed, 384 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/dnsmasq_dhcp/parse_configuration.go b/src/go/collectors/go.d.plugin/modules/dnsmasq_dhcp/parse_configuration.go
new file mode 100644
index 000000000..24a20bb59
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/dnsmasq_dhcp/parse_configuration.go
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package dnsmasq_dhcp
+
+import (
+ "bufio"
+ "fmt"
+ "net"
+ "os"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strings"
+
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/iprange"
+)
+
+func (d *DnsmasqDHCP) parseDnsmasqDHCPConfiguration() ([]iprange.Range, []net.IP) {
+ configs := findConfigurationFiles(d.ConfPath, d.ConfDir)
+
+ dhcpRanges := d.getDHCPRanges(configs)
+ dhcpHosts := d.getDHCPHosts(configs)
+
+ return dhcpRanges, dhcpHosts
+}
+
+func (d *DnsmasqDHCP) getDHCPRanges(configs []*configFile) []iprange.Range {
+ var dhcpRanges []iprange.Range
+ var parsed string
+ seen := make(map[string]bool)
+
+ for _, conf := range configs {
+ d.Debugf("looking in '%s'", conf.path)
+
+ for _, value := range conf.get("dhcp-range") {
+ d.Debugf("found dhcp-range '%s'", value)
+ if parsed = parseDHCPRangeValue(value); parsed == "" || seen[parsed] {
+ continue
+ }
+ seen[parsed] = true
+
+ r, err := iprange.ParseRange(parsed)
+ if r == nil || err != nil {
+ d.Warningf("error on parsing dhcp-range '%s', skipping it", parsed)
+ continue
+ }
+
+ d.Debugf("adding dhcp-range '%s'", parsed)
+ dhcpRanges = append(dhcpRanges, r)
+ }
+ }
+
+ // order: ipv4, ipv6
+ sort.Slice(dhcpRanges, func(i, j int) bool { return dhcpRanges[i].Family() < dhcpRanges[j].Family() })
+
+ return dhcpRanges
+}
+
+func (d *DnsmasqDHCP) getDHCPHosts(configs []*configFile) []net.IP {
+ var dhcpHosts []net.IP
+ seen := make(map[string]bool)
+ var parsed string
+
+ for _, conf := range configs {
+ d.Debugf("looking in '%s'", conf.path)
+
+ for _, value := range conf.get("dhcp-host") {
+ d.Debugf("found dhcp-host '%s'", value)
+ if parsed = parseDHCPHostValue(value); parsed == "" || seen[parsed] {
+ continue
+ }
+ seen[parsed] = true
+
+ v := net.ParseIP(parsed)
+ if v == nil {
+ d.Warningf("error on parsing dhcp-host '%s', skipping it", parsed)
+ continue
+ }
+
+ d.Debugf("adding dhcp-host '%s'", parsed)
+ dhcpHosts = append(dhcpHosts, v)
+ }
+ }
+ return dhcpHosts
+}
+
+/*
+Examples:
+ - 192.168.0.50,192.168.0.150,12h
+ - 192.168.0.50,192.168.0.150,255.255.255.0,12h
+ - set:red,1.1.1.50,1.1.2.150, 255.255.252.0
+ - 192.168.0.0,static
+ - 1234::2,1234::500, 64, 12h
+ - 1234::2,1234::500
+ - 1234::2,1234::500, slaac
+ - 1234::,ra-only
+ - 1234::,ra-names
+ - 1234::,ra-stateless
+*/
+var reDHCPRange = regexp.MustCompile(`([0-9a-f.:]+),([0-9a-f.:]+)`)
+
+func parseDHCPRangeValue(s string) (r string) {
+ if strings.Contains(s, "ra-stateless") {
+ return
+ }
+
+ match := reDHCPRange.FindStringSubmatch(s)
+ if match == nil {
+ return
+ }
+
+ start, end := net.ParseIP(match[1]), net.ParseIP(match[2])
+ if start == nil || end == nil {
+ return
+ }
+
+ return fmt.Sprintf("%s-%s", start, end)
+}
+
+/*
+Examples:
+ - 11:22:33:44:55:66,192.168.0.60
+ - 11:22:33:44:55:66,fred,192.168.0.60,45m
+ - 11:22:33:44:55:66,12:34:56:78:90:12,192.168.0.60
+ - bert,192.168.0.70,infinite
+ - id:01:02:02:04,192.168.0.60
+ - id:ff:00:00:00:00:00:02:00:00:02:c9:00:f4:52:14:03:00:28:05:81,192.168.0.61
+ - id:marjorie,192.168.0.60
+ - id:00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2, fred, [1234::5]
+*/
+var (
+ reDHCPHostV4 = regexp.MustCompile(`(?:[0-9]{1,3}\.){3}[0-9]{1,3}`)
+ reDHCPHostV6 = regexp.MustCompile(`\[([0-9a-f.:]+)]`)
+)
+
+func parseDHCPHostValue(s string) (r string) {
+ if strings.Contains(s, "[") {
+ return strings.Trim(reDHCPHostV6.FindString(s), "[]")
+ }
+ return reDHCPHostV4.FindString(s)
+}
+
+type (
+ extension string
+
+ extensions []extension
+
+ configDir struct {
+ path string
+ include extensions
+ exclude extensions
+ }
+)
+
+func (e extension) match(filename string) bool {
+ return strings.HasSuffix(filename, string(e))
+}
+
+func (es extensions) match(filename string) bool {
+ for _, e := range es {
+ if e.match(filename) {
+ return true
+ }
+ }
+ return false
+}
+
+func parseConfDir(confDirStr string) configDir {
+ // # Include all the files in a directory except those ending in .bak
+ //#conf-dir=/etc/dnsmasq.d,.bak
+ //# Include all files in a directory which end in .conf
+ //#conf-dir=/etc/dnsmasq.d/,*.conf
+
+ parts := strings.Split(confDirStr, ",")
+ cd := configDir{path: parts[0]}
+
+ for _, arg := range parts[1:] {
+ arg = strings.TrimSpace(arg)
+ if strings.HasPrefix(arg, "*") {
+ cd.include = append(cd.include, extension(arg[1:]))
+ } else {
+ cd.exclude = append(cd.exclude, extension(arg))
+ }
+ }
+ return cd
+}
+
+func (cd configDir) isValidFilename(filename string) bool {
+ switch {
+ default:
+ return true
+ case strings.HasPrefix(filename, "."):
+ case strings.HasPrefix(filename, "~"):
+ case strings.HasPrefix(filename, "#") && strings.HasSuffix(filename, "#"):
+ }
+ return false
+}
+
+func (cd configDir) match(filename string) bool {
+ switch {
+ default:
+ return true
+ case !cd.isValidFilename(filename):
+ case len(cd.include) > 0 && !cd.include.match(filename):
+ case cd.exclude.match(filename):
+ }
+ return false
+}
+
+func (cd configDir) findConfigs() ([]string, error) {
+ fis, err := os.ReadDir(cd.path)
+ if err != nil {
+ return nil, err
+ }
+
+ var files []string
+ for _, fi := range fis {
+ info, err := fi.Info()
+ if err != nil {
+ return nil, err
+ }
+ if !info.Mode().IsRegular() || !cd.match(fi.Name()) {
+ continue
+ }
+ files = append(files, filepath.Join(cd.path, fi.Name()))
+ }
+ return files, nil
+}
+
+func openFile(filepath string) (f *os.File, err error) {
+ defer func() {
+ if err != nil && f != nil {
+ _ = f.Close()
+ }
+ }()
+
+ f, err = os.Open(filepath)
+ if err != nil {
+ return nil, err
+ }
+
+ fi, err := f.Stat()
+ if err != nil {
+ return nil, err
+ }
+
+ if !fi.Mode().IsRegular() {
+ return nil, fmt.Errorf("'%s' is not a regular file", filepath)
+ }
+ return f, nil
+}
+
+type (
+ configOption struct {
+ key, value string
+ }
+
+ configFile struct {
+ path string
+ options []configOption
+ }
+)
+
+func (cf *configFile) get(name string) []string {
+ var options []string
+ for _, o := range cf.options {
+ if o.key != name {
+ continue
+ }
+ options = append(options, o.value)
+ }
+ return options
+}
+
+func parseConfFile(filename string) (*configFile, error) {
+ f, err := openFile(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer func() { _ = f.Close() }()
+
+ cf := configFile{path: filename}
+ s := bufio.NewScanner(f)
+ for s.Scan() {
+ line := strings.TrimSpace(s.Text())
+ if strings.HasPrefix(line, "#") {
+ continue
+ }
+
+ if !strings.Contains(line, "=") {
+ continue
+ }
+
+ line = strings.ReplaceAll(line, " ", "")
+ parts := strings.Split(line, "=")
+ if len(parts) != 2 {
+ continue
+ }
+
+ cf.options = append(cf.options, configOption{key: parts[0], value: parts[1]})
+ }
+ return &cf, nil
+}
+
+type ConfigFinder struct {
+ entryConfig string
+ entryDir string
+ visitedConfigs map[string]bool
+ visitedDirs map[string]bool
+}
+
+func (f *ConfigFinder) find() []*configFile {
+ f.visitedConfigs = make(map[string]bool)
+ f.visitedDirs = make(map[string]bool)
+
+ configs := f.recursiveFind(f.entryConfig)
+
+ for _, file := range f.entryDirConfigs() {
+ configs = append(configs, f.recursiveFind(file)...)
+ }
+ return configs
+}
+
+func (f *ConfigFinder) entryDirConfigs() []string {
+ if f.entryDir == "" {
+ return nil
+ }
+ files, err := parseConfDir(f.entryDir).findConfigs()
+ if err != nil {
+ return nil
+ }
+ return files
+}
+
+func (f *ConfigFinder) recursiveFind(filename string) (configs []*configFile) {
+ if f.visitedConfigs[filename] {
+ return nil
+ }
+
+ config, err := parseConfFile(filename)
+ if err != nil {
+ return nil
+ }
+
+ files, dirs := config.get("conf-file"), config.get("conf-dir")
+
+ f.visitedConfigs[filename] = true
+ configs = append(configs, config)
+
+ for _, file := range files {
+ configs = append(configs, f.recursiveFind(file)...)
+ }
+
+ for _, dir := range dirs {
+ if dir == "" {
+ continue
+ }
+
+ d := parseConfDir(dir)
+
+ if f.visitedDirs[d.path] {
+ continue
+ }
+ f.visitedDirs[d.path] = true
+
+ files, err = d.findConfigs()
+ if err != nil {
+ continue
+ }
+
+ for _, file := range files {
+ configs = append(configs, f.recursiveFind(file)...)
+ }
+ }
+ return configs
+}
+
+func findConfigurationFiles(entryConfig string, entryDir string) []*configFile {
+ cf := ConfigFinder{
+ entryConfig: entryConfig,
+ entryDir: entryDir,
+ }
+ return cf.find()
+}