Skip to content
This repository was archived by the owner on Mar 27, 2024. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea
bin/
mocks/springboot2/.gradle/
/godplugin
215 changes: 198 additions & 17 deletions modules/dnsmasq_dhcp/autodetection.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,42 @@ import (
"io/ioutil"
"net"
"os"
"path"
"path/filepath"
"regexp"
"strings"
)

// configDir represents conf-dir directive
//
// #conf-dir=/etc/dnsmasq.d
//
// # 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
type configDir struct {
path string
includeSuffix []string
excludeSuffix []string
}

func (d *DnsmasqDHCP) findDHCPRanges() ([]string, error) {
cs, err := findConfigurations(d.ConfDir)
if err != nil {
d.Warningf("error during configuration dir scanning : %v", err)
}
configs := d.findConfigs(d.ConfPath)

configs = append([]string{d.ConfPath}, configs...)

configs = unique(configs)

configs := []string{d.ConfPath}
configs = append(configs, cs...)
d.Infof("configuration files to read : %v", configs)
d.Infof("configuration files to read: %v", configs)

seen := make(map[string]bool)
var ranges []string

for _, config := range configs {
d.Debugf("reading %s", config)
rs, err := findDHCPRanges(config)
if err != nil {
if err != nil && !os.IsNotExist(err) {
return nil, err
}

Expand All @@ -46,21 +60,96 @@ func (d *DnsmasqDHCP) findDHCPRanges() ([]string, error) {
return ranges, nil
}

func findConfigurations(confDir string) ([]string, error) {
fis, err := ioutil.ReadDir(confDir)
// findConfigs recursively finds and reads configuration files respecting
// conf-file and conf-dir directives.
// findConfigs is tolerant to IO errors and finds as maximum config
// files as possible, therefore if an error occurrs during scanning process,
// it will be just logged with warning severity
func (d *DnsmasqDHCP) findConfigs(confPath string) []string {
Copy link
Copy Markdown
Member

@ilyam8 ilyam8 Jul 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

findConfigs returns configs, right? but it doesnt include entry config (confPath string). Looks a little bit lame to me, not sure. What do you think?

having it this way we need to

	configs := d.findConfigs(d.ConfPath)
	configs = append([]string{d.ConfPath}, configs...)

^^ looks like a workaround or smth

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

@ilyam8 ilyam8 Jul 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i know, i know.

it was

findConfigurations(confDir string) ([]string, error)

which is ok (but not good ofc), if finds configs in dir.

we can fix it later.


One more thing i noticed, your implementation ignores ConfDir.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, maybe we need to get rid of ConfigDir

All we need is the entry config

CONFIG FILE
       At startup, dnsmasq reads /etc/dnsmasq.conf, if it exists. (On FreeBSD, the file is /usr/local/etc/dnsmasq.conf ) (but see the -C and -7 options.) The format of this file consists of one option per line, exactly as the long options detailed in the OPTIONS section but without the
       leading "--". Lines starting with # are comments and ignored. For options which may only be specified once, the configuration file overrides the command line.  Quoting is allowed in a config file: between " quotes the special meanings of ,:. and # are removed and  the  following
       escapes are allowed: \\ \" \t \e \b \r and \n. The later corresponding to tab, escape, backspace, return and newline.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On debian it reads /etc/default/dnsmasq

from

/etc/init.d/dnsmasq

NAME=dnsmasq
DESC="DNS forwarder and DHCP server"

# Most configuration options in /etc/default/dnsmasq are deprecated
# but still honoured.
ENABLED=1
if [ -r /etc/default/$NAME ]; then
        . /etc/default/$NAME
fi

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, it seems /etc/default/dnsmasq doesnt work, i found in README in dnsmasq.d

# All files in this directory will be read by dnsmasq as 
# configuration files, except if their names end in 
# ".dpkg-dist",".dpkg-old" or ".dpkg-new"
#
# This can be changed by editing /etc/default/dnsmasq

but, as i said it doesnt work, so we can ignore it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i finally read man dnsmasq lmao

       -C, --conf-file=<file>
              Specify a different configuration file. The conf-file option is also allowed in configuration files, to include multiple configuration files. A filename of "-" causes dnsmasq to read configuration from stdin.

       -7, --conf-dir=<directory>[,<file-extension>......],
              Read all the files in the given directory as configuration files. If extension(s) are given, any files which end in those extensions are skipped. Any files whose names end in ~ or start with . or start and end with # are always skipped. If the extension starts with * then
              only files which have that extension are loaded. So --conf-dir=/path/to/dir,*.conf loads all files with the suffix .conf in /path/to/dir. This flag may be given on the command line or in a configuration file. If giving it on the command line, be sure to escape  *  charac‐
              ters.

it looks like we need both config_path and config_dir since both of them can be specified via command line.

config, err := os.Open(confPath)
if err != nil {
return nil, err
d.Warningf("error during configuration file %q reading: %v", confPath, err)
return nil
}

var configs []string
for _, fi := range fis {
if !fi.Mode().IsRegular() || !strings.HasSuffix(fi.Name(), ".conf") {
defer config.Close()

var (
includeFiles []string
includeDirs []configDir
)

scanner := bufio.NewScanner(config)
for scanner.Scan() {
line := scanner.Text()

if path, ok := getConfValue(line, "conf-file"); ok {
includeFiles = append(includeFiles, path)
continue
}

if path, ok := getConfValue(line, "conf-dir"); ok {
args := strings.Split(path, ",")

dir := configDir{
path: args[0],
}

for _, arg := range args[1:] {
arg := strings.TrimSpace(arg)
// dnsmasq treats suffixes with asterisk as "to include" and without
// asterisk as "to exclude"
if strings.HasPrefix(arg, "*") {
dir.includeSuffix = append(dir.includeSuffix, arg[1:])
} else {
dir.excludeSuffix = append(dir.excludeSuffix, arg)
}
}

includeDirs = append(includeDirs, dir)

continue
}
configs = append(configs, path.Join(confDir, fi.Name()))
}

return configs, nil
for _, dir := range includeDirs {
dirFiles, err := dir.findConfigs()
if err != nil {
d.Warningf("error during configuration dir %q scanning: %v", dir.path, err)
}

includeFiles = append(includeFiles, dirFiles...)
}

for _, file := range includeFiles {
files := d.findConfigs(file)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we need to avoid infinite recursion here (it is not infinite, i know)

that is what i get, fatal error doesnt look good

[ DEBUG ] dnsmasq_dhcp[dnsmasq_dhcp] autodetection.go:126 664436 opening/etc/dnsmasq.conf
[ DEBUG ] dnsmasq_dhcp[dnsmasq_dhcp] autodetection.go:126 664437 opening/etc/dnsmasq.conf
[ DEBUG ] dnsmasq_dhcp[dnsmasq_dhcp] autodetection.go:126 664438 opening/etc/dnsmasq.conf
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow


includeFiles = append(includeFiles, files...)
}

return includeFiles
}

func getConfValue(line, prefix string) (value string, ok bool) {
if !strings.HasPrefix(line, prefix) {
return "", false
}

value = strings.TrimPrefix(line, prefix)

value = strings.TrimSpace(value)

if !strings.HasPrefix(value, "=") {
// got some unexpected line, has prefix like conf-file but there is no
// assign sign
return "", false
}

value = strings.TrimPrefix(value, "=")

value = strings.TrimSpace(value)

return value, true
}

func findDHCPRanges(filePath string) ([]string, error) {
Expand Down Expand Up @@ -128,3 +217,95 @@ func parseDHCPRangeLine(s string) (r string) {

return fmt.Sprintf("%s-%s", start, end)
}

func (dir configDir) findConfigs() ([]string, error) {
fis, err := ioutil.ReadDir(dir.path)
if err != nil {
return nil, err
}

var configs []string

for _, fi := range fis {
if !fi.Mode().IsRegular() {
continue
}

name := fi.Name()
if !dir.isValidFileName(name) {
continue
}

if !dir.matchFileName(name) {
continue
}

configs = append(configs, filepath.Join(dir.path, fi.Name()))
}

return configs, nil
}

func (dir configDir) isValidFileName(name string) bool {
// We copy the dnsmasq's logic
//
// /* ignore emacs backups and dotfiles */
// if (len == 0 ||
// ent->d_name[len - 1] == '~' ||
// (ent->d_name[0] == '#' && ent->d_name[len - 1] == '#') ||
// ent->d_name[0] == '.')
// continue;

if strings.HasSuffix(name, "~") ||
(strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#")) ||
strings.HasPrefix(name, ".") {
return false
}

return true
}

func (dir configDir) matchFileName(name string) bool {
if len(dir.includeSuffix) > 0 {
including := false
for _, suffix := range dir.includeSuffix {
if strings.HasSuffix(name, suffix) {
including = true
break
}
}

if !including {
return false
}
}

for _, suffix := range dir.excludeSuffix {
if strings.HasSuffix(name, suffix) {
return false
}
}

return true
}

func unique(slice []string) []string {
result := []string{}

for _, item := range slice {
if !contains(result, item) {
result = append(result, item)
}
}
return result
}

func contains(slice []string, target string) bool {
for _, item := range slice {
if item == target {
return true
}
}

return false
}
64 changes: 64 additions & 0 deletions modules/dnsmasq_dhcp/dhcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ func TestDnsmasqDHCP_Collect(t *testing.T) {
"1230::-1230::9_percentage": 60,
"1231::-1231::9": 0,
"1231::-1231::9_percentage": 0,
"172.168.0.0-172.168.0.9": 0,
"172.168.0.0-172.168.0.9_percentage": 0,
"192.168.0.0-192.168.0.9": 5,
"192.168.0.0-192.168.0.9_percentage": 50,
"192.168.1.0-192.168.1.9": 4,
Expand All @@ -101,3 +103,65 @@ func TestDnsmasqDHCP_Collect(t *testing.T) {

assert.Equal(t, expected, job.Collect())
}

func TestConfigDir_findConfigs(t *testing.T) {
testcases := []struct {
config configDir
expected []string
}{
0: {
configDir{
path: "testdata",
includeSuffix: nil,
excludeSuffix: nil,
},
[]string{
"testdata/dnsmasq.conf",
"testdata/dnsmasq.leases",
"testdata/dnsmasq.more.conf",
},
},

1: {
configDir{
path: "testdata",
includeSuffix: []string{".leases"},
excludeSuffix: nil,
},
[]string{
"testdata/dnsmasq.leases",
},
},

2: {
configDir{
path: "testdata",
includeSuffix: nil,
excludeSuffix: []string{".leases"},
},
[]string{
"testdata/dnsmasq.conf",
"testdata/dnsmasq.more.conf",
},
},

3: {
// weird one, but possible
configDir{
path: "testdata",
includeSuffix: []string{".conf", ".leases"},
excludeSuffix: []string{".conf"},
},
[]string{
"testdata/dnsmasq.leases",
},
},
}

for i, testcase := range testcases {
actual, err := testcase.config.findConfigs()
assert.NoError(t, err, "testcase: %d", i)

assert.Equal(t, testcase.expected, actual, "testcase: %d", i)
}
}
Empty file.
Empty file.
2 changes: 2 additions & 0 deletions modules/dnsmasq_dhcp/testdata/dnsmasq.conf
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,5 @@ dhcp-range = 1235::2, 1235::500, ra-stateless
dhcp-range=1234::, ra-stateless, ra-names
dhcp-range=1234::, ra-stateless
dhcp-range=1234::, ra-only, 48h

conf-dir=testdata/dnsmasq.d,*.conf,.bak
1 change: 1 addition & 0 deletions modules/dnsmasq_dhcp/testdata/dnsmasq.d/dnsmasqv4.conf
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
dhcp-range=192.168.2.0,192.168.2.9,12h
conf-file=testdata/dnsmasq.more.conf
2 changes: 2 additions & 0 deletions modules/dnsmasq_dhcp/testdata/dnsmasq.more.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dhcp-range= 172.168.0.0,172.168.0.9,12h
dhcp-range=tag:green,192.168.1.0,192.168.1.9,12h
Empty file.