1
- // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
2
- //go:build go1.23
3
-
4
1
// Package resolvconf is used to generate a container's /etc/resolv.conf file.
5
2
//
6
3
// Constructor Load and Parse read a resolv.conf file from the filesystem or
@@ -21,19 +18,15 @@ import (
21
18
"bufio"
22
19
"bytes"
23
20
"context"
24
- "fmt"
25
21
"io"
26
- "io/fs"
27
22
"net/netip"
28
23
"os"
24
+ "slices"
29
25
"strconv"
30
26
"strings"
31
- "text/template"
32
27
33
- "github.com/containerd/log"
34
- "github.com/moby/sys/atomicwriter"
35
- "github.com/opencontainers/go-digest"
36
- "github.com/pkg/errors"
28
+ "github.com/moby/buildkit/errdefs"
29
+ "github.com/moby/buildkit/util/bklog"
37
30
)
38
31
39
32
// Fallback nameservers, to use if none can be obtained from the host or command
@@ -70,7 +63,7 @@ type ExtDNSEntry struct {
70
63
71
64
func (ed ExtDNSEntry ) String () string {
72
65
if ed .HostLoopback {
73
- return fmt . Sprintf ( "host(%s)" , ed .Addr )
66
+ return "host(" + ed .Addr . String () + ")"
74
67
}
75
68
return ed .Addr .String ()
76
69
}
@@ -119,7 +112,7 @@ func Parse(reader io.Reader, path string) (ResolvConf, error) {
119
112
rc .processLine (scanner .Text ())
120
113
}
121
114
if err := scanner .Err (); err != nil {
122
- return ResolvConf {}, errSystem { err }
115
+ return ResolvConf {}, errdefs . Internal ( err )
123
116
}
124
117
if _ , ok := rc .Option ("ndots" ); ok {
125
118
rc .md .NDotsFrom = "host"
@@ -141,7 +134,7 @@ func (rc *ResolvConf) SetHeader(c string) {
141
134
142
135
// NameServers returns addresses used in nameserver directives.
143
136
func (rc * ResolvConf ) NameServers () []netip.Addr {
144
- return append ([]netip. Addr ( nil ), rc .nameServers ... )
137
+ return slices . Clone ( rc .nameServers )
145
138
}
146
139
147
140
// OverrideNameServers replaces the current set of nameservers.
@@ -152,7 +145,7 @@ func (rc *ResolvConf) OverrideNameServers(nameServers []netip.Addr) {
152
145
153
146
// Search returns the current DNS search domains.
154
147
func (rc * ResolvConf ) Search () []string {
155
- return append ([] string ( nil ), rc .search ... )
148
+ return slices . Clone ( rc .search )
156
149
}
157
150
158
151
// OverrideSearch replaces the current DNS search domains.
@@ -169,7 +162,7 @@ func (rc *ResolvConf) OverrideSearch(search []string) {
169
162
170
163
// Options returns the current options.
171
164
func (rc * ResolvConf ) Options () []string {
172
- return append ([] string ( nil ), rc .options ... )
165
+ return slices . Clone ( rc .options )
173
166
}
174
167
175
168
// Option finds the last option named search, and returns (value, true) if
@@ -181,7 +174,7 @@ func (rc *ResolvConf) Options() []string {
181
174
// Option("ndots") -> ("1", true)
182
175
// Option("edns0") -> ("", true)
183
176
func (rc * ResolvConf ) Option (search string ) (string , bool ) {
184
- for i := len (rc .options ) - 1 ; i >= 0 ; i -= 1 {
177
+ for i := len (rc .options ) - 1 ; i >= 0 ; i -- {
185
178
k , v , _ := strings .Cut (rc .options [i ], ":" )
186
179
if k == search {
187
180
return v , true
@@ -192,7 +185,7 @@ func (rc *ResolvConf) Option(search string) (string, bool) {
192
185
193
186
// OverrideOptions replaces the current DNS options.
194
187
func (rc * ResolvConf ) OverrideOptions (options []string ) {
195
- rc .options = append ([] string ( nil ), options ... )
188
+ rc .options = slices . Clone ( options )
196
189
rc .md .NDotsFrom = ""
197
190
if _ , exists := rc .Option ("ndots" ); exists {
198
191
rc .md .NDotsFrom = "override"
@@ -227,7 +220,7 @@ func (rc *ResolvConf) TransformForLegacyNw(ipv6 bool) {
227
220
}
228
221
rc .nameServers = filtered
229
222
if len (rc .nameServers ) == 0 {
230
- log .G (context .TODO ()).Info ("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers" )
223
+ bklog .G (context .TODO ()).Info ("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers" )
231
224
rc .nameServers = defaultNSAddrs (ipv6 )
232
225
rc .md .Warnings = append (rc .md .Warnings , "Used default nameservers." )
233
226
}
@@ -283,145 +276,123 @@ func (rc *ResolvConf) TransformForIntNS(
283
276
if len (rc .md .ExtNameServers ) == 0 {
284
277
rc .md .Warnings = append (rc .md .Warnings , "NO EXTERNAL NAMESERVERS DEFINED" )
285
278
}
286
- return append ([] ExtDNSEntry ( nil ), rc .md .ExtNameServers ... ), nil
279
+ return slices . Clone ( rc .md .ExtNameServers ), nil
287
280
}
288
281
289
282
// Generate returns content suitable for writing to a resolv.conf file. If comments
290
283
// is true, the file will include header information if supplied, and a trailing
291
284
// comment that describes how the file was constructed and lists external resolvers.
292
285
func (rc * ResolvConf ) Generate (comments bool ) ([]byte , error ) {
293
- s := struct {
294
- Md * metadata
295
- NameServers []netip.Addr
296
- Search []string
297
- Options []string
298
- Other []string
299
- Overrides []string
300
- Comments bool
301
- }{
302
- Md : & rc .md ,
303
- NameServers : rc .nameServers ,
304
- Search : rc .search ,
305
- Options : rc .options ,
306
- Other : rc .other ,
307
- Comments : comments ,
308
- }
309
- if rc .md .NSOverride {
310
- s .Overrides = append (s .Overrides , "nameservers" )
311
- }
312
- if rc .md .SearchOverride {
313
- s .Overrides = append (s .Overrides , "search" )
314
- }
315
- if rc .md .OptionsOverride {
316
- s .Overrides = append (s .Overrides , "options" )
317
- }
318
-
319
- const templateText = `{{if .Comments}}{{with .Md.Header}}{{.}}
320
-
321
- {{end}}{{end}}{{range .NameServers -}}
322
- nameserver {{.}}
323
- {{end}}{{with .Search -}}
324
- search {{join . " "}}
325
- {{end}}{{with .Options -}}
326
- options {{join . " "}}
327
- {{end}}{{with .Other -}}
328
- {{join . "\n"}}
329
- {{end}}{{if .Comments}}
330
- # Based on host file: '{{.Md.SourcePath}}'{{with .Md.Transform}} ({{.}}){{end}}
331
- {{range .Md.Warnings -}}
332
- # {{.}}
333
- {{end -}}
334
- {{with .Md.ExtNameServers -}}
335
- # ExtServers: {{.}}
336
- {{end -}}
337
- {{with .Md.InvalidNSs -}}
338
- # Invalid nameservers: {{.}}
339
- {{end -}}
340
- # Overrides: {{.Overrides}}
341
- {{with .Md.NDotsFrom -}}
342
- # Option ndots from: {{.}}
343
- {{end -}}
344
- {{end -}}
345
- `
346
-
347
- funcs := template.FuncMap {"join" : strings .Join }
348
- var buf bytes.Buffer
349
- templ , err := template .New ("summary" ).Funcs (funcs ).Parse (templateText )
350
- if err != nil {
351
- return nil , errSystem {err }
286
+ var b bytes.Buffer
287
+ b .Grow (512 ) // estimated size for a regular resolv.conf we produce.
288
+
289
+ if comments && rc .md .Header != "" {
290
+ b .WriteString (rc .md .Header + "\n " )
291
+ b .WriteByte ('\n' )
292
+ }
293
+ for _ , ns := range rc .nameServers {
294
+ b .WriteString ("nameserver " )
295
+ b .WriteString (ns .String ())
296
+ b .WriteByte ('\n' )
297
+ }
298
+ if len (rc .search ) > 0 {
299
+ b .WriteString ("search " )
300
+ for i , s := range rc .search {
301
+ if i > 0 {
302
+ b .WriteByte (' ' )
303
+ }
304
+ b .WriteString (s )
305
+ }
306
+ b .WriteByte ('\n' )
352
307
}
353
- if err := templ .Execute (& buf , s ); err != nil {
354
- return nil , errSystem {err }
308
+ if len (rc .options ) > 0 {
309
+ b .WriteString ("options " )
310
+ for i , s := range rc .options {
311
+ if i > 0 {
312
+ b .WriteByte (' ' )
313
+ }
314
+ b .WriteString (s )
315
+ }
316
+ b .WriteByte ('\n' )
355
317
}
356
- return buf .Bytes (), nil
357
- }
358
-
359
- // WriteFile generates content and writes it to path. If hashPath is non-zero, it
360
- // also writes a file containing a hash of the content, to enable UserModified()
361
- // to determine whether the file has been modified.
362
- func (rc * ResolvConf ) WriteFile (path , hashPath string , perm os.FileMode ) error {
363
- content , err := rc .Generate (true )
364
- if err != nil {
365
- return err
318
+ for _ , o := range rc .other {
319
+ b .WriteString (o )
320
+ b .WriteByte ('\n' )
366
321
}
367
322
368
- // Write the resolv.conf file - it's bind-mounted into the container, so can't
369
- // move a temp file into place, just have to truncate and write it.
370
- if err := os .WriteFile (path , content , perm ); err != nil {
371
- return errSystem {err }
372
- }
323
+ if comments {
324
+ b .WriteByte ('\n' )
325
+ b .WriteString ("# Based on host file: '" + rc .md .SourcePath + "'" )
326
+ if rc .md .Transform != "" {
327
+ b .WriteString (" (" + rc .md .Transform + ")" )
328
+ }
329
+ b .WriteByte ('\n' )
330
+ for _ , w := range rc .md .Warnings {
331
+ b .WriteString ("# " )
332
+ b .WriteString (w )
333
+ b .WriteByte ('\n' )
334
+ }
335
+ if len (rc .md .ExtNameServers ) > 0 {
336
+ b .WriteString ("# ExtServers: [" )
337
+ for i , ext := range rc .md .ExtNameServers {
338
+ if i > 0 {
339
+ b .WriteByte (' ' )
340
+ }
341
+ b .WriteString (ext .String ())
342
+ }
343
+ b .WriteByte (']' )
344
+ b .WriteByte ('\n' )
345
+ }
346
+ if len (rc .md .InvalidNSs ) > 0 {
347
+ b .WriteString ("# Invalid nameservers: [" )
348
+ for i , ext := range rc .md .InvalidNSs {
349
+ if i > 0 {
350
+ b .WriteByte (' ' )
351
+ }
352
+ b .WriteString (ext )
353
+ }
354
+ b .WriteByte (']' )
355
+ b .WriteByte ('\n' )
356
+ }
373
357
374
- // Write the hash file.
375
- if hashPath != "" {
376
- hashFile , err := atomicwriter .New (hashPath , perm )
377
- if err != nil {
378
- return errSystem {err }
358
+ b .WriteString ("# Overrides: [" )
359
+ var overrides int
360
+ if rc .md .NSOverride {
361
+ b .WriteString ("nameservers" )
362
+ overrides ++
363
+ }
364
+ if rc .md .SearchOverride {
365
+ if overrides > 0 {
366
+ b .WriteByte (' ' )
367
+ }
368
+ b .WriteString ("search" )
369
+ overrides ++
379
370
}
380
- defer hashFile .Close ()
371
+ if rc .md .OptionsOverride {
372
+ if overrides > 0 {
373
+ b .WriteByte (' ' )
374
+ }
375
+ b .WriteString ("options" )
376
+ }
377
+ b .WriteByte (']' )
378
+ b .WriteByte ('\n' )
381
379
382
- if _ , err = hashFile . Write ([] byte ( digest . FromBytes ( content ))); err != nil {
383
- return err
380
+ if rc . md . NDotsFrom != "" {
381
+ b . WriteString ( "# Option ndots from: " + rc . md . NDotsFrom + " \n " )
384
382
}
385
383
}
386
384
387
- return nil
385
+ return b . Bytes (), nil
388
386
}
389
387
390
- // UserModified can be used to determine whether the resolv.conf file has been
391
- // modified since it was generated. It returns false with no error if the file
392
- // matches the hash, true with no error if the file no longer matches the hash,
393
- // and false with an error if the result cannot be determined.
394
- func UserModified (rcPath , rcHashPath string ) (bool , error ) {
395
- currRCHash , err := os .ReadFile (rcHashPath )
396
- if err != nil {
397
- // If the hash file doesn't exist, can only assume it hasn't been written
398
- // yet (so, the user hasn't modified the file it hashes).
399
- if errors .Is (err , fs .ErrNotExist ) {
400
- return false , nil
401
- }
402
- return false , errors .Wrapf (err , "failed to read hash file %s" , rcHashPath )
403
- }
404
- expected , err := digest .Parse (string (currRCHash ))
405
- if err != nil {
406
- return false , errors .Wrapf (err , "failed to parse hash file %s" , rcHashPath )
407
- }
408
- v := expected .Verifier ()
409
- currRC , err := os .Open (rcPath )
410
- if err != nil {
411
- return false , errors .Wrapf (err , "failed to open %s to check for modifications" , rcPath )
412
- }
413
- defer currRC .Close ()
414
- if _ , err := io .Copy (v , currRC ); err != nil {
415
- return false , errors .Wrapf (err , "failed to hash %s to check for modifications" , rcPath )
388
+ func (rc * ResolvConf ) processLine (line string ) {
389
+ // Strip blank lines and comments.
390
+ if line == "" || line [0 ] == '#' || line [0 ] == ';' {
391
+ return
416
392
}
417
- return ! v .Verified (), nil
418
- }
419
393
420
- func (rc * ResolvConf ) processLine (line string ) {
421
394
fields := strings .Fields (line )
422
-
423
- // Strip blank lines and comments.
424
- if len (fields ) == 0 || fields [0 ][0 ] == '#' || fields [0 ][0 ] == ';' {
395
+ if len (fields ) == 0 {
425
396
return
426
397
}
427
398
@@ -470,8 +441,11 @@ func defaultNSAddrs(ipv6 bool) []netip.Addr {
470
441
func removeInvalidNDots (options []string ) []string {
471
442
n := 0
472
443
for _ , opt := range options {
473
- k , v , _ := strings .Cut (opt , ":" )
444
+ k , v , hasSep := strings .Cut (opt , ":" )
474
445
if k == "ndots" {
446
+ if ! hasSep || v == "" {
447
+ continue
448
+ }
475
449
ndots , err := strconv .Atoi (v )
476
450
if err != nil || ndots < 0 {
477
451
continue
@@ -483,16 +457,3 @@ func removeInvalidNDots(options []string) []string {
483
457
clear (options [n :]) // Zero out the obsolete elements, for GC.
484
458
return options [:n ]
485
459
}
486
-
487
- // errSystem implements [github.com/docker/docker/errdefs.ErrSystem].
488
- //
489
- // We don't use the errdefs helpers here, because the resolvconf package
490
- // is imported in BuildKit, and this is the only location that used the
491
- // errdefs package outside of the client.
492
- type errSystem struct { error }
493
-
494
- func (errSystem ) System () {}
495
-
496
- func (e errSystem ) Unwrap () error {
497
- return e .error
498
- }
0 commit comments