Quellcode durchsuchen

Add DNS 'options' support

This is needed to expose DNS options like 'ndots' into containers.

https://github.com/docker/docker/issues/14069

Signed-off-by: Tim Hockin <thockin@google.com>
Tim Hockin vor 10 Jahren
Ursprung
Commit
27296caeb8

+ 26 - 3
libnetwork/resolvconf/resolvconf.go

@@ -30,6 +30,7 @@ var (
 	nsIPv6Regexp      = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`)
 	nsRegexp          = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
 	searchRegexp      = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
+	optionsRegexp     = regexp.MustCompile(`^\s*options\s*(([^\s]+\s*)*)$`)
 )
 
 var lastModified struct {
@@ -165,10 +166,25 @@ func GetSearchDomains(resolvConf []byte) []string {
 	return domains
 }
 
+// GetOptions returns options (if any) listed in /etc/resolv.conf
+// If more than one options line is encountered, only the contents of the last
+// one is returned.
+func GetOptions(resolvConf []byte) []string {
+	options := []string{}
+	for _, line := range getLines(resolvConf, []byte("#")) {
+		match := optionsRegexp.FindSubmatch(line)
+		if match == nil {
+			continue
+		}
+		options = strings.Fields(string(match[1]))
+	}
+	return options
+}
+
 // Build writes a configuration file to path containing a "nameserver" entry
-// for every element in dns, and a "search" entry for every element in
-// dnsSearch.
-func Build(path string, dns, dnsSearch []string) (string, error) {
+// for every element in dns, a "search" entry for every element in
+// dnsSearch, and an "options" entry for every element in dnsOptions.
+func Build(path string, dns, dnsSearch, dnsOptions []string) (string, error) {
 	content := bytes.NewBuffer(nil)
 	if len(dnsSearch) > 0 {
 		if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." {
@@ -182,6 +198,13 @@ func Build(path string, dns, dnsSearch []string) (string, error) {
 			return "", err
 		}
 	}
+	if len(dnsOptions) > 0 {
+		if optsString := strings.Join(dnsOptions, " "); strings.Trim(optsString, " ") != "" {
+			if _, err := content.WriteString("options " + optsString + "\n"); err != nil {
+				return "", err
+			}
+		}
+	}
 
 	hash, err := ioutils.HashData(bytes.NewReader(content.Bytes()))
 	if err != nil {

+ 55 - 4
libnetwork/resolvconf/resolvconf_test.go

@@ -98,6 +98,32 @@ nameserver 4.30.20.100`: {"foo.example.com", "example.com"},
 	}
 }
 
