Browse code

daemon/libnetwork/resolvconf: remove unused code

This package is a wrapper for the libnetwork/internal/resolvconf package,
which is a modernized, more performant rewrite of the original parsing
code.

The libnetwork/resolvconf package was still maintained because it was
used by BuildKit, but since [moby/buildkit@3d43066], BuildKit maintains
its own copy of the internal package.

The only remaining uses of this package was as part of some tests (which
would also benefit of using the internal pacakge's implementation directly),
and a _single_ use of `resolvconf.Path` in the daemon, which cannot use
the internal package currently because it's internal to libnetwork.

This patch:

- Removes all functions that were not used.
- Rewrites some tests in libnetwork to use the internal/resolvconf package
directly, instead of depending on the wrapper.
- Add TODOs to consider moving the "Path" function separate (which could
be in daemon/config if we consider it to be the default for the daemon's
resolvconf path configuration).

[moby/buildkit@3d43066]: https://github.com/moby/buildkit/commit/3d43066f2e814d4d10383ba871256245edf08a25

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2025/08/01 21:28:43
Showing 7 changed files
... ...
@@ -146,6 +146,7 @@ func setupResolvConf(config *config.Config) {
146 146
 	if config.ResolvConf != "" {
147 147
 		return
148 148
 	}
149
+	// FIXME(thaJeztah): we can't use [github.com/moby/moby/v2/daemon/libnetwork/internal/resolvconf.Path] here, because it's internal to libnetwork.
149 150
 	config.ResolvConf = resolvconf.Path()
150 151
 }
151 152
 
... ...
@@ -3,14 +3,15 @@
3 3
 package netutils
4 4
 
5 5
 import (
6
+	"bytes"
6 7
 	"net/netip"
7 8
 	"os"
8 9
 	"slices"
9 10
 
10 11
 	"github.com/moby/moby/v2/daemon/libnetwork/internal/netiputil"
12
+	"github.com/moby/moby/v2/daemon/libnetwork/internal/resolvconf"
11 13
 	"github.com/moby/moby/v2/daemon/libnetwork/nlwrap"
12 14
 	"github.com/moby/moby/v2/daemon/libnetwork/ns"
13
-	"github.com/moby/moby/v2/daemon/libnetwork/resolvconf"
14 15
 	"github.com/moby/moby/v2/daemon/libnetwork/types"
15 16
 	"github.com/pkg/errors"
16 17
 	"github.com/vishvananda/netlink"
... ...
@@ -62,7 +63,7 @@ func InferReservedNetworks(v6 bool) []netip.Prefix {
62 62
 	// We don't really care if os.ReadFile fails here. It either doesn't exist,
63 63
 	// or we can't read it for some reason.
64 64
 	if rc, err := os.ReadFile(resolvconf.Path()); err == nil {
65
-		reserved = slices.DeleteFunc(resolvconf.GetNameserversAsPrefix(rc), func(p netip.Prefix) bool {
65
+		reserved = slices.DeleteFunc(tryGetNameserversAsPrefix(rc), func(p netip.Prefix) bool {
66 66
 			return p.Addr().Is6() != v6
67 67
 		})
68 68
 	}
... ...
@@ -75,6 +76,22 @@ func InferReservedNetworks(v6 bool) []netip.Prefix {
75 75
 	return reserved
76 76
 }
77 77
 
78
+// tryGetNameserversAsPrefix returns nameservers (if any) listed in
79
+// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32"). It ignores
80
+// failures to parse the file, as this utility is used as a "best-effort".
81
+func tryGetNameserversAsPrefix(resolvConf []byte) []netip.Prefix {
82
+	rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "")
83
+	if err != nil {
84
+		return nil
85
+	}
86
+	nsAddrs := rc.NameServers()
87
+	nameservers := make([]netip.Prefix, 0, len(nsAddrs))
88
+	for _, addr := range nsAddrs {
89
+		nameservers = append(nameservers, netip.PrefixFrom(addr, addr.BitLen()))
90
+	}
91
+	return nameservers
92
+}
93
+
78 94
 // queryOnLinkRoutes returns a list of on-link routes available on the host.
79 95
 // Only IPv4 prefixes are returned as there's no such thing as on-link
80 96
 // routes for IPv6.
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"strings"
9 9
 	"testing"
10 10
 
11
+	"github.com/google/go-cmp/cmp/cmpopts"
11 12
 	"github.com/moby/moby/v2/daemon/libnetwork/internal/netiputil"
12 13
 	"github.com/moby/moby/v2/internal/testutils/netnsutils"
13 14
 	"github.com/vishvananda/netlink"
... ...
@@ -60,6 +61,61 @@ func TestGenerateRandomName(t *testing.T) {
60 60
 	}
61 61
 }
62 62
 
63
+func TestGetNameserversAsPrefix(t *testing.T) {
64
+	for _, tc := range []struct {
65
+		input  string
66
+		result []netip.Prefix
67
+	}{
68
+		{
69
+			input:  ``,
70
+			result: []netip.Prefix{},
71
+		},
72
+		{
73
+			input:  `search example.com`,
74
+			result: []netip.Prefix{},
75
+		},
76
+		{
77
+			input:  `  nameserver 1.2.3.4   `,
78
+			result: []netip.Prefix{netip.MustParsePrefix("1.2.3.4/32")},
79
+		},
80
+		{
81
+			input: `
82
+nameserver 1.2.3.4
83
+nameserver 40.3.200.10
84
+search example.com`,
85
+			result: []netip.Prefix{netip.MustParsePrefix("1.2.3.4/32"), netip.MustParsePrefix("40.3.200.10/32")},
86
+		},
87
+		{
88
+			input: `nameserver 1.2.3.4
89
+search example.com
90
+nameserver 4.30.20.100`,
91
+			result: []netip.Prefix{netip.MustParsePrefix("1.2.3.4/32"), netip.MustParsePrefix("4.30.20.100/32")},
92
+		},
93
+		{
94
+			input: `search example.com
95
+nameserver 1.2.3.4
96
+#nameserver 4.3.2.1`,
97
+			result: []netip.Prefix{netip.MustParsePrefix("1.2.3.4/32")},
98
+		},
99
+		{
100
+			input: `search example.com
101
+nameserver 1.2.3.4 # not 4.3.2.1`,
102
+			result: []netip.Prefix{netip.MustParsePrefix("1.2.3.4/32")},
103
+		},
104
+		{
105
+			input:  `nameserver fd6f:c490:ec68::1`,
106
+			result: []netip.Prefix{netip.MustParsePrefix("fd6f:c490:ec68::1/128")},
107
+		},
108
+		{
109
+			input:  `nameserver fe80::1234%eth0`,
110
+			result: []netip.Prefix{netip.MustParsePrefix("fe80::1234/128")},
111
+		},
112
+	} {
113
+		test := tryGetNameserversAsPrefix([]byte(tc.input))
114
+		assert.DeepEqual(t, test, tc.result, cmpopts.EquateComparable(netip.Prefix{}))
115
+	}
116
+}
117
+
63 118
 // Test mac generation.
64 119
 func TestUtilGenerateRandomMAC(t *testing.T) {
65 120
 	mac1 := GenerateRandomMAC()
... ...
@@ -1,159 +1,14 @@
1
-// Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf
1
+// Package resolvconf provides utility code to get the host's "resolv.conf" path.
2 2
 package resolvconf
3 3
 
4
-import (
5
-	"bytes"
6
-	"fmt"
7
-	"net/netip"
8
-	"os"
9
-
10
-	"github.com/moby/moby/v2/daemon/libnetwork/internal/resolvconf"
11
-	"github.com/opencontainers/go-digest"
12
-)
13
-
14
-// constants for the IP address type
15
-const (
16
-	IP = iota // IPv4 and IPv6
17
-	IPv4
18
-	IPv6
19
-)
20
-
21
-// File contains the resolv.conf content and its hash
22
-type File struct {
23
-	Content []byte
24
-	Hash    []byte
25
-}
4
+import "github.com/moby/moby/v2/daemon/libnetwork/internal/resolvconf"
26 5
 
6
+// Path is an alias for [resolvconf.Path], which is internal to libnetwork.
7
+//
8
+// FIXME(thaJeztah): remove this when possible. This is only used in [github.com/moby/moby/v2/daemon.setupResolvConf].
9
+// Either we can move "libnetwork/internal/resolvconf" to "daemon/internal",
10
+// or move the "Path" function to daemon/config (considering it a default
11
+// for the daemon's config).
27 12
 func Path() string {
28 13
 	return resolvconf.Path()
29 14
 }
30
-
31
-// Get returns the contents of /etc/resolv.conf and its hash
32
-func Get() (*File, error) {
33
-	return GetSpecific(Path())
34
-}
35
-
36
-// GetSpecific returns the contents of the user specified resolv.conf file and its hash
37
-func GetSpecific(path string) (*File, error) {
38
-	resolv, err := os.ReadFile(path)
39
-	if err != nil {
40
-		return nil, err
41
-	}
42
-	hash := digest.FromBytes(resolv)
43
-	return &File{Content: resolv, Hash: []byte(hash)}, nil
44
-}
45
-
46
-// FilterResolvDNS cleans up the config in resolvConf.  It has two main jobs:
47
-//  1. It looks for localhost (127.*|::1) entries in the provided
48
-//     resolv.conf, removing local nameserver entries, and, if the resulting
49
-//     cleaned config has no defined nameservers left, adds default DNS entries
50
-//  2. Given the caller provides the enable/disable state of IPv6, the filter
51
-//     code will remove all IPv6 nameservers if it is not enabled for containers
52
-func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool) (*File, error) {
53
-	rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "")
54
-	if err != nil {
55
-		return nil, err
56
-	}
57
-	rc.TransformForLegacyNw(ipv6Enabled)
58
-	content, err := rc.Generate(false)
59
-	if err != nil {
60
-		return nil, err
61
-	}
62
-	hash := digest.FromBytes(content)
63
-	return &File{Content: content, Hash: []byte(hash)}, nil
64
-}
65
-
66
-// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
67
-func GetNameservers(resolvConf []byte, kind int) []string {
68
-	rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "")
69
-	if err != nil {
70
-		return nil
71
-	}
72
-	nsAddrs := rc.NameServers()
73
-	var nameservers []string
74
-	for _, addr := range nsAddrs {
75
-		if kind == IP {
76
-			nameservers = append(nameservers, addr.String())
77
-		} else if kind == IPv4 && addr.Is4() {
78
-			nameservers = append(nameservers, addr.String())
79
-		} else if kind == IPv6 && addr.Is6() {
80
-			nameservers = append(nameservers, addr.String())
81
-		}
82
-	}
83
-	return nameservers
84
-}
85
-
86
-// GetNameserversAsPrefix returns nameservers (if any) listed in
87
-// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32")
88
-func GetNameserversAsPrefix(resolvConf []byte) []netip.Prefix {
89
-	rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "")
90
-	if err != nil {
91
-		return nil
92
-	}
93
-	nsAddrs := rc.NameServers()
94
-	nameservers := make([]netip.Prefix, 0, len(nsAddrs))
95
-	for _, addr := range nsAddrs {
96
-		nameservers = append(nameservers, netip.PrefixFrom(addr, addr.BitLen()))
97
-	}
98
-	return nameservers
99
-}
100
-
101
-// GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf
102
-// If more than one search line is encountered, only the contents of the last
103
-// one is returned.
104
-func GetSearchDomains(resolvConf []byte) []string {
105
-	rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "")
106
-	if err != nil {
107
-		return nil
108
-	}
109
-	return rc.Search()
110
-}
111
-
112
-// GetOptions returns options (if any) listed in /etc/resolv.conf
113
-// If more than one options line is encountered, only the contents of the last
114
-// one is returned.
115
-func GetOptions(resolvConf []byte) []string {
116
-	rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "")
117
-	if err != nil {
118
-		return nil
119
-	}
120
-	return rc.Options()
121
-}
122
-
123
-// Build generates and writes a configuration file to path containing a nameserver
124
-// entry for every element in nameservers, a "search" entry for every element in
125
-// dnsSearch, and an "options" entry for every element in dnsOptions. It returns
126
-// a File containing the generated content and its (sha256) hash.
127
-//
128
-// Note that the resolv.conf file is written, but the hash file is not.
129
-func Build(path string, nameservers, dnsSearch, dnsOptions []string) (*File, error) {
130
-	var ns []netip.Addr
131
-	for _, addr := range nameservers {
132
-		ipAddr, err := netip.ParseAddr(addr)
133
-		if err != nil {
134
-			return nil, fmt.Errorf("bad nameserver address: %w", err)
135
-		}
136
-		ns = append(ns, ipAddr)
137
-	}
138
-	rc := resolvconf.ResolvConf{}
139
-	rc.OverrideNameServers(ns)
140
-	rc.OverrideSearch(dnsSearch)
141
-	rc.OverrideOptions(dnsOptions)
142
-
143
-	content, err := rc.Generate(false)
144
-	if err != nil {
145
-		return nil, err
146
-	}
147
-
148
-	// Write the resolv.conf file - it's bind-mounted into the container, so can't
149
-	// move a temp file into place, just have to truncate and write it.
150
-	//
151
-	// 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.
152
-	if err := os.WriteFile(path, content, 0o644); err != nil {
153
-		return nil, err
154
-	}
155
-
156
-	// TODO(thaJeztah): the Build function is currently only used by BuildKit, which does not use the Hash
157
-	hash := digest.FromBytes(content)
158
-	return &File{Content: content, Hash: []byte(hash)}, nil
159
-}
160 15
deleted file mode 100644
... ...
@@ -1,455 +0,0 @@
1
-//go:build !windows
2
-
3
-package resolvconf
4
-
5
-import (
6
-	"bytes"
7
-	"net/netip"
8
-	"os"
9
-	"strings"
10
-	"testing"
11
-
12
-	"github.com/google/go-cmp/cmp/cmpopts"
13
-	"github.com/opencontainers/go-digest"
14
-	"gotest.tools/v3/assert"
15
-	is "gotest.tools/v3/assert/cmp"
16
-)
17
-
18
-func TestGet(t *testing.T) {
19
-	actual, err := Get()
20
-	if err != nil {
21
-		t.Fatal(err)
22
-	}
23
-	expected, err := os.ReadFile(Path())
24
-	if err != nil {
25
-		t.Fatal(err)
26
-	}
27
-	if !bytes.Equal(actual.Content, expected) {
28
-		t.Errorf("%s and GetResolvConf have different content.", Path())
29
-	}
30
-	hash := digest.FromBytes(expected)
31
-	if !bytes.Equal(actual.Hash, []byte(hash)) {
32
-		t.Errorf("%s and GetResolvConf have different hashes.", Path())
33
-	}
34
-}
35
-
36
-func TestGetNameservers(t *testing.T) {
37
-	for _, tc := range []struct {
38
-		input  string
39
-		result []string
40
-	}{
41
-		{
42
-			input: ``,
43
-		},
44
-		{
45
-			input: `search example.com`,
46
-		},
47
-		{
48
-			input:  `  nameserver 1.2.3.4   `,
49
-			result: []string{"1.2.3.4"},
50
-		},
51
-		{
52
-			input: `
53
-nameserver 1.2.3.4
54
-nameserver 40.3.200.10
55
-search example.com`,
56
-			result: []string{"1.2.3.4", "40.3.200.10"},
57
-		},
58
-		{
59
-			input: `nameserver 1.2.3.4
60
-search example.com
61
-nameserver 4.30.20.100`,
62
-			result: []string{"1.2.3.4", "4.30.20.100"},
63
-		},
64
-		{
65
-			input: `search example.com
66
-nameserver 1.2.3.4
67
-#nameserver 4.3.2.1`,
68
-			result: []string{"1.2.3.4"},
69
-		},
70
-		{
71
-			input: `search example.com
72
-nameserver 1.2.3.4 # not 4.3.2.1`,
73
-			result: []string{"1.2.3.4"},
74
-		},
75
-	} {
76
-		test := GetNameservers([]byte(tc.input), IP)
77
-		if !strSlicesEqual(test, tc.result) {
78
-			t.Errorf("Wrong nameserver string {%s} should be %v. Input: %s", test, tc.result, tc.input)
79
-		}
80
-	}
81
-}
82
-
83
-func TestGetNameserversAsPrefix(t *testing.T) {
84
-	for _, tc := range []struct {
85
-		input  string
86
-		result []netip.Prefix
87
-	}{
88
-		{
89
-			input:  ``,
90
-			result: []netip.Prefix{},
91
-		},
92
-		{
93
-			input:  `search example.com`,
94
-			result: []netip.Prefix{},
95
-		},
96
-		{
97
-			input:  `  nameserver 1.2.3.4   `,
98
-			result: []netip.Prefix{netip.MustParsePrefix("1.2.3.4/32")},
99
-		},
100
-		{
101
-			input: `
102
-nameserver 1.2.3.4
103
-nameserver 40.3.200.10
104
-search example.com`,
105
-			result: []netip.Prefix{netip.MustParsePrefix("1.2.3.4/32"), netip.MustParsePrefix("40.3.200.10/32")},
106
-		},
107
-		{
108
-			input: `nameserver 1.2.3.4
109
-search example.com
110
-nameserver 4.30.20.100`,
111
-			result: []netip.Prefix{netip.MustParsePrefix("1.2.3.4/32"), netip.MustParsePrefix("4.30.20.100/32")},
112
-		},
113
-		{
114
-			input: `search example.com
115
-nameserver 1.2.3.4
116
-#nameserver 4.3.2.1`,
117
-			result: []netip.Prefix{netip.MustParsePrefix("1.2.3.4/32")},
118
-		},
119
-		{
120
-			input: `search example.com
121
-nameserver 1.2.3.4 # not 4.3.2.1`,
122
-			result: []netip.Prefix{netip.MustParsePrefix("1.2.3.4/32")},
123
-		},
124
-		{
125
-			input:  `nameserver fd6f:c490:ec68::1`,
126
-			result: []netip.Prefix{netip.MustParsePrefix("fd6f:c490:ec68::1/128")},
127
-		},
128
-		{
129
-			input:  `nameserver fe80::1234%eth0`,
130
-			result: []netip.Prefix{netip.MustParsePrefix("fe80::1234/128")},
131
-		},
132
-	} {
133
-		test := GetNameserversAsPrefix([]byte(tc.input))
134
-		assert.DeepEqual(t, test, tc.result, cmpopts.EquateComparable(netip.Prefix{}))
135
-	}
136
-}
137
-
138
-func TestGetSearchDomains(t *testing.T) {
139
-	for _, tc := range []struct {
140
-		input  string
141
-		result []string
142
-	}{
143
-		{
144
-			input: ``,
145
-		},
146
-		{
147
-			input: `# ignored`,
148
-		},
149
-		{
150
-			input:  `search example.com`,
151
-			result: []string{"example.com"},
152
-		},
153
-		{
154
-			input:  `search example.com # notignored`,
155
-			result: []string{"example.com", "#", "notignored"},
156
-		},
157
-		{
158
-			input:  `	  search	 example.com	  `,
159
-			result: []string{"example.com"},
160
-		},
161
-		{
162
-			input:  `	  search	 example.com	  # notignored`,
163
-			result: []string{"example.com", "#", "notignored"},
164
-		},
165
-		{
166
-			input:  `search foo.example.com example.com`,
167
-			result: []string{"foo.example.com", "example.com"},
168
-		},
169
-		{
170
-			input:  `	   search	   foo.example.com	 example.com	`,
171
-			result: []string{"foo.example.com", "example.com"},
172
-		},
173
-		{
174
-			input:  `	   search	   foo.example.com	 example.com	# notignored`,
175
-			result: []string{"foo.example.com", "example.com", "#", "notignored"},
176
-		},
177
-		{
178
-			input: `nameserver 1.2.3.4
179
-search foo.example.com example.com`,
180
-			result: []string{"foo.example.com", "example.com"},
181
-		},
182
-		{
183
-			input: `nameserver 1.2.3.4
184
-search dup1.example.com dup2.example.com
185
-search foo.example.com example.com`,
186
-			result: []string{"foo.example.com", "example.com"},
187
-		},
188
-		{
189
-			input: `nameserver 1.2.3.4
190
-search foo.example.com example.com
191
-nameserver 4.30.20.100`,
192
-			result: []string{"foo.example.com", "example.com"},
193
-		},
194
-		{
195
-			input:  `domain an.example`,
196
-			result: []string{"an.example"},
197
-		},
198
-	} {
199
-		test := GetSearchDomains([]byte(tc.input))
200
-		if !strSlicesEqual(test, tc.result) {
201
-			t.Errorf("Wrong search domain string {%s} should be %v. Input: %s", test, tc.result, tc.input)
202
-		}
203
-	}
204
-}
205
-
206
-func TestGetOptions(t *testing.T) {
207
-	for _, tc := range []struct {
208
-		input  string
209
-		result []string
210
-	}{
211
-		{
212
-			input: ``,
213
-		},
214
-		{
215
-			input: `# ignored`,
216
-		},
217
-		{
218
-			input: `; ignored`,
219
-		},
220
-		{
221
-			input: `nameserver 1.2.3.4`,
222
-		},
223
-		{
224
-			input:  `options opt1`,
225
-			result: []string{"opt1"},
226
-		},
227
-		{
228
-			input:  `options opt1 # notignored`,
229
-			result: []string{"opt1", "#", "notignored"},
230
-		},
231
-		{
232
-			input:  `options opt1 ; notignored`,
233
-			result: []string{"opt1", ";", "notignored"},
234
-		},
235
-		{
236
-			input:  `	  options	 opt1	  `,
237
-			result: []string{"opt1"},
238
-		},
239
-		{
240
-			input:  `	  options	 opt1	  # notignored`,
241
-			result: []string{"opt1", "#", "notignored"},
242
-		},
243
-		{
244
-			input:  `options opt1 opt2 opt3`,
245
-			result: []string{"opt1", "opt2", "opt3"},
246
-		},
247
-		{
248
-			input:  `options opt1 opt2 opt3 # notignored`,
249
-			result: []string{"opt1", "opt2", "opt3", "#", "notignored"},
250
-		},
251
-		{
252
-			input:  `	   options	 opt1	 opt2	 opt3	`,
253
-			result: []string{"opt1", "opt2", "opt3"},
254
-		},
255
-		{
256
-			input:  `	   options	 opt1	 opt2	 opt3	# notignored`,
257
-			result: []string{"opt1", "opt2", "opt3", "#", "notignored"},
258
-		},
259
-		{
260
-			input: `nameserver 1.2.3.4
261
-options opt1 opt2 opt3`,
262
-			result: []string{"opt1", "opt2", "opt3"},
263
-		},
264
-		{
265
-			input: `nameserver 1.2.3.4
266
-options opt1 opt2
267
-options opt3 opt4`,
268
-			result: []string{"opt1", "opt2", "opt3", "opt4"},
269
-		},
270
-	} {
271
-		test := GetOptions([]byte(tc.input))
272
-		if !strSlicesEqual(test, tc.result) {
273
-			t.Errorf("Wrong options string {%s} should be %v. Input: %s", test, tc.result, tc.input)
274
-		}
275
-	}
276
-}
277
-
278
-func strSlicesEqual(a, b []string) bool {
279
-	if len(a) != len(b) {
280
-		return false
281
-	}
282
-
283
-	for i, v := range a {
284
-		if v != b[i] {
285
-			return false
286
-		}
287
-	}
288
-
289
-	return true
290
-}
291
-
292
-const (
293
-	// Example IP-addresses as defined in [RFC 5737], [RFC 3849, section 2].
294
-	//
295
-	// [RFC 5737]: https://datatracker.ietf.org/doc/html/rfc5737
296
-	// [RFC 3849, section 2]: https://datatracker.ietf.org/doc/html/rfc3849#section-2
297
-	testNS1 = "192.0.2.1"
298
-	testNS2 = "2001:db8::1"
299
-	testNS3 = "203.0.113.3"
300
-)
301
-
302
-func TestBuild(t *testing.T) {
303
-	tests := []struct {
304
-		doc         string
305
-		nameServers []string
306
-		dnsSearch   []string
307
-		dnsOptions  []string
308
-		expOut      string
309
-		expErr      string
310
-	}{
311
-		{
312
-			doc:    "no options",
313
-			expOut: ``,
314
-		},
315
-		{
316
-			doc:         "all options",
317
-			nameServers: []string{testNS1, testNS2, testNS3},
318
-			dnsSearch:   []string{"search1"},
319
-			dnsOptions:  []string{"opt1"},
320
-			expOut: `nameserver 192.0.2.1
321
-nameserver 2001:db8::1
322
-nameserver 203.0.113.3
323
-search search1
324
-options opt1
325
-`,
326
-		},
327
-		{
328
-			doc:         "zero-length dns search",
329
-			nameServers: []string{testNS1, testNS2, testNS3},
330
-			dnsSearch:   []string{"."},
331
-			dnsOptions:  []string{"opt1"},
332
-			expOut: `nameserver 192.0.2.1
333
-nameserver 2001:db8::1
334
-nameserver 203.0.113.3
335
-options opt1
336
-`,
337
-		},
338
-		{
339
-			doc:         "no dns options",
340
-			nameServers: []string{testNS1, testNS2, testNS3},
341
-			dnsSearch:   []string{"search1"},
342
-			dnsOptions:  []string{},
343
-			expOut: `nameserver 192.0.2.1
344
-nameserver 2001:db8::1
345
-nameserver 203.0.113.3
346
-search search1
347
-`,
348
-		},
349
-		{
350
-			doc:         "invalid nameserver",
351
-			nameServers: []string{"resolver.example.com"},
352
-			expErr:      `bad nameserver address: ParseAddr("resolver.example.com"): unexpected character (at "resolver.example.com")`,
353
-		},
354
-	}
355
-
356
-	tmpDir := t.TempDir()
357
-	for _, tc := range tests {
358
-		t.Run(tc.doc, func(t *testing.T) {
359
-			file, err := os.CreateTemp(tmpDir, "")
360
-			assert.NilError(t, err)
361
-
362
-			f, err := Build(file.Name(), tc.nameServers, tc.dnsSearch, tc.dnsOptions)
363
-			if tc.expErr != "" {
364
-				assert.Error(t, err, tc.expErr)
365
-			} else if err != nil {
366
-				assert.NilError(t, err)
367
-				assert.Equal(t, string(f.Content), tc.expOut)
368
-			}
369
-
370
-			// Verify the content matches the expected; for error-cases,
371
-			// this verifies the file is empty.
372
-			content, err := os.ReadFile(file.Name())
373
-			assert.NilError(t, err)
374
-			assert.Equal(t, string(content), tc.expOut)
375
-		})
376
-	}
377
-}
378
-
379
-func TestFilterResolvDNS(t *testing.T) {
380
-	testcases := []struct {
381
-		name        string
382
-		input       string
383
-		ipv6Enabled bool
384
-		expOut      string
385
-	}{
386
-		{
387
-			name:   "No localhost",
388
-			input:  "nameserver 10.16.60.14\nnameserver 10.16.60.21\n",
389
-			expOut: "nameserver 10.16.60.14\nnameserver 10.16.60.21",
390
-		},
391
-		{
392
-			name:   "Localhost last",
393
-			input:  "nameserver 10.16.60.14\nnameserver 10.16.60.21\nnameserver 127.0.0.1\n",
394
-			expOut: "nameserver 10.16.60.14\nnameserver 10.16.60.21",
395
-		},
396
-		{
397
-			name:   "Localhost middle",
398
-			input:  "nameserver 10.16.60.14\nnameserver 127.0.0.1\nnameserver 10.16.60.21\n",
399
-			expOut: "nameserver 10.16.60.14\nnameserver 10.16.60.21",
400
-		},
401
-		{
402
-			name:   "Localhost first",
403
-			input:  "nameserver 127.0.1.1\nnameserver 10.16.60.14\nnameserver 10.16.60.21\n",
404
-			expOut: "nameserver 10.16.60.14\nnameserver 10.16.60.21",
405
-		},
406
-		{
407
-			name:   "IPv6 Localhost",
408
-			input:  "nameserver ::1\nnameserver 10.16.60.14\nnameserver 127.0.2.1\nnameserver 10.16.60.21\n",
409
-			expOut: "nameserver 10.16.60.14\nnameserver 10.16.60.21",
410
-		},
411
-		{
412
-			name:   "Two IPv6 Localhosts",
413
-			input:  "nameserver 10.16.60.14\nnameserver ::1\nnameserver 10.16.60.21\nnameserver ::1",
414
-			expOut: "nameserver 10.16.60.14\nnameserver 10.16.60.21",
415
-		},
416
-		{
417
-			name:   "IPv6 disabled",
418
-			input:  "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\nnameserver ::1",
419
-			expOut: "nameserver 10.16.60.14\nnameserver 10.16.60.21",
420
-		},
421
-		{
422
-			name:   "IPv6 link-local disabled",
423
-			input:  "nameserver 10.16.60.14\nnameserver FE80::BB1%1\nnameserver FE80::BB1%eth0\nnameserver 10.16.60.21",
424
-			expOut: "nameserver 10.16.60.14\nnameserver 10.16.60.21",
425
-		},
426
-		{
427
-			name:        "IPv6 enabled",
428
-			input:       "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\nnameserver ::1\n",
429
-			ipv6Enabled: true,
430
-			expOut:      "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21",
431
-		},
432
-		{
433
-			// with IPv6 enabled, and no non-localhost servers, Google defaults (both IPv4+IPv6) should be added
434
-			name:        "localhost only IPv6",
435
-			input:       "nameserver 127.0.0.1\nnameserver ::1\nnameserver 127.0.2.1",
436
-			ipv6Enabled: true,
437
-			expOut:      "nameserver 8.8.8.8\nnameserver 8.8.4.4\nnameserver 2001:4860:4860::8888\nnameserver 2001:4860:4860::8844",
438
-		},
439
-		{
440
-			// with IPv6 disabled, and no non-localhost servers, Google defaults (only IPv4) should be added
441
-			name:   "localhost only no IPv6",
442
-			input:  "nameserver 127.0.0.1\nnameserver ::1\nnameserver 127.0.2.1",
443
-			expOut: "nameserver 8.8.8.8\nnameserver 8.8.4.4",
444
-		},
445
-	}
446
-
447
-	for _, tc := range testcases {
448
-		t.Run(tc.name, func(t *testing.T) {
449
-			f, err := FilterResolvDNS([]byte(tc.input), tc.ipv6Enabled)
450
-			assert.Check(t, is.Nil(err))
451
-			out := strings.TrimSpace(string(f.Content))
452
-			assert.Check(t, is.Equal(out, tc.expOut))
453
-		})
454
-	}
455
-}
... ...
@@ -3,15 +3,26 @@
3 3
 package libnetwork
