Skip to content
Open
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
52 changes: 48 additions & 4 deletions ecs-agent/netlib/platform/common_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,10 +566,54 @@ func (c *common) createResolvConf(netNSDir string,
return err
}

// Copy Host's resolv.conf file.
return c.copyFile(filepath.Join(netNSDir, ResolveConfFileName),
filepath.Join(c.resolvConfPath, ResolveConfFileName),
taskDNSConfigFileMode)
// Copy Host's resolv.conf file and backfill DNS fields on the interface
// so downstream consumers can read DNS config from the model.
src := filepath.Join(c.resolvConfPath, ResolveConfFileName)
contents, err := c.ioutil.ReadFile(src)
if err != nil {
return errors.Wrapf(err, "unable to read %s", src)
}
dst := filepath.Join(netNSDir, ResolveConfFileName)
if err := c.ioutil.WriteFile(dst, contents, taskDNSConfigFileMode); err != nil {
return errors.Wrapf(err, "unable to write to %s", dst)
}
servers, searches := parseResolvConf(contents)
iface.DomainNameServers = servers
iface.DomainNameSearchList = searches
return nil
}

// parseResolvConf extracts nameserver addresses and search domains from content
// in the resolv.conf(5) format.
//
// Parsing rules (matching the behavior of Go's net.dnsReadConfig in
// src/net/dnsconfig_unix.go and glibc's res_init):
// - Lines starting with ';' or '#' are treated as comments and skipped.
// - Fields are separated by any combination of spaces and tabs.
// - A "nameserver" line contributes one IP address (the second field).
// - A "search" line contributes all subsequent fields as search domains;
// if multiple search lines appear, only the last is used.
// - All other directives are ignored.
func parseResolvConf(content []byte) (servers, searches []string) {
for _, line := range bytes.Split(content, []byte("\n")) {
if len(line) == 0 || line[0] == ';' || line[0] == '#' {
continue
}
fields := bytes.Fields(line)
if len(fields) < 2 {
continue
}
switch string(fields[0]) {
case "nameserver":
servers = append(servers, string(fields[1]))
case "search":
searches = nil
for _, f := range fields[1:] {
searches = append(searches, string(f))
}
}
}
return servers, searches
}

func (c *common) copyFile(dst, src string, fileMode os.FileMode) error {
Expand Down
75 changes: 75 additions & 0 deletions ecs-agent/netlib/platform/common_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,3 +606,78 @@ func TestParseHosts(t *testing.T) {
})
}
}

func TestParseResolvConf(t *testing.T) {
tests := []struct {
name string
input string
expectedServers []string
expectedSearch []string
}{
{
// Test fixture from Go's net package resolv.conf parser tests:
// https://github.com/golang/go/blob/master/src/net/testdata/resolv.conf
name: "Go stdlib test fixture",
input: "# /etc/resolv.conf\n" +
"domain localdomain\n" +
"nameserver 8.8.8.8\n" +
"nameserver 2001:4860:4860::8888\n" +
"nameserver fe80::1%lo0\n" +
"options ndots:5 timeout:10 attempts:3 rotate\n" +
"options attempts 3\n",
expectedServers: []string{"8.8.8.8", "2001:4860:4860::8888", "fe80::1%lo0"},
expectedSearch: nil,
},
{
name: "semicolon comments",
input: "; this is a comment\nnameserver 8.8.8.8\n",
expectedServers: []string{"8.8.8.8"},
expectedSearch: nil,
},
{
name: "tabs as separators",
input: "nameserver\t10.0.0.2\nsearch\tlocal\texample.com\n",
expectedServers: []string{"10.0.0.2"},
expectedSearch: []string{"local", "example.com"},
},
{
name: "multiple spaces between keyword and value",
input: "nameserver 8.8.8.8\nsearch example.com\n",
expectedServers: []string{"8.8.8.8"},
expectedSearch: []string{"example.com"},
},
{
name: "last search directive wins",
input: "search first.com\n" +
"search second.com third.com\n",
expectedServers: nil,
expectedSearch: []string{"second.com", "third.com"},
},
{
name: "empty input",
input: "",
expectedServers: nil,
expectedSearch: nil,
},
{
name: "only comments and blanks",
input: "# comment\n\n; another\n",
expectedServers: nil,
expectedSearch: nil,
},
{
name: "unknown directives ignored",
input: "options ndots:5\nnameserver 10.0.0.2\ndomain example.com\nsortlist 130.155.160.0\n",
expectedServers: []string{"10.0.0.2"},
expectedSearch: nil,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
servers, searches := parseResolvConf([]byte(tc.input))
assert.Equal(t, tc.expectedServers, servers)
assert.Equal(t, tc.expectedSearch, searches)
})
}
}
Loading