diff --git a/libnetwork/resolvconf/resolvconf.go b/libnetwork/resolvconf/resolvconf.go index 4805b0eadf..3be2bff6ca 100644 --- a/libnetwork/resolvconf/resolvconf.go +++ b/libnetwork/resolvconf/resolvconf.go @@ -5,7 +5,6 @@ import ( "bytes" "net/netip" "os" - "strings" "github.com/docker/docker/libnetwork/internal/resolvconf" "github.com/opencontainers/go-digest" @@ -127,31 +126,33 @@ func GetOptions(resolvConf []byte) []string { // // Note that the resolv.conf file is written, but the hash file is not. func Build(path string, nameservers, dnsSearch, dnsOptions []string) (*File, error) { - content := bytes.NewBuffer(nil) - if len(dnsSearch) > 0 { - if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." { - if _, err := content.WriteString("search " + searchString + "\n"); err != nil { - return nil, err - } - } - } - for _, dns := range nameservers { - if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil { + var ns []netip.Addr + for _, addr := range nameservers { + ipAddr, err := netip.ParseAddr(addr) + if err != nil { return nil, err } + ns = append(ns, ipAddr) } - if len(dnsOptions) > 0 { - if optsString := strings.Join(dnsOptions, " "); strings.Trim(optsString, " ") != "" { - if _, err := content.WriteString("options " + optsString + "\n"); err != nil { - return nil, err - } - } - } + rc := resolvconf.ResolvConf{} + rc.OverrideNameServers(ns) + rc.OverrideSearch(dnsSearch) + rc.OverrideOptions(dnsOptions) - if err := os.WriteFile(path, content.Bytes(), 0o644); err != nil { + content, err := rc.Generate(false) + if err != nil { return nil, err } - hash := digest.FromBytes(content.Bytes()) - return &File{Content: content.Bytes(), Hash: []byte(hash)}, nil + // Write the resolv.conf file - it's bind-mounted into the container, so can't + // move a temp file into place, just have to truncate and write it. + // + // TODO(thaJeztah): the Build function is currently only used by BuildKit, which only uses "File.Content", and doesn't require the file to be written. + if err := os.WriteFile(path, content, 0o644); err != nil { + return nil, err + } + + // TODO(thaJeztah): the Build function is currently only used by BuildKit, which does not use the Hash + hash := digest.FromBytes(content) + return &File{Content: content, Hash: []byte(hash)}, nil } diff --git a/libnetwork/resolvconf/resolvconf_unix_test.go b/libnetwork/resolvconf/resolvconf_unix_test.go index 5cf6ea8397..3f98a7e39d 100644 --- a/libnetwork/resolvconf/resolvconf_unix_test.go +++ b/libnetwork/resolvconf/resolvconf_unix_test.go @@ -289,6 +289,16 @@ func strSlicesEqual(a, b []string) bool { return true } +const ( + // Example IP-addresses as defined in [RFC 5737], [RFC 3849, section 2]. + // + // [RFC 5737]: https://datatracker.ietf.org/doc/html/rfc5737 + // [RFC 3849, section 2]: https://datatracker.ietf.org/doc/html/rfc3849#section-2 + testNS1 = "192.0.2.1" + testNS2 = "2001:db8::1" + testNS3 = "203.0.113.3" +) + func TestBuild(t *testing.T) { tmpDir := t.TempDir() file, err := os.CreateTemp(tmpDir, "") @@ -296,12 +306,18 @@ func TestBuild(t *testing.T) { t.Fatal(err) } - f, err := Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"search1"}, []string{"opt1"}) + f, err := Build(file.Name(), []string{testNS1, testNS2, testNS3}, []string{"search1"}, []string{"opt1"}) if err != nil { t.Fatal(err) } - const expected = "search search1\nnameserver ns1\nnameserver ns2\nnameserver ns3\noptions opt1\n" + const expected = `nameserver 192.0.2.1 +nameserver 2001:db8::1 +nameserver 203.0.113.3 +search search1 +options opt1 +` + if !bytes.Equal(f.Content, []byte(expected)) { t.Errorf("Expected to find '%s' got '%s'", expected, f.Content) } @@ -321,12 +337,17 @@ func TestBuildWithZeroLengthDomainSearch(t *testing.T) { t.Fatal(err) } - f, err := Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"."}, []string{"opt1"}) + f, err := Build(file.Name(), []string{testNS1, testNS2, testNS3}, []string{"."}, []string{"opt1"}) if err != nil { t.Fatal(err) } - const expected = "nameserver ns1\nnameserver ns2\nnameserver ns3\noptions opt1\n" + const expected = `nameserver 192.0.2.1 +nameserver 2001:db8::1 +nameserver 203.0.113.3 +options opt1 +` + if !bytes.Equal(f.Content, []byte(expected)) { t.Errorf("Expected to find '%s' got '%s'", expected, f.Content) } @@ -346,12 +367,17 @@ func TestBuildWithNoOptions(t *testing.T) { t.Fatal(err) } - f, err := Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"search1"}, []string{}) + f, err := Build(file.Name(), []string{testNS1, testNS2, testNS3}, []string{"search1"}, []string{}) if err != nil { t.Fatal(err) } - const expected = "search search1\nnameserver ns1\nnameserver ns2\nnameserver ns3\n" + const expected = `nameserver 192.0.2.1 +nameserver 2001:db8::1 +nameserver 203.0.113.3 +search search1 +` + if !bytes.Equal(f.Content, []byte(expected)) { t.Errorf("Expected to find '%s' got '%s'", expected, f.Content) }