4 4
 
5 5
 import (
6
+	"bytes"
6 7
 	"context"
8
+	"os"
7 9
 	"testing"
8 10
 
9 11
 	"github.com/moby/moby/v2/daemon/libnetwork/config"
10
-	"github.com/moby/moby/v2/daemon/libnetwork/resolvconf"
12
+	"github.com/moby/moby/v2/daemon/libnetwork/internal/resolvconf"
11 13
 	"gotest.tools/v3/assert"
12 14
 	is "gotest.tools/v3/assert/cmp"
13 15
 )
14 16
 
17
+func getResolvConfOptions(t *testing.T, rcPath string) []string {
18
+	t.Helper()
19
+	resolv, err := os.ReadFile(rcPath)
20
+	assert.NilError(t, err)
21
+	rc, err := resolvconf.Parse(bytes.NewBuffer(resolv), "")
22
+	assert.NilError(t, err)
23
+	return rc.Options()
24
+}
25
+
15 26
 func TestDNSOptions(t *testing.T) {
16 27
 	c, err := New(context.Background(), config.OptionDataDir(t.TempDir()))
17 28
 	assert.NilError(t, err)
... ...
@@ -32,26 +43,20 @@ func TestDNSOptions(t *testing.T) {
32 32
 	assert.NilError(t, err)
33 33
 	err = sb.rebuildDNS()
34 34
 	assert.NilError(t, err)
35
-	currRC, err := resolvconf.GetSpecific(sb.config.resolvConfPath)
36
-	assert.NilError(t, err)
37
-	dnsOptionsList := resolvconf.GetOptions(currRC.Content)
35
+	dnsOptionsList := getResolvConfOptions(t, sb.config.resolvConfPath)
38 36
 	assert.Check(t, is.Len(dnsOptionsList, 1))
39 37
 	assert.Check(t, is.Equal("ndots:0", dnsOptionsList[0]))
40 38
 
41 39
 	sb.config.dnsOptionsList = []string{"ndots:5"}
42 40
 	err = sb.setupDNS()
43 41
 	assert.NilError(t, err)
44
-	currRC, err = resolvconf.GetSpecific(sb.config.resolvConfPath)
45
-	assert.NilError(t, err)
46
-	dnsOptionsList = resolvconf.GetOptions(currRC.Content)
42
+	dnsOptionsList = getResolvConfOptions(t, sb.config.resolvConfPath)
47 43
 	assert.Check(t, is.Len(dnsOptionsList, 1))
48 44
 	assert.Check(t, is.Equal("ndots:5", dnsOptionsList[0]))
49 45
 
50 46
 	err = sb.rebuildDNS()
51 47
 	assert.NilError(t, err)
52
-	currRC, err = resolvconf.GetSpecific(sb.config.resolvConfPath)
53
-	assert.NilError(t, err)
54
-	dnsOptionsList = resolvconf.GetOptions(currRC.Content)
48
+	dnsOptionsList = getResolvConfOptions(t, sb.config.resolvConfPath)
55 49
 	assert.Check(t, is.Len(dnsOptionsList, 1))
56 50
 	assert.Check(t, is.Equal("ndots:5", dnsOptionsList[0]))
57 51
 
... ...
@@ -65,9 +70,7 @@ func TestDNSOptions(t *testing.T) {
65 65
 	assert.NilError(t, err)
66 66
 	err = sb2.rebuildDNS()
67 67
 	assert.NilError(t, err)
68
-	currRC, err = resolvconf.GetSpecific(sb2.config.resolvConfPath)
69
-	assert.NilError(t, err)
70
-	dnsOptionsList = resolvconf.GetOptions(currRC.Content)
68
+	dnsOptionsList = getResolvConfOptions(t, sb2.config.resolvConfPath)
71 69
 	assert.Check(t, is.Len(dnsOptionsList, 1))
72 70
 	assert.Check(t, is.Equal("ndots:0", dnsOptionsList[0]))
73 71
 
... ...
@@ -76,9 +79,7 @@ func TestDNSOptions(t *testing.T) {
76 76
 	assert.NilError(t, err)
77 77
 	err = sb2.rebuildDNS()
78 78
 	assert.NilError(t, err)
79
-	currRC, err = resolvconf.GetSpecific(sb2.config.resolvConfPath)
80
-	assert.NilError(t, err)
81
-	dnsOptionsList = resolvconf.GetOptions(currRC.Content)
79
+	dnsOptionsList = getResolvConfOptions(t, sb2.config.resolvConfPath)
82 80
 	assert.Check(t, is.DeepEqual([]string{"ndots:0"}, dnsOptionsList))
83 81
 
84 82
 	sb2.config.dnsOptionsList = []string{"ndots:-1"}
... ...
@@ -86,8 +87,6 @@ func TestDNSOptions(t *testing.T) {
86 86
 	assert.NilError(t, err)
87 87
 	err = sb2.rebuildDNS()
88 88
 	assert.NilError(t, err)
89
-	currRC, err = resolvconf.GetSpecific(sb2.config.resolvConfPath)
90
-	assert.NilError(t, err)
91
-	dnsOptionsList = resolvconf.GetOptions(currRC.Content)
89
+	dnsOptionsList = getResolvConfOptions(t, sb2.config.resolvConfPath)
92 90
 	assert.Check(t, is.DeepEqual([]string{"ndots:0"}, dnsOptionsList))
93 91
 }
... ...
@@ -12,8 +12,7 @@ import (
12 12
 	"github.com/moby/moby/v2/errdefs"
13 13
 )
14 14
 
15
-// constants for the IP address type
16
-// Deprecated: use the consts defined in github.com/docker/docker/libnetwork/resolvconf
15
+// constants for the IP address type.
17 16
 const (
18 17
 	IP = iota // IPv4 and IPv6
19 18
 	IPv4