Browse code

Merge pull request #11636 from robertabbott/refactor_networkfs

Refactor pkg/networkfs

Jessie Frazelle authored on 2015/03/24 08:58:29
Showing 13 changed files
... ...
@@ -37,11 +37,11 @@ import (
37 37
 	"github.com/docker/docker/pkg/fileutils"
38 38
 	"github.com/docker/docker/pkg/homedir"
39 39
 	flag "github.com/docker/docker/pkg/mflag"
40
-	"github.com/docker/docker/pkg/networkfs/resolvconf"
41 40
 	"github.com/docker/docker/pkg/parsers"
42 41
 	"github.com/docker/docker/pkg/parsers/filters"
43 42
 	"github.com/docker/docker/pkg/progressreader"
44 43
 	"github.com/docker/docker/pkg/promise"
44
+	"github.com/docker/docker/pkg/resolvconf"
45 45
 	"github.com/docker/docker/pkg/signal"
46 46
 	"github.com/docker/docker/pkg/symlink"
47 47
 	"github.com/docker/docker/pkg/term"
... ...
@@ -32,10 +32,10 @@ import (
32 32
 	"github.com/docker/docker/pkg/broadcastwriter"
33 33
 	"github.com/docker/docker/pkg/common"
34 34
 	"github.com/docker/docker/pkg/directory"
35
+	"github.com/docker/docker/pkg/etchosts"
35 36
 	"github.com/docker/docker/pkg/ioutils"
36
-	"github.com/docker/docker/pkg/networkfs/etchosts"
37
-	"github.com/docker/docker/pkg/networkfs/resolvconf"
38 37
 	"github.com/docker/docker/pkg/promise"
38
+	"github.com/docker/docker/pkg/resolvconf"
39 39
 	"github.com/docker/docker/pkg/symlink"
40 40
 	"github.com/docker/docker/pkg/ulimit"
41 41
 	"github.com/docker/docker/runconfig"
... ...
@@ -35,9 +35,9 @@ import (
35 35
 	"github.com/docker/docker/pkg/graphdb"
36 36
 	"github.com/docker/docker/pkg/ioutils"
37 37
 	"github.com/docker/docker/pkg/namesgenerator"
38
-	"github.com/docker/docker/pkg/networkfs/resolvconf"
39 38
 	"github.com/docker/docker/pkg/parsers"
40 39
 	"github.com/docker/docker/pkg/parsers/kernel"
40
+	"github.com/docker/docker/pkg/resolvconf"
41 41
 	"github.com/docker/docker/pkg/sysinfo"
42 42
 	"github.com/docker/docker/pkg/truncindex"
43 43
 	"github.com/docker/docker/runconfig"
... ...
@@ -17,8 +17,8 @@ import (
17 17
 	"github.com/docker/docker/engine"
18 18
 	"github.com/docker/docker/nat"
19 19
 	"github.com/docker/docker/pkg/iptables"
20
-	"github.com/docker/docker/pkg/networkfs/resolvconf"
21 20
 	"github.com/docker/docker/pkg/parsers/kernel"
21
+	"github.com/docker/docker/pkg/resolvconf"
22 22
 	"github.com/docker/libcontainer/netlink"
23 23
 )
24 24
 
... ...
@@ -20,7 +20,7 @@ import (
20 20
 	"time"
21 21
 
22 22
 	"github.com/docker/docker/nat"
23
-	"github.com/docker/docker/pkg/networkfs/resolvconf"
23
+	"github.com/docker/docker/pkg/resolvconf"
24 24
 )
25 25
 
26 26
 // "test123" should be printed by docker run
27 27
new file mode 100644
... ...
@@ -0,0 +1,67 @@
0
+package etchosts
1
+
2
+import (
3
+	"bytes"
4
+	"fmt"
5
+	"io"
6
+	"io/ioutil"
7
+	"regexp"
8
+)
9
+
10
+type Record struct {
11
+	Hosts string
12
+	IP    string
13
+}
14
+
15
+func (r Record) WriteTo(w io.Writer) (int64, error) {
16
+	n, err := fmt.Fprintf(w, "%s\t%s\n", r.IP, r.Hosts)
17
+	return int64(n), err
18
+}
19
+
20
+var defaultContent = []Record{
21
+	{Hosts: "localhost", IP: "127.0.0.1"},
22
+	{Hosts: "localhost ip6-localhost ip6-loopback", IP: "::1"},
23
+	{Hosts: "ip6-localnet", IP: "fe00::0"},
24
+	{Hosts: "ip6-mcastprefix", IP: "ff00::0"},
25
+	{Hosts: "ip6-allnodes", IP: "ff02::1"},
26
+	{Hosts: "ip6-allrouters", IP: "ff02::2"},
27
+}
28
+
29
+func Build(path, IP, hostname, domainname string, extraContent []Record) error {
30
+	content := bytes.NewBuffer(nil)
31
+	if IP != "" {
32
+		var mainRec Record
33
+		mainRec.IP = IP
34
+		if domainname != "" {
35
+			mainRec.Hosts = fmt.Sprintf("%s.%s %s", hostname, domainname, hostname)
36
+		} else {
37
+			mainRec.Hosts = hostname
38
+		}
39
+		if _, err := mainRec.WriteTo(content); err != nil {
40
+			return err
41
+		}
42
+	}
43
+
44
+	for _, r := range defaultContent {
45
+		if _, err := r.WriteTo(content); err != nil {
46
+			return err
47
+		}
48
+	}
49
+
50
+	for _, r := range extraContent {
51
+		if _, err := r.WriteTo(content); err != nil {
52
+			return err
53
+		}
54
+	}
55
+
56
+	return ioutil.WriteFile(path, content.Bytes(), 0644)
57
+}
58
+
59
+func Update(path, IP, hostname string) error {
60
+	old, err := ioutil.ReadFile(path)
61
+	if err != nil {
62
+		return err
63
+	}
64
+	var re = regexp.MustCompile(fmt.Sprintf("(\\S*)(\\t%s)", regexp.QuoteMeta(hostname)))
65
+	return ioutil.WriteFile(path, re.ReplaceAll(old, []byte(IP+"$2")), 0644)
66
+}
0 67
new file mode 100644
... ...
@@ -0,0 +1,134 @@
0
+package etchosts
1
+
2
+import (
3
+	"bytes"
4
+	"io/ioutil"
5
+	"os"
6
+	"testing"
7
+)
8
+
9
+func TestBuildDefault(t *testing.T) {
10
+	file, err := ioutil.TempFile("", "")
11
+	if err != nil {
12
+		t.Fatal(err)
13
+	}
14
+	defer os.Remove(file.Name())
15
+
16
+	// check that /etc/hosts has consistent ordering
17
+	for i := 0; i <= 5; i++ {
18
+		err = Build(file.Name(), "", "", "", nil)
19
+		if err != nil {
20
+			t.Fatal(err)
21
+		}
22
+
23
+		content, err := ioutil.ReadFile(file.Name())
24
+		if err != nil {
25
+			t.Fatal(err)
26
+		}
27
+		expected := "127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\nfe00::0\tip6-localnet\nff00::0\tip6-mcastprefix\nff02::1\tip6-allnodes\nff02::2\tip6-allrouters\n"
28
+
29
+		if expected != string(content) {
30
+			t.Fatalf("Expected to find '%s' got '%s'", expected, content)
31
+		}
32
+	}
33
+}
34
+
35
+func TestBuildHostnameDomainname(t *testing.T) {
36
+	file, err := ioutil.TempFile("", "")
37
+	if err != nil {
38
+		t.Fatal(err)
39
+	}
40
+	defer os.Remove(file.Name())
41
+
42
+	err = Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil)
43
+	if err != nil {
44
+		t.Fatal(err)
45
+	}
46
+
47
+	content, err := ioutil.ReadFile(file.Name())
48
+	if err != nil {
49
+		t.Fatal(err)
50
+	}
51
+
52
+	if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
53
+		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
54
+	}
55
+}
56
+
57
+func TestBuildHostname(t *testing.T) {
58
+	file, err := ioutil.TempFile("", "")
59
+	if err != nil {
60
+		t.Fatal(err)
61
+	}
62
+	defer os.Remove(file.Name())
63
+
64
+	err = Build(file.Name(), "10.11.12.13", "testhostname", "", nil)
65
+	if err != nil {
66
+		t.Fatal(err)
67
+	}
68
+
69
+	content, err := ioutil.ReadFile(file.Name())
70
+	if err != nil {
71
+		t.Fatal(err)
72
+	}
73
+
74
+	if expected := "10.11.12.13\ttesthostname\n"; !bytes.Contains(content, []byte(expected)) {
75
+		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
76
+	}
77
+}
78
+
79
+func TestBuildNoIP(t *testing.T) {
80
+	file, err := ioutil.TempFile("", "")
81
+	if err != nil {
82
+		t.Fatal(err)
83
+	}
84
+	defer os.Remove(file.Name())
85
+
86
+	err = Build(file.Name(), "", "testhostname", "", nil)
87
+	if err != nil {
88
+		t.Fatal(err)
89
+	}
90
+
91
+	content, err := ioutil.ReadFile(file.Name())
92
+	if err != nil {
93
+		t.Fatal(err)
94
+	}
95
+
96
+	if expected := ""; !bytes.Contains(content, []byte(expected)) {
97
+		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
98
+	}
99
+}
100
+
101
+func TestUpdate(t *testing.T) {
102
+	file, err := ioutil.TempFile("", "")
103
+	if err != nil {
104
+		t.Fatal(err)
105
+	}
106
+	defer os.Remove(file.Name())
107
+
108
+	if err := Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil); err != nil {
109
+		t.Fatal(err)
110
+	}
111
+
112
+	content, err := ioutil.ReadFile(file.Name())
113
+	if err != nil {
114
+		t.Fatal(err)
115
+	}
116
+
117
+	if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
118
+		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
119
+	}
120
+
121
+	if err := Update(file.Name(), "1.1.1.1", "testhostname"); err != nil {
122
+		t.Fatal(err)
123
+	}
124
+
125
+	content, err = ioutil.ReadFile(file.Name())
126
+	if err != nil {
127
+		t.Fatal(err)
128
+	}
129
+
130
+	if expected := "1.1.1.1\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
131
+		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
132
+	}
133
+}
0 134
deleted file mode 100644
... ...
@@ -1,67 +0,0 @@
1
-package etchosts
2
-
3
-import (
4
-	"bytes"
5
-	"fmt"
6
-	"io"
7
-	"io/ioutil"
8
-	"regexp"
9
-)
10
-
11
-type Record struct {
12
-	Hosts string
13
-	IP    string
14
-}
15
-
16
-func (r Record) WriteTo(w io.Writer) (int64, error) {
17
-	n, err := fmt.Fprintf(w, "%s\t%s\n", r.IP, r.Hosts)
18
-	return int64(n), err
19
-}
20
-
21
-var defaultContent = []Record{
22
-	{Hosts: "localhost", IP: "127.0.0.1"},
23
-	{Hosts: "localhost ip6-localhost ip6-loopback", IP: "::1"},
24
-	{Hosts: "ip6-localnet", IP: "fe00::0"},
25
-	{Hosts: "ip6-mcastprefix", IP: "ff00::0"},
26
-	{Hosts: "ip6-allnodes", IP: "ff02::1"},
27
-	{Hosts: "ip6-allrouters", IP: "ff02::2"},
28
-}
29
-
30
-func Build(path, IP, hostname, domainname string, extraContent []Record) error {
31
-	content := bytes.NewBuffer(nil)
32
-	if IP != "" {
33
-		var mainRec Record
34
-		mainRec.IP = IP
35
-		if domainname != "" {
36
-			mainRec.Hosts = fmt.Sprintf("%s.%s %s", hostname, domainname, hostname)
37
-		} else {
38
-			mainRec.Hosts = hostname
39
-		}
40
-		if _, err := mainRec.WriteTo(content); err != nil {
41
-			return err
42
-		}
43
-	}
44
-
45
-	for _, r := range defaultContent {
46
-		if _, err := r.WriteTo(content); err != nil {
47
-			return err
48
-		}
49
-	}
50
-
51
-	for _, r := range extraContent {
52
-		if _, err := r.WriteTo(content); err != nil {
53
-			return err
54
-		}
55
-	}
56
-
57
-	return ioutil.WriteFile(path, content.Bytes(), 0644)
58
-}
59
-
60
-func Update(path, IP, hostname string) error {
61
-	old, err := ioutil.ReadFile(path)
62
-	if err != nil {
63
-		return err
64
-	}
65
-	var re = regexp.MustCompile(fmt.Sprintf("(\\S*)(\\t%s)", regexp.QuoteMeta(hostname)))
66
-	return ioutil.WriteFile(path, re.ReplaceAll(old, []byte(IP+"$2")), 0644)
67
-}
68 1
deleted file mode 100644
... ...
@@ -1,134 +0,0 @@
1
-package etchosts
2
-
3
-import (
4
-	"bytes"
5
-	"io/ioutil"
6
-	"os"
7
-	"testing"
8
-)
9
-
10
-func TestBuildDefault(t *testing.T) {
11
-	file, err := ioutil.TempFile("", "")
12
-	if err != nil {
13
-		t.Fatal(err)
14
-	}
15
-	defer os.Remove(file.Name())
16
-
17
-	// check that /etc/hosts has consistent ordering
18
-	for i := 0; i <= 5; i++ {
19
-		err = Build(file.Name(), "", "", "", nil)
20
-		if err != nil {
21
-			t.Fatal(err)
22
-		}
23
-
24
-		content, err := ioutil.ReadFile(file.Name())
25
-		if err != nil {
26
-			t.Fatal(err)
27
-		}
28
-		expected := "127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\nfe00::0\tip6-localnet\nff00::0\tip6-mcastprefix\nff02::1\tip6-allnodes\nff02::2\tip6-allrouters\n"
29
-
30
-		if expected != string(content) {
31
-			t.Fatalf("Expected to find '%s' got '%s'", expected, content)
32
-		}
33
-	}
34
-}
35
-
36
-func TestBuildHostnameDomainname(t *testing.T) {
37
-	file, err := ioutil.TempFile("", "")
38
-	if err != nil {
39
-		t.Fatal(err)
40
-	}
41
-	defer os.Remove(file.Name())
42
-
43
-	err = Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil)
44
-	if err != nil {
45
-		t.Fatal(err)
46
-	}
47
-
48
-	content, err := ioutil.ReadFile(file.Name())
49
-	if err != nil {
50
-		t.Fatal(err)
51
-	}
52
-
53
-	if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
54
-		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
55
-	}
56
-}
57
-
58
-func TestBuildHostname(t *testing.T) {
59
-	file, err := ioutil.TempFile("", "")
60
-	if err != nil {
61
-		t.Fatal(err)
62
-	}
63
-	defer os.Remove(file.Name())
64
-
65
-	err = Build(file.Name(), "10.11.12.13", "testhostname", "", nil)
66
-	if err != nil {
67
-		t.Fatal(err)
68
-	}
69
-
70
-	content, err := ioutil.ReadFile(file.Name())
71
-	if err != nil {
72
-		t.Fatal(err)
73
-	}
74
-
75
-	if expected := "10.11.12.13\ttesthostname\n"; !bytes.Contains(content, []byte(expected)) {
76
-		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
77
-	}
78
-}
79
-
80
-func TestBuildNoIP(t *testing.T) {
81
-	file, err := ioutil.TempFile("", "")
82
-	if err != nil {
83
-		t.Fatal(err)
84
-	}
85
-	defer os.Remove(file.Name())
86
-
87
-	err = Build(file.Name(), "", "testhostname", "", nil)
88
-	if err != nil {
89
-		t.Fatal(err)
90
-	}
91
-
92
-	content, err := ioutil.ReadFile(file.Name())
93
-	if err != nil {
94
-		t.Fatal(err)
95
-	}
96
-
97
-	if expected := ""; !bytes.Contains(content, []byte(expected)) {
98
-		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
99
-	}
100
-}
101
-
102
-func TestUpdate(t *testing.T) {
103
-	file, err := ioutil.TempFile("", "")
104
-	if err != nil {
105
-		t.Fatal(err)
106
-	}
107
-	defer os.Remove(file.Name())
108
-
109
-	if err := Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil); err != nil {
110
-		t.Fatal(err)
111
-	}
112
-
113
-	content, err := ioutil.ReadFile(file.Name())
114
-	if err != nil {
115
-		t.Fatal(err)
116
-	}
117
-
118
-	if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
119
-		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
120
-	}
121
-
122
-	if err := Update(file.Name(), "1.1.1.1", "testhostname"); err != nil {
123
-		t.Fatal(err)
124
-	}
125
-
126
-	content, err = ioutil.ReadFile(file.Name())
127
-	if err != nil {
128
-		t.Fatal(err)
129
-	}
130
-
131
-	if expected := "1.1.1.1\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
132
-		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
133
-	}
134
-}
135 1
deleted file mode 100644
... ...
@@ -1,190 +0,0 @@
1
-package resolvconf
2
-
3
-import (
4
-	"bytes"
5
-	"io/ioutil"
6
-	"regexp"
7
-	"strings"
8
-	"sync"
9
-
10
-	log "github.com/Sirupsen/logrus"
11
-	"github.com/docker/docker/utils"
12
-)
13
-
14
-var (
15
-	// Note: the default IPv4 & IPv6 resolvers are set to Google's Public DNS
16
-	defaultIPv4Dns = []string{"nameserver 8.8.8.8", "nameserver 8.8.4.4"}
17
-	defaultIPv6Dns = []string{"nameserver 2001:4860:4860::8888", "nameserver 2001:4860:4860::8844"}
18
-	ipv4NumBlock   = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`
19
-	ipv4Address    = `(` + ipv4NumBlock + `\.){3}` + ipv4NumBlock
20
-	// This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also
21
-	// will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants
22
-	// -- e.g. other link-local types -- either won't work in containers or are unnecessary.
23
-	// For readability and sufficiency for Docker purposes this seemed more reasonable than a
24
-	// 1000+ character regexp with exact and complete IPv6 validation
25
-	ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})`
26
-	ipLocalhost = `((127\.([0-9]{1,3}.){2}[0-9]{1,3})|(::1))`
27
-
28
-	localhostIPRegexp = regexp.MustCompile(ipLocalhost)
29
-	localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipLocalhost + `\s*\n*`)
30
-	nsIPv6Regexp      = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`)
31
-	nsRegexp          = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
32
-	searchRegexp      = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
33
-)
34
-
35
-var lastModified struct {
36
-	sync.Mutex
37
-	sha256   string
38
-	contents []byte
39
-}
40
-
41
-func Get() ([]byte, error) {
42
-	resolv, err := ioutil.ReadFile("/etc/resolv.conf")
43
-	if err != nil {
44
-		return nil, err
45
-	}
46
-	return resolv, nil
47
-}
48
-
49
-// Retrieves the host /etc/resolv.conf file, checks against the last hash
50
-// and, if modified since last check, returns the bytes and new hash.
51
-// This feature is used by the resolv.conf updater for containers
52
-func GetIfChanged() ([]byte, string, error) {
53
-	lastModified.Lock()
54
-	defer lastModified.Unlock()
55
-
56
-	resolv, err := ioutil.ReadFile("/etc/resolv.conf")
57
-	if err != nil {
58
-		return nil, "", err
59
-	}
60
-	newHash, err := utils.HashData(bytes.NewReader(resolv))
61
-	if err != nil {
62
-		return nil, "", err
63
-	}
64
-	if lastModified.sha256 != newHash {
65
-		lastModified.sha256 = newHash
66
-		lastModified.contents = resolv
67
-		return resolv, newHash, nil
68
-	}
69
-	// nothing changed, so return no data
70
-	return nil, "", nil
71
-}
72
-
73
-// retrieve the last used contents and hash of the host resolv.conf
74
-// Used by containers updating on restart
75
-func GetLastModified() ([]byte, string) {
76
-	lastModified.Lock()
77
-	defer lastModified.Unlock()
78
-
79
-	return lastModified.contents, lastModified.sha256
80
-}
81
-
82
-// FilterResolvDns has two main jobs:
83
-// 1. It looks for localhost (127.*|::1) entries in the provided
84
-//    resolv.conf, removing local nameserver entries, and, if the resulting
85
-//    cleaned config has no defined nameservers left, adds default DNS entries
86
-// 2. Given the caller provides the enable/disable state of IPv6, the filter
87
-//    code will remove all IPv6 nameservers if it is not enabled for containers
88
-//
89
-// It also returns a boolean to notify the caller if changes were made at all
90
-func FilterResolvDns(resolvConf []byte, ipv6Enabled bool) ([]byte, bool) {
91
-	changed := false
92
-	cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{})
93
-	// if IPv6 is not enabled, also clean out any IPv6 address nameserver
94
-	if !ipv6Enabled {
95
-		cleanedResolvConf = nsIPv6Regexp.ReplaceAll(cleanedResolvConf, []byte{})
96
-	}
97
-	// if the resulting resolvConf has no more nameservers defined, add appropriate
98
-	// default DNS servers for IPv4 and (optionally) IPv6
99
-	if len(GetNameservers(cleanedResolvConf)) == 0 {
100
-		log.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers : %v", defaultIPv4Dns)
101
-		dns := defaultIPv4Dns
102
-		if ipv6Enabled {
103
-			log.Infof("IPv6 enabled; Adding default IPv6 external servers : %v", defaultIPv6Dns)
104
-			dns = append(dns, defaultIPv6Dns...)
105
-		}
106
-		cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...)
107
-	}
108
-	if !bytes.Equal(resolvConf, cleanedResolvConf) {
109
-		changed = true
110
-	}
111
-	return cleanedResolvConf, changed
112
-}
113
-
114
-// getLines parses input into lines and strips away comments.
115
-func getLines(input []byte, commentMarker []byte) [][]byte {
116
-	lines := bytes.Split(input, []byte("\n"))
117
-	var output [][]byte
118
-	for _, currentLine := range lines {
119
-		var commentIndex = bytes.Index(currentLine, commentMarker)
120
-		if commentIndex == -1 {
121
-			output = append(output, currentLine)
122
-		} else {
123
-			output = append(output, currentLine[:commentIndex])
124
-		}
125
-	}
126
-	return output
127
-}
128
-
129
-// returns true if the IP string matches the localhost IP regular expression.
130
-// Used for determining if nameserver settings are being passed which are
131
-// localhost addresses
132
-func IsLocalhost(ip string) bool {
133
-	return localhostIPRegexp.MatchString(ip)
134
-}
135
-
136
-// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
137
-func GetNameservers(resolvConf []byte) []string {
138
-	nameservers := []string{}
139
-	for _, line := range getLines(resolvConf, []byte("#")) {
140
-		var ns = nsRegexp.FindSubmatch(line)
141
-		if len(ns) > 0 {
142
-			nameservers = append(nameservers, string(ns[1]))
143
-		}
144
-	}
145
-	return nameservers
146
-}
147
-
148
-// GetNameserversAsCIDR returns nameservers (if any) listed in
149
-// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32")
150
-// This function's output is intended for net.ParseCIDR
151
-func GetNameserversAsCIDR(resolvConf []byte) []string {
152
-	nameservers := []string{}
153
-	for _, nameserver := range GetNameservers(resolvConf) {
154
-		nameservers = append(nameservers, nameserver+"/32")
155
-	}
156
-	return nameservers
157
-}
158
-
159
-// GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf
160
-// If more than one search line is encountered, only the contents of the last
161
-// one is returned.
162
-func GetSearchDomains(resolvConf []byte) []string {
163
-	domains := []string{}
164
-	for _, line := range getLines(resolvConf, []byte("#")) {
165
-		match := searchRegexp.FindSubmatch(line)
166
-		if match == nil {
167
-			continue
168
-		}
169
-		domains = strings.Fields(string(match[1]))
170
-	}
171
-	return domains
172
-}
173
-
174
-func Build(path string, dns, dnsSearch []string) error {
175
-	content := bytes.NewBuffer(nil)
176
-	for _, dns := range dns {
177
-		if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil {
178
-			return err
179
-		}
180
-	}
181
-	if len(dnsSearch) > 0 {
182
-		if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." {
183
-			if _, err := content.WriteString("search " + searchString + "\n"); err != nil {
184
-				return err
185
-			}
186
-		}
187
-	}
188
-
189
-	return ioutil.WriteFile(path, content.Bytes(), 0644)
190
-}
191 1
deleted file mode 100644
... ...
@@ -1,238 +0,0 @@
1
-package resolvconf
2
-
3
-import (
4
-	"bytes"
5
-	"io/ioutil"
6
-	"os"
7
-	"testing"
8
-)
9
-
10
-func TestGet(t *testing.T) {
11
-	resolvConfUtils, err := Get()
12
-	if err != nil {
13
-		t.Fatal(err)
14
-	}
15
-	resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
16
-	if err != nil {
17
-		t.Fatal(err)
18
-	}
19
-	if string(resolvConfUtils) != string(resolvConfSystem) {
20
-		t.Fatalf("/etc/resolv.conf and GetResolvConf have different content.")
21
-	}
22
-}
23
-
24
-func TestGetNameservers(t *testing.T) {
25
-	for resolv, result := range map[string][]string{`
26
-nameserver 1.2.3.4
27
-nameserver 40.3.200.10
28
-search example.com`: {"1.2.3.4", "40.3.200.10"},
29
-		`search example.com`: {},
30
-		`nameserver 1.2.3.4
31
-search example.com
32
-nameserver 4.30.20.100`: {"1.2.3.4", "4.30.20.100"},
33
-		``: {},
34
-		`  nameserver 1.2.3.4   `: {"1.2.3.4"},
35
-		`search example.com
36
-nameserver 1.2.3.4
37
-#nameserver 4.3.2.1`: {"1.2.3.4"},
38
-		`search example.com
39
-nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4"},
40
-	} {
41
-		test := GetNameservers([]byte(resolv))
42
-		if !strSlicesEqual(test, result) {
43
-			t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv)
44
-		}
45
-	}
46
-}
47
-
48
-func TestGetNameserversAsCIDR(t *testing.T) {
49
-	for resolv, result := range map[string][]string{`
50
-nameserver 1.2.3.4
51
-nameserver 40.3.200.10
52
-search example.com`: {"1.2.3.4/32", "40.3.200.10/32"},
53
-		`search example.com`: {},
54
-		`nameserver 1.2.3.4
55
-search example.com
56
-nameserver 4.30.20.100`: {"1.2.3.4/32", "4.30.20.100/32"},
57
-		``: {},
58
-		`  nameserver 1.2.3.4   `: {"1.2.3.4/32"},
59
-		`search example.com
60
-nameserver 1.2.3.4
61
-#nameserver 4.3.2.1`: {"1.2.3.4/32"},
62
-		`search example.com
63
-nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4/32"},
64
-	} {
65
-		test := GetNameserversAsCIDR([]byte(resolv))
66
-		if !strSlicesEqual(test, result) {
67
-			t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv)
68
-		}
69
-	}
70
-}
71
-
72
-func TestGetSearchDomains(t *testing.T) {
73
-	for resolv, result := range map[string][]string{
74
-		`search example.com`:           {"example.com"},
75
-		`search example.com # ignored`: {"example.com"},
76
-		` 	  search 	 example.com 	  `: {"example.com"},
77
-		` 	  search 	 example.com 	  # ignored`: {"example.com"},
78
-		`search foo.example.com example.com`: {"foo.example.com", "example.com"},
79
-		`	   search   	   foo.example.com 	 example.com 	`: {"foo.example.com", "example.com"},
80
-		`	   search   	   foo.example.com 	 example.com 	# ignored`: {"foo.example.com", "example.com"},
81
-		``:          {},
82
-		`# ignored`: {},
83
-		`nameserver 1.2.3.4
84
-search foo.example.com example.com`: {"foo.example.com", "example.com"},
85
-		`nameserver 1.2.3.4
86
-search dup1.example.com dup2.example.com
87
-search foo.example.com example.com`: {"foo.example.com", "example.com"},
88
-		`nameserver 1.2.3.4
89
-search foo.example.com example.com
90
-nameserver 4.30.20.100`: {"foo.example.com", "example.com"},
91
-	} {
92
-		test := GetSearchDomains([]byte(resolv))
93
-		if !strSlicesEqual(test, result) {
94
-			t.Fatalf("Wrong search domain string {%s} should be %v. Input: %s", test, result, resolv)
95
-		}
96
-	}
97
-}
98
-
99
-func strSlicesEqual(a, b []string) bool {
100
-	if len(a) != len(b) {
101
-		return false
102
-	}
103
-
104
-	for i, v := range a {
105
-		if v != b[i] {
106
-			return false
107
-		}
108
-	}
109
-
110
-	return true
111
-}
112
-
113
-func TestBuild(t *testing.T) {
114
-	file, err := ioutil.TempFile("", "")
115
-	if err != nil {
116
-		t.Fatal(err)
117
-	}
118
-	defer os.Remove(file.Name())
119
-
120
-	err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"search1"})
121
-	if err != nil {
122
-		t.Fatal(err)
123
-	}
124
-
125
-	content, err := ioutil.ReadFile(file.Name())
126
-	if err != nil {
127
-		t.Fatal(err)
128
-	}
129
-
130
-	if expected := "nameserver ns1\nnameserver ns2\nnameserver ns3\nsearch search1\n"; !bytes.Contains(content, []byte(expected)) {
131
-		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
132
-	}
133
-}
134
-
135
-func TestBuildWithZeroLengthDomainSearch(t *testing.T) {
136
-	file, err := ioutil.TempFile("", "")
137
-	if err != nil {
138
-		t.Fatal(err)
139
-	}
140
-	defer os.Remove(file.Name())
141
-
142
-	err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"."})
143
-	if err != nil {
144
-		t.Fatal(err)
145
-	}
146
-
147
-	content, err := ioutil.ReadFile(file.Name())
148
-	if err != nil {
149
-		t.Fatal(err)
150
-	}
151
-
152
-	if expected := "nameserver ns1\nnameserver ns2\nnameserver ns3\n"; !bytes.Contains(content, []byte(expected)) {
153
-		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
154
-	}
155
-	if notExpected := "search ."; bytes.Contains(content, []byte(notExpected)) {
156
-		t.Fatalf("Expected to not find '%s' got '%s'", notExpected, content)
157
-	}
158
-}
159
-
160
-func TestFilterResolvDns(t *testing.T) {
161
-	ns0 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\n"
162
-
163
-	if result, _ := FilterResolvDns([]byte(ns0), false); result != nil {
164
-		if ns0 != string(result) {
165
-			t.Fatalf("Failed No Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
166
-		}
167
-	}
168
-
169
-	ns1 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\nnameserver 127.0.0.1\n"
170
-	if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
171
-		if ns0 != string(result) {
172
-			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
173
-		}
174
-	}
175
-
176
-	ns1 = "nameserver 10.16.60.14\nnameserver 127.0.0.1\nnameserver 10.16.60.21\n"
177
-	if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
178
-		if ns0 != string(result) {
179
-			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
180
-		}
181
-	}
182
-
183
-	ns1 = "nameserver 127.0.1.1\nnameserver 10.16.60.14\nnameserver 10.16.60.21\n"
184
-	if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
185
-		if ns0 != string(result) {
186
-			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
187
-		}
188
-	}
189
-
190
-	ns1 = "nameserver ::1\nnameserver 10.16.60.14\nnameserver 127.0.2.1\nnameserver 10.16.60.21\n"
191
-	if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
192
-		if ns0 != string(result) {
193
-			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
194
-		}
195
-	}
196
-
197
-	ns1 = "nameserver 10.16.60.14\nnameserver ::1\nnameserver 10.16.60.21\nnameserver ::1"
198
-	if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
199
-		if ns0 != string(result) {
200
-			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
201
-		}
202
-	}
203
-
204
-	// with IPv6 disabled (false param), the IPv6 nameserver should be removed
205
-	ns1 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\nnameserver ::1"
206
-	if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
207
-		if ns0 != string(result) {
208
-			t.Fatalf("Failed Localhost+IPv6 off: expected \n<%s> got \n<%s>", ns0, string(result))
209
-		}
210
-	}
211
-
212
-	// with IPv6 enabled, the IPv6 nameserver should be preserved
213
-	ns0 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\n"
214
-	ns1 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\nnameserver ::1"
215
-	if result, _ := FilterResolvDns([]byte(ns1), true); result != nil {
216
-		if ns0 != string(result) {
217
-			t.Fatalf("Failed Localhost+IPv6 on: expected \n<%s> got \n<%s>", ns0, string(result))
218
-		}
219
-	}
220
-
221
-	// with IPv6 enabled, and no non-localhost servers, Google defaults (both IPv4+IPv6) should be added
222
-	ns0 = "\nnameserver 8.8.8.8\nnameserver 8.8.4.4\nnameserver 2001:4860:4860::8888\nnameserver 2001:4860:4860::8844"
223
-	ns1 = "nameserver 127.0.0.1\nnameserver ::1\nnameserver 127.0.2.1"
224
-	if result, _ := FilterResolvDns([]byte(ns1), true); result != nil {
225
-		if ns0 != string(result) {
226
-			t.Fatalf("Failed no Localhost+IPv6 enabled: expected \n<%s> got \n<%s>", ns0, string(result))
227
-		}
228
-	}
229
-
230
-	// with IPv6 disabled, and no non-localhost servers, Google defaults (only IPv4) should be added
231
-	ns0 = "\nnameserver 8.8.8.8\nnameserver 8.8.4.4"
232
-	ns1 = "nameserver 127.0.0.1\nnameserver ::1\nnameserver 127.0.2.1"
233
-	if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
234
-		if ns0 != string(result) {
235
-			t.Fatalf("Failed no Localhost+IPv6 enabled: expected \n<%s> got \n<%s>", ns0, string(result))
236
-		}
237
-	}
238
-}
239 1
new file mode 100644
... ...
@@ -0,0 +1,190 @@
0
+package resolvconf
1
+
2
+import (
3
+	"bytes"
4
+	"io/ioutil"
5
+	"regexp"
6
+	"strings"
7
+	"sync"
8
+
9
+	log "github.com/Sirupsen/logrus"
10
+	"github.com/docker/docker/utils"
11
+)
12
+
13
+var (
14
+	// Note: the default IPv4 & IPv6 resolvers are set to Google's Public DNS
15
+	defaultIPv4Dns = []string{"nameserver 8.8.8.8", "nameserver 8.8.4.4"}
16
+	defaultIPv6Dns = []string{"nameserver 2001:4860:4860::8888", "nameserver 2001:4860:4860::8844"}
17
+	ipv4NumBlock   = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`
18
+	ipv4Address    = `(` + ipv4NumBlock + `\.){3}` + ipv4NumBlock
19
+	// This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also
20
+	// will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants
21
+	// -- e.g. other link-local types -- either won't work in containers or are unnecessary.
22
+	// For readability and sufficiency for Docker purposes this seemed more reasonable than a
23
+	// 1000+ character regexp with exact and complete IPv6 validation
24
+	ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})`
25
+	ipLocalhost = `((127\.([0-9]{1,3}.){2}[0-9]{1,3})|(::1))`
26
+
27
+	localhostIPRegexp = regexp.MustCompile(ipLocalhost)
28
+	localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipLocalhost + `\s*\n*`)
29
+	nsIPv6Regexp      = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`)
30
+	nsRegexp          = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
31
+	searchRegexp      = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
32
+)
33
+
34
+var lastModified struct {
35
+	sync.Mutex
36
+	sha256   string
37
+	contents []byte
38
+}
39
+
40
+func Get() ([]byte, error) {
41
+	resolv, err := ioutil.ReadFile("/etc/resolv.conf")
42
+	if err != nil {
43
+		return nil, err
44
+	}
45
+	return resolv, nil
46
+}
47
+
48
+// Retrieves the host /etc/resolv.conf file, checks against the last hash
49
+// and, if modified since last check, returns the bytes and new hash.
50
+// This feature is used by the resolv.conf updater for containers
51
+func GetIfChanged() ([]byte, string, error) {
52
+	lastModified.Lock()
53
+	defer lastModified.Unlock()
54
+
55
+	resolv, err := ioutil.ReadFile("/etc/resolv.conf")
56
+	if err != nil {
57
+		return nil, "", err
58
+	}
59
+	newHash, err := utils.HashData(bytes.NewReader(resolv))
60
+	if err != nil {
61
+		return nil, "", err
62
+	}
63
+	if lastModified.sha256 != newHash {
64
+		lastModified.sha256 = newHash
65
+		lastModified.contents = resolv
66
+		return resolv, newHash, nil
67
+	}
68
+	// nothing changed, so return no data
69
+	return nil, "", nil
70
+}
71
+
72
+// retrieve the last used contents and hash of the host resolv.conf
73
+// Used by containers updating on restart
74
+func GetLastModified() ([]byte, string) {
75
+	lastModified.Lock()
76
+	defer lastModified.Unlock()
77
+
78
+	return lastModified.contents, lastModified.sha256
79
+}
80
+
81
+// FilterResolvDns has two main jobs:
82
+// 1. It looks for localhost (127.*|::1) entries in the provided
83
+//    resolv.conf, removing local nameserver entries, and, if the resulting
84
+//    cleaned config has no defined nameservers left, adds default DNS entries
85
+// 2. Given the caller provides the enable/disable state of IPv6, the filter
86
+//    code will remove all IPv6 nameservers if it is not enabled for containers
87
+//
88
+// It also returns a boolean to notify the caller if changes were made at all
89
+func FilterResolvDns(resolvConf []byte, ipv6Enabled bool) ([]byte, bool) {
90
+	changed := false
91
+	cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{})
92
+	// if IPv6 is not enabled, also clean out any IPv6 address nameserver
93
+	if !ipv6Enabled {
94
+		cleanedResolvConf = nsIPv6Regexp.ReplaceAll(cleanedResolvConf, []byte{})
95
+	}
96
+	// if the resulting resolvConf has no more nameservers defined, add appropriate
97
+	// default DNS servers for IPv4 and (optionally) IPv6
98
+	if len(GetNameservers(cleanedResolvConf)) == 0 {
99
+		log.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers : %v", defaultIPv4Dns)
100
+		dns := defaultIPv4Dns
101
+		if ipv6Enabled {
102
+			log.Infof("IPv6 enabled; Adding default IPv6 external servers : %v", defaultIPv6Dns)
103
+			dns = append(dns, defaultIPv6Dns...)
104
+		}
105
+		cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...)
106
+	}
107
+	if !bytes.Equal(resolvConf, cleanedResolvConf) {
108
+		changed = true
109
+	}
110
+	return cleanedResolvConf, changed
111
+}
112
+
113
+// getLines parses input into lines and strips away comments.
114
+func getLines(input []byte, commentMarker []byte) [][]byte {
115
+	lines := bytes.Split(input, []byte("\n"))
116
+	var output [][]byte
117
+	for _, currentLine := range lines {
118
+		var commentIndex = bytes.Index(currentLine, commentMarker)
119
+		if commentIndex == -1 {
120
+			output = append(output, currentLine)
121
+		} else {
122
+			output = append(output, currentLine[:commentIndex])
123
+		}
124
+	}
125
+	return output
126
+}
127
+
128
+// returns true if the IP string matches the localhost IP regular expression.
129
+// Used for determining if nameserver settings are being passed which are
130
+// localhost addresses
131
+func IsLocalhost(ip string) bool {
132
+	return localhostIPRegexp.MatchString(ip)
133
+}
134
+
135
+// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
136
+func GetNameservers(resolvConf []byte) []string {
137
+	nameservers := []string{}
138
+	for _, line := range getLines(resolvConf, []byte("#")) {
139
+		var ns = nsRegexp.FindSubmatch(line)
140
+		if len(ns) > 0 {
141
+			nameservers = append(nameservers, string(ns[1]))
142
+		}
143
+	}
144
+	return nameservers
145
+}
146
+
147
+// GetNameserversAsCIDR returns nameservers (if any) listed in
148
+// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32")
149
+// This function's output is intended for net.ParseCIDR
150
+func GetNameserversAsCIDR(resolvConf []byte) []string {
151
+	nameservers := []string{}
152
+	for _, nameserver := range GetNameservers(resolvConf) {
153
+		nameservers = append(nameservers, nameserver+"/32")
154
+	}
155
+	return nameservers
156
+}
157
+
158
+// GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf
159
+// If more than one search line is encountered, only the contents of the last
160
+// one is returned.
161
+func GetSearchDomains(resolvConf []byte) []string {
162
+	domains := []string{}
163
+	for _, line := range getLines(resolvConf, []byte("#")) {
164
+		match := searchRegexp.FindSubmatch(line)
165
+		if match == nil {
166
+			continue
167
+		}
168
+		domains = strings.Fields(string(match[1]))
169
+	}
170
+	return domains
171
+}
172
+
173
+func Build(path string, dns, dnsSearch []string) error {
174
+	content := bytes.NewBuffer(nil)
175
+	for _, dns := range dns {
176
+		if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil {
177
+			return err
178
+		}
179
+	}
180
+	if len(dnsSearch) > 0 {
181
+		if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." {
182
+			if _, err := content.WriteString("search " + searchString + "\n"); err != nil {
183
+				return err
184
+			}
185
+		}
186
+	}
187
+
188
+	return ioutil.WriteFile(path, content.Bytes(), 0644)
189
+}
0 190
new file mode 100644
... ...
@@ -0,0 +1,238 @@
0
+package resolvconf
1
+
2
+import (
3
+	"bytes"
4
+	"io/ioutil"
5
+	"os"
6
+	"testing"
7
+)
8
+
9
+func TestGet(t *testing.T) {
10
+	resolvConfUtils, err := Get()
11
+	if err != nil {
12
+		t.Fatal(err)
13
+	}
14
+	resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
15
+	if err != nil {
16
+		t.Fatal(err)
17
+	}
18
+	if string(resolvConfUtils) != string(resolvConfSystem) {
19
+		t.Fatalf("/etc/resolv.conf and GetResolvConf have different content.")
20
+	}
21
+}
22
+
23
+func TestGetNameservers(t *testing.T) {
24
+	for resolv, result := range map[string][]string{`
25
+nameserver 1.2.3.4
26
+nameserver 40.3.200.10
27
+search example.com`: {"1.2.3.4", "40.3.200.10"},
28
+		`search example.com`: {},
29
+		`nameserver 1.2.3.4
30
+search example.com
31
+nameserver 4.30.20.100`: {"1.2.3.4", "4.30.20.100"},
32
+		``: {},
33
+		`  nameserver 1.2.3.4   `: {"1.2.3.4"},
34
+		`search example.com
35
+nameserver 1.2.3.4
36
+#nameserver 4.3.2.1`: {"1.2.3.4"},
37
+		`search example.com
38
+nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4"},
39
+	} {
40
+		test := GetNameservers([]byte(resolv))
41
+		if !strSlicesEqual(test, result) {
42
+			t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv)
43
+		}
44
+	}
45
+}
46
+
47
+func TestGetNameserversAsCIDR(t *testing.T) {
48
+	for resolv, result := range map[string][]string{`
49
+nameserver 1.2.3.4
50
+nameserver 40.3.200.10
51
+search example.com`: {"1.2.3.4/32", "40.3.200.10/32"},
52
+		`search example.com`: {},
53
+		`nameserver 1.2.3.4
54
+search example.com
55
+nameserver 4.30.20.100`: {"1.2.3.4/32", "4.30.20.100/32"},
56
+		``: {},
57
+		`  nameserver 1.2.3.4   `: {"1.2.3.4/32"},
58
+		`search example.com
59
+nameserver 1.2.3.4
60
+#nameserver 4.3.2.1`: {"1.2.3.4/32"},
61
+		`search example.com
62
+nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4/32"},
63
+	} {
64
+		test := GetNameserversAsCIDR([]byte(resolv))
65
+		if !strSlicesEqual(test, result) {
66
+			t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv)
67
+		}
68
+	}
69
+}
70
+
71
+func TestGetSearchDomains(t *testing.T) {
72
+	for resolv, result := range map[string][]string{
73
+		`search example.com`:           {"example.com"},
74
+		`search example.com # ignored`: {"example.com"},
75
+		` 	  search 	 example.com 	  `: {"example.com"},
76
+		` 	  search 	 example.com 	  # ignored`: {"example.com"},
77
+		`search foo.example.com example.com`: {"foo.example.com", "example.com"},
78
+		`	   search   	   foo.example.com 	 example.com 	`: {"foo.example.com", "example.com"},
79
+		`	   search   	   foo.example.com 	 example.com 	# ignored`: {"foo.example.com", "example.com"},
80
+		``:          {},
81
+		`# ignored`: {},
82
+		`nameserver 1.2.3.4
83
+search foo.example.com example.com`: {"foo.example.com", "example.com"},
84
+		`nameserver 1.2.3.4
85
+search dup1.example.com dup2.example.com
86
+search foo.example.com example.com`: {"foo.example.com", "example.com"},
87
+		`nameserver 1.2.3.4
88
+search foo.example.com example.com
89
+nameserver 4.30.20.100`: {"foo.example.com", "example.com"},
90
+	} {
91
+		test := GetSearchDomains([]byte(resolv))
92
+		if !strSlicesEqual(test, result) {
93
+			t.Fatalf("Wrong search domain string {%s} should be %v. Input: %s", test, result, resolv)
94
+		}
95
+	}
96
+}
97
+
98
+func strSlicesEqual(a, b []string) bool {
99
+	if len(a) != len(b) {
100
+		return false
101
+	}
102
+
103
+	for i, v := range a {
104
+		if v != b[i] {
105
+			return false
106
+		}
107
+	}
108
+
109
+	return true
110
+}
111
+
112
+func TestBuild(t *testing.T) {
113
+	file, err := ioutil.TempFile("", "")
114
+	if err != nil {
115
+		t.Fatal(err)
116
+	}
117
+	defer os.Remove(file.Name())
118
+
119
+	err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"search1"})
120
+	if err != nil {
121
+		t.Fatal(err)
122
+	}
123
+
124
+	content, err := ioutil.ReadFile(file.Name())
125
+	if err != nil {
126
+		t.Fatal(err)
127
+	}
128
+
129
+	if expected := "nameserver ns1\nnameserver ns2\nnameserver ns3\nsearch search1\n"; !bytes.Contains(content, []byte(expected)) {
130
+		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
131
+	}
132
+}
133
+
134
+func TestBuildWithZeroLengthDomainSearch(t *testing.T) {
135
+	file, err := ioutil.TempFile("", "")
136
+	if err != nil {
137
+		t.Fatal(err)
138
+	}
139
+	defer os.Remove(file.Name())
140
+
141
+	err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"."})
142
+	if err != nil {
143
+		t.Fatal(err)
144
+	}
145
+
146
+	content, err := ioutil.ReadFile(file.Name())
147
+	if err != nil {
148
+		t.Fatal(err)
149
+	}
150
+
151
+	if expected := "nameserver ns1\nnameserver ns2\nnameserver ns3\n"; !bytes.Contains(content, []byte(expected)) {
152
+		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
153
+	}
154
+	if notExpected := "search ."; bytes.Contains(content, []byte(notExpected)) {
155
+		t.Fatalf("Expected to not find '%s' got '%s'", notExpected, content)
156
+	}
157
+}
158
+
159
+func TestFilterResolvDns(t *testing.T) {
160
+	ns0 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\n"
161
+
162
+	if result, _ := FilterResolvDns([]byte(ns0), false); result != nil {
163
+		if ns0 != string(result) {
164
+			t.Fatalf("Failed No Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
165
+		}
166
+	}
167
+
168
+	ns1 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\nnameserver 127.0.0.1\n"
169
+	if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
170
+		if ns0 != string(result) {
171
+			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
172
+		}
173
+	}
174
+
175
+	ns1 = "nameserver 10.16.60.14\nnameserver 127.0.0.1\nnameserver 10.16.60.21\n"
176
+	if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
177
+		if ns0 != string(result) {
178
+			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
179
+		}
180
+	}
181
+
182
+	ns1 = "nameserver 127.0.1.1\nnameserver 10.16.60.14\nnameserver 10.16.60.21\n"
183
+	if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
184
+		if ns0 != string(result) {
185
+			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
186
+		}
187
+	}
188
+
189
+	ns1 = "nameserver ::1\nnameserver 10.16.60.14\nnameserver 127.0.2.1\nnameserver 10.16.60.21\n"
190
+	if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
191
+		if ns0 != string(result) {
192
+			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
193
+		}
194
+	}
195
+
196
+	ns1 = "nameserver 10.16.60.14\nnameserver ::1\nnameserver 10.16.60.21\nnameserver ::1"
197
+	if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
198
+		if ns0 != string(result) {
199
+			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
200
+		}
201
+	}
202
+
203
+	// with IPv6 disabled (false param), the IPv6 nameserver should be removed
204
+	ns1 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\nnameserver ::1"
205
+	if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
206
+		if ns0 != string(result) {
207
+			t.Fatalf("Failed Localhost+IPv6 off: expected \n<%s> got \n<%s>", ns0, string(result))
208
+		}
209
+	}
210
+
211
+	// with IPv6 enabled, the IPv6 nameserver should be preserved
212
+	ns0 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\n"
213
+	ns1 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\nnameserver ::1"
214
+	if result, _ := FilterResolvDns([]byte(ns1), true); result != nil {
215
+		if ns0 != string(result) {
216
+			t.Fatalf("Failed Localhost+IPv6 on: expected \n<%s> got \n<%s>", ns0, string(result))
217
+		}
218
+	}
219
+
220
+	// with IPv6 enabled, and no non-localhost servers, Google defaults (both IPv4+IPv6) should be added
221
+	ns0 = "\nnameserver 8.8.8.8\nnameserver 8.8.4.4\nnameserver 2001:4860:4860::8888\nnameserver 2001:4860:4860::8844"
222
+	ns1 = "nameserver 127.0.0.1\nnameserver ::1\nnameserver 127.0.2.1"
223
+	if result, _ := FilterResolvDns([]byte(ns1), true); result != nil {
224
+		if ns0 != string(result) {
225
+			t.Fatalf("Failed no Localhost+IPv6 enabled: expected \n<%s> got \n<%s>", ns0, string(result))
226
+		}
227
+	}
228
+
229
+	// with IPv6 disabled, and no non-localhost servers, Google defaults (only IPv4) should be added
230
+	ns0 = "\nnameserver 8.8.8.8\nnameserver 8.8.4.4"
231
+	ns1 = "nameserver 127.0.0.1\nnameserver ::1\nnameserver 127.0.2.1"
232
+	if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
233
+		if ns0 != string(result) {
234
+			t.Fatalf("Failed no Localhost+IPv6 enabled: expected \n<%s> got \n<%s>", ns0, string(result))
235
+		}
236
+	}
237
+}