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>
This commit is contained in:
Tim Hockin 2015-08-21 17:31:31 -07:00
parent b236fa5cfb
commit 27296caeb8
3 changed files with 96 additions and 9 deletions

View File

@ -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 {

View File

@ -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)) {

View File

@ -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 {