+func TestGetOptions(t *testing.T) {
+	for resolv, result := range map[string][]string{
+		`options opt1`:           {"opt1"},
+		`options opt1 # ignored`: {"opt1"},
+		` 	  options 	 opt1 	  `: {"opt1"},
+		` 	  options 	 opt1 	  # ignored`: {"opt1"},
+		`options opt1 opt2 opt3`:           {"opt1", "opt2", "opt3"},
+		`options opt1 opt2 opt3 # ignored`: {"opt1", "opt2", "opt3"},
+		`	   options 	 opt1 	 opt2 	 opt3 	`: {"opt1", "opt2", "opt3"},
+		`	   options 	 opt1 	 opt2 	 opt3 	# ignored`: {"opt1", "opt2", "opt3"},
+		``:                   {},
+		`# ignored`:          {},
+		`nameserver 1.2.3.4`: {},
+		`nameserver 1.2.3.4
+options opt1 opt2 opt3`: {"opt1", "opt2", "opt3"},
+		`nameserver 1.2.3.4
+options opt1 opt2
+options opt3 opt4`: {"opt3", "opt4"},
+	} {
+		test := GetOptions([]byte(resolv))
+		if !strSlicesEqual(test, result) {
+			t.Fatalf("Wrong options string {%s} should be %v. Input: %s", test, result, resolv)
+		}
+	}
+}
+
 func strSlicesEqual(a, b []string) bool {
 	if len(a) != len(b) {
 		return false
@@ -119,7 +145,7 @@ func TestBuild(t *testing.T) {
 	}
 	defer os.Remove(file.Name())
 
-	_, err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"search1"})
+	_, err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"search1"}, []string{"opt1"})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -129,7 +155,7 @@ func TestBuild(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if expected := "search search1\nnameserver ns1\nnameserver ns2\nnameserver ns3\n"; !bytes.Contains(content, []byte(expected)) {
+	if expected := "search search1\nnameserver ns1\nnameserver ns2\nnameserver ns3\noptions opt1\n"; !bytes.Contains(content, []byte(expected)) {
 		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
 	}
 }
@@ -141,7 +167,7 @@ func TestBuildWithZeroLengthDomainSearch(t *testing.T) {
 	}
 	defer os.Remove(file.Name())
 
-	_, err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"."})
+	_, err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"."}, []string{"opt1"})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -151,7 +177,32 @@ func TestBuildWithZeroLengthDomainSearch(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if expected := "nameserver ns1\nnameserver ns2\nnameserver ns3\n"; !bytes.Contains(content, []byte(expected)) {
+	if expected := "nameserver ns1\nnameserver ns2\nnameserver ns3\noptions opt1\n"; !bytes.Contains(content, []byte(expected)) {
+		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+	}
+	if notExpected := "search ."; bytes.Contains(content, []byte(notExpected)) {
+		t.Fatalf("Expected to not find '%s' got '%s'", notExpected, content)
+	}
+}
+
+func TestBuildWithNoOptions(t *testing.T) {
+	file, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(file.Name())
+
+	_, err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"search1"}, []string{})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	content, err := ioutil.ReadFile(file.Name())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if expected := "search search1\nnameserver ns1\nnameserver ns2\nnameserver ns3\n"; !bytes.Contains(content, []byte(expected)) {
 		t.Fatalf("Expected to find '%s' got '%s'", expected, content)
 	}
 	if notExpected := "search ."; bytes.Contains(content, []byte(notExpected)) {

+ 15 - 2
libnetwork/sandbox.go

@@ -93,6 +93,7 @@ type resolvConfPathConfig struct {
 	resolvConfHashFile   string
 	dnsList              []string
 	dnsSearchList        []string
+	dnsOptionsList       []string
 }
 
 type containerConfig struct {
@@ -406,17 +407,21 @@ func (sb *sandbox) setupDNS() error {
 	}
 	dnsList := resolvconf.GetNameservers(resolvConf)
 	dnsSearchList := resolvconf.GetSearchDomains(resolvConf)
+	dnsOptionsList := resolvconf.GetOptions(resolvConf)
 
-	if len(sb.config.dnsList) > 0 || len(sb.config.dnsSearchList) > 0 {
+	if len(sb.config.dnsList) > 0 || len(sb.config.dnsSearchList) > 0 || len(dnsOptionsList) > 0 {
 		if len(sb.config.dnsList) > 0 {
 			dnsList = sb.config.dnsList
 		}
 		if len(sb.config.dnsSearchList) > 0 {
 			dnsSearchList = sb.config.dnsSearchList
 		}
+		if len(sb.config.dnsOptionsList) > 0 {
+			dnsOptionsList = sb.config.dnsOptionsList
+		}
 	}
 
-	hash, err := resolvconf.Build(sb.config.resolvConfPath, dnsList, dnsSearchList)
+	hash, err := resolvconf.Build(sb.config.resolvConfPath, dnsList, dnsSearchList, dnsOptionsList)
 	if err != nil {
 		return err
 	}
@@ -580,6 +585,14 @@ func OptionDNSSearch(search string) SandboxOption {
 	}
 }
 
+// OptionDNSOptions function returns an option setter for dns options entry option to
+// be passed to container Create method.
+func OptionDNSOptions(options string) SandboxOption {
+	return func(sb *sandbox) {
+		sb.config.dnsOptionsList = append(sb.config.dnsOptionsList, options)
+	}
+}
+
 // OptionUseDefaultSandbox function returns an option setter for using default sandbox to
 // be passed to container Create method.
 func OptionUseDefaultSandbox() SandboxOption {