github.com/khulnasoft-lab/khulnasoft@v26.0.1-0.20240328202558-330a6f959fe0+incompatible/libnetwork/internal/resolvconf/resolvconf_test.go (about)

     1  package resolvconf
     2  
     3  import (
     4  	"bytes"
     5  	"io/fs"
     6  	"net/netip"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/docker/docker/internal/sliceutil"
    14  	"github.com/google/go-cmp/cmp/cmpopts"
    15  	"gotest.tools/v3/assert"
    16  	is "gotest.tools/v3/assert/cmp"
    17  	"gotest.tools/v3/golden"
    18  )
    19  
    20  func TestRCOption(t *testing.T) {
    21  	testcases := []struct {
    22  		name     string
    23  		options  string
    24  		search   string
    25  		expFound bool
    26  		expValue string
    27  	}{
    28  		{
    29  			name:    "Empty options",
    30  			options: "",
    31  			search:  "ndots",
    32  		},
    33  		{
    34  			name:    "Not found",
    35  			options: "ndots:0 edns0",
    36  			search:  "trust-ad",
    37  		},
    38  		{
    39  			name:     "Found with value",
    40  			options:  "ndots:0 edns0",
    41  			search:   "ndots",
    42  			expFound: true,
    43  			expValue: "0",
    44  		},
    45  		{
    46  			name:     "Found without value",
    47  			options:  "ndots:0 edns0",
    48  			search:   "edns0",
    49  			expFound: true,
    50  			expValue: "",
    51  		},
    52  		{
    53  			name:     "Found last value",
    54  			options:  "ndots:0 edns0 ndots:1",
    55  			search:   "ndots",
    56  			expFound: true,
    57  			expValue: "1",
    58  		},
    59  	}
    60  
    61  	for _, tc := range testcases {
    62  		t.Run(tc.name, func(t *testing.T) {
    63  			rc, err := Parse(bytes.NewBuffer([]byte("options "+tc.options)), "")
    64  			assert.NilError(t, err)
    65  			value, found := rc.Option(tc.search)
    66  			assert.Check(t, is.Equal(found, tc.expFound))
    67  			assert.Check(t, is.Equal(value, tc.expValue))
    68  		})
    69  	}
    70  }
    71  
    72  func TestRCWrite(t *testing.T) {
    73  	testcases := []struct {
    74  		name            string
    75  		fileName        string
    76  		perm            os.FileMode
    77  		hashFileName    string
    78  		modify          bool
    79  		expUserModified bool
    80  	}{
    81  		{
    82  			name:         "Write with hash",
    83  			fileName:     "testfile",
    84  			hashFileName: "testfile.hash",
    85  		},
    86  		{
    87  			name:            "Write with hash and modify",
    88  			fileName:        "testfile",
    89  			hashFileName:    "testfile.hash",
    90  			modify:          true,
    91  			expUserModified: true,
    92  		},
    93  		{
    94  			name:            "Write without hash and modify",
    95  			fileName:        "testfile",
    96  			modify:          true,
    97  			expUserModified: false,
    98  		},
    99  		{
   100  			name:     "Write perm",
   101  			fileName: "testfile",
   102  			perm:     0640,
   103  		},
   104  	}
   105  
   106  	rc, err := Parse(bytes.NewBuffer([]byte("nameserver 1.2.3.4")), "")
   107  	assert.NilError(t, err)
   108  
   109  	for _, tc := range testcases {
   110  		t.Run(tc.name, func(t *testing.T) {
   111  			tc := tc
   112  			d := t.TempDir()
   113  			path := filepath.Join(d, tc.fileName)
   114  			var hashPath string
   115  			if tc.hashFileName != "" {
   116  				hashPath = filepath.Join(d, tc.hashFileName)
   117  			}
   118  			if tc.perm == 0 {
   119  				tc.perm = 0644
   120  			}
   121  			err := rc.WriteFile(path, hashPath, tc.perm)
   122  			assert.NilError(t, err)
   123  
   124  			fi, err := os.Stat(path)
   125  			assert.NilError(t, err)
   126  			// Windows files won't have the expected perms.
   127  			if runtime.GOOS != "windows" {
   128  				assert.Check(t, is.Equal(fi.Mode(), tc.perm))
   129  			}
   130  
   131  			if tc.modify {
   132  				err := os.WriteFile(path, []byte("modified"), 0644)
   133  				assert.NilError(t, err)
   134  			}
   135  
   136  			um, err := UserModified(path, hashPath)
   137  			assert.NilError(t, err)
   138  			assert.Check(t, is.Equal(um, tc.expUserModified))
   139  		})
   140  	}
   141  }
   142  
   143  var a2s = sliceutil.Mapper(netip.Addr.String)
   144  var s2a = sliceutil.Mapper(netip.MustParseAddr)
   145  
   146  // Test that a resolv.conf file can be modified using OverrideXXX() methods
   147  // to modify nameservers/search/options directives, and tha options can be
   148  // added via AddOption().
   149  func TestRCModify(t *testing.T) {
   150  	testcases := []struct {
   151  		name            string
   152  		inputNS         []string
   153  		inputSearch     []string
   154  		inputOptions    []string
   155  		noOverrides     bool // Whether to apply overrides (empty lists are valid overrides).
   156  		overrideNS      []string
   157  		overrideSearch  []string
   158  		overrideOptions []string
   159  		addOption       string
   160  	}{
   161  		{
   162  			name:    "No content no overrides",
   163  			inputNS: []string{},
   164  		},
   165  		{
   166  			name:         "No overrides",
   167  			noOverrides:  true,
   168  			inputNS:      []string{"1.2.3.4"},
   169  			inputSearch:  []string{"invalid"},
   170  			inputOptions: []string{"ndots:0"},
   171  		},
   172  		{
   173  			name:         "Empty overrides",
   174  			inputNS:      []string{"1.2.3.4"},
   175  			inputSearch:  []string{"invalid"},
   176  			inputOptions: []string{"ndots:0"},
   177  		},
   178  		{
   179  			name:            "Overrides",
   180  			inputNS:         []string{"1.2.3.4"},
   181  			inputSearch:     []string{"invalid"},
   182  			inputOptions:    []string{"ndots:0"},
   183  			overrideNS:      []string{"2.3.4.5", "fdba:acdd:587c::53"},
   184  			overrideSearch:  []string{"com", "invalid", "example"},
   185  			overrideOptions: []string{"ndots:1", "edns0", "trust-ad"},
   186  		},
   187  		{
   188  			name:         "Add option no overrides",
   189  			noOverrides:  true,
   190  			inputNS:      []string{"1.2.3.4"},
   191  			inputSearch:  []string{"invalid"},
   192  			inputOptions: []string{"ndots:0"},
   193  			addOption:    "attempts:3",
   194  		},
   195  	}
   196  
   197  	for _, tc := range testcases {
   198  		t.Run(tc.name, func(t *testing.T) {
   199  			tc := tc
   200  			var input string
   201  			if len(tc.inputNS) != 0 {
   202  				for _, ns := range tc.inputNS {
   203  					input += "nameserver " + ns + "\n"
   204  				}
   205  			}
   206  			if len(tc.inputSearch) != 0 {
   207  				input += "search " + strings.Join(tc.inputSearch, " ") + "\n"
   208  			}
   209  			if len(tc.inputOptions) != 0 {
   210  				input += "options " + strings.Join(tc.inputOptions, " ") + "\n"
   211  			}
   212  			rc, err := Parse(bytes.NewBuffer([]byte(input)), "")
   213  			assert.NilError(t, err)
   214  			assert.Check(t, is.DeepEqual(a2s(rc.NameServers()), tc.inputNS))
   215  			assert.Check(t, is.DeepEqual(rc.Search(), tc.inputSearch))
   216  			assert.Check(t, is.DeepEqual(rc.Options(), tc.inputOptions))
   217  
   218  			if !tc.noOverrides {
   219  				overrideNS := s2a(tc.overrideNS)
   220  				rc.OverrideNameServers(overrideNS)
   221  				rc.OverrideSearch(tc.overrideSearch)
   222  				rc.OverrideOptions(tc.overrideOptions)
   223  
   224  				assert.Check(t, is.DeepEqual(rc.NameServers(), overrideNS, cmpopts.EquateEmpty(), cmpopts.EquateComparable(netip.Addr{})))
   225  				assert.Check(t, is.DeepEqual(rc.Search(), tc.overrideSearch, cmpopts.EquateEmpty()))
   226  				assert.Check(t, is.DeepEqual(rc.Options(), tc.overrideOptions, cmpopts.EquateEmpty()))
   227  			}
   228  
   229  			if tc.addOption != "" {
   230  				options := rc.Options()
   231  				rc.AddOption(tc.addOption)
   232  				assert.Check(t, is.DeepEqual(rc.Options(), append(options, tc.addOption), cmpopts.EquateEmpty()))
   233  			}
   234  
   235  			d := t.TempDir()
   236  			path := filepath.Join(d, "resolv.conf")
   237  			err = rc.WriteFile(path, "", 0644)
   238  			assert.NilError(t, err)
   239  
   240  			content, err := os.ReadFile(path)
   241  			assert.NilError(t, err)
   242  			assert.Check(t, golden.String(string(content), t.Name()+".golden"))
   243  		})
   244  	}
   245  }
   246  
   247  func TestRCTransformForLegacyNw(t *testing.T) {
   248  	testcases := []struct {
   249  		name       string
   250  		input      string
   251  		ipv6       bool
   252  		overrideNS []string
   253  	}{
   254  		{
   255  			name:  "Routable IPv4 only",
   256  			input: "nameserver 10.0.0.1",
   257  		},
   258  		{
   259  			name:  "Routable IPv4 and IPv6, ipv6 enabled",
   260  			input: "nameserver 10.0.0.1\nnameserver fdb6:b8fe:b528::1",
   261  			ipv6:  true,
   262  		},
   263  		{
   264  			name:  "Routable IPv4 and IPv6, ipv6 disabled",
   265  			input: "nameserver 10.0.0.1\nnameserver fdb6:b8fe:b528::1",
   266  			ipv6:  false,
   267  		},
   268  		{
   269  			name:  "IPv4 localhost, ipv6 disabled",
   270  			input: "nameserver 127.0.0.53",
   271  			ipv6:  false,
   272  		},
   273  		{
   274  			name:  "IPv4 localhost, ipv6 enabled",
   275  			input: "nameserver 127.0.0.53",
   276  			ipv6:  true,
   277  		},
   278  		{
   279  			name:  "IPv4 and IPv6 localhost, ipv6 disabled",
   280  			input: "nameserver 127.0.0.53\nnameserver ::1",
   281  			ipv6:  false,
   282  		},
   283  		{
   284  			name:  "IPv4 and IPv6 localhost, ipv6 enabled",
   285  			input: "nameserver 127.0.0.53\nnameserver ::1",
   286  			ipv6:  true,
   287  		},
   288  		{
   289  			name:  "IPv4 localhost, IPv6 routeable, ipv6 enabled",
   290  			input: "nameserver 127.0.0.53\nnameserver fd3e:2d1a:1f5a::1",
   291  			ipv6:  true,
   292  		},
   293  		{
   294  			name:  "IPv4 localhost, IPv6 routeable, ipv6 disabled",
   295  			input: "nameserver 127.0.0.53\nnameserver fd3e:2d1a:1f5a::1",
   296  			ipv6:  false,
   297  		},
   298  		{
   299  			name:       "Override nameservers",
   300  			input:      "nameserver 127.0.0.53",
   301  			overrideNS: []string{"127.0.0.1", "::1"},
   302  			ipv6:       false,
   303  		},
   304  	}
   305  
   306  	for _, tc := range testcases {
   307  		t.Run(tc.name, func(t *testing.T) {
   308  			tc := tc
   309  			rc, err := Parse(bytes.NewBuffer([]byte(tc.input)), "/etc/resolv.conf")
   310  			assert.NilError(t, err)
   311  			if tc.overrideNS != nil {
   312  				rc.OverrideNameServers(s2a(tc.overrideNS))
   313  			}
   314  
   315  			rc.TransformForLegacyNw(tc.ipv6)
   316  
   317  			d := t.TempDir()
   318  			path := filepath.Join(d, "resolv.conf")
   319  			err = rc.WriteFile(path, "", 0644)
   320  			assert.NilError(t, err)
   321  
   322  			content, err := os.ReadFile(path)
   323  			assert.NilError(t, err)
   324  			assert.Check(t, golden.String(string(content), t.Name()+".golden"))
   325  		})
   326  	}
   327  }
   328  
   329  func TestRCTransformForIntNS(t *testing.T) {
   330  	mke := func(addr string, hostLoopback bool) ExtDNSEntry {
   331  		return ExtDNSEntry{
   332  			Addr:         netip.MustParseAddr(addr),
   333  			HostLoopback: hostLoopback,
   334  		}
   335  	}
   336  
   337  	testcases := []struct {
   338  		name            string
   339  		input           string
   340  		intNameServer   string
   341  		ipv6            bool
   342  		overrideNS      []string
   343  		overrideOptions []string
   344  		reqdOptions     []string
   345  		expExtServers   []ExtDNSEntry
   346  		expErr          string
   347  	}{
   348  		{
   349  			name:          "IPv4 only",
   350  			input:         "nameserver 10.0.0.1",
   351  			expExtServers: []ExtDNSEntry{mke("10.0.0.1", false)},
   352  		},
   353  		{
   354  			name:  "IPv4 and IPv6, ipv6 enabled",
   355  			input: "nameserver 10.0.0.1\nnameserver fdb6:b8fe:b528::1",
   356  			ipv6:  true,
   357  			expExtServers: []ExtDNSEntry{
   358  				mke("10.0.0.1", false),
   359  				mke("fdb6:b8fe:b528::1", false),
   360  			},
   361  		},
   362  		{
   363  			name:  "IPv4 and IPv6, ipv6 disabled",
   364  			input: "nameserver 10.0.0.1\nnameserver fdb6:b8fe:b528::1",
   365  			ipv6:  false,
   366  			expExtServers: []ExtDNSEntry{
   367  				mke("10.0.0.1", false),
   368  				mke("fdb6:b8fe:b528::1", true),
   369  			},
   370  		},
   371  		{
   372  			name:          "IPv4 localhost",
   373  			input:         "nameserver 127.0.0.53",
   374  			ipv6:          false,
   375  			expExtServers: []ExtDNSEntry{mke("127.0.0.53", true)},
   376  		},
   377  		{
   378  			// Overriding the nameserver with a localhost address means use the container's
   379  			// loopback interface, not the host's.
   380  			name:          "IPv4 localhost override",
   381  			input:         "nameserver 10.0.0.1",
   382  			ipv6:          false,
   383  			overrideNS:    []string{"127.0.0.53"},
   384  			expExtServers: []ExtDNSEntry{mke("127.0.0.53", false)},
   385  		},
   386  		{
   387  			name:          "IPv4 localhost, ipv6 enabled",
   388  			input:         "nameserver 127.0.0.53",
   389  			ipv6:          true,
   390  			expExtServers: []ExtDNSEntry{mke("127.0.0.53", true)},
   391  		},
   392  		{
   393  			name:          "IPv6 addr, IPv6 enabled",
   394  			input:         "nameserver fd14:6e0e:f855::1",
   395  			ipv6:          true,
   396  			expExtServers: []ExtDNSEntry{mke("fd14:6e0e:f855::1", false)},
   397  		},
   398  		{
   399  			name:  "IPv4 and IPv6 localhost, IPv6 disabled",
   400  			input: "nameserver 127.0.0.53\nnameserver ::1",
   401  			ipv6:  false,
   402  			expExtServers: []ExtDNSEntry{
   403  				mke("127.0.0.53", true),
   404  				mke("::1", true),
   405  			},
   406  		},
   407  		{
   408  			name:  "IPv4 and IPv6 localhost, ipv6 enabled",
   409  			input: "nameserver 127.0.0.53\nnameserver ::1",
   410  			ipv6:  true,
   411  			expExtServers: []ExtDNSEntry{
   412  				mke("127.0.0.53", true),
   413  				mke("::1", true),
   414  			},
   415  		},
   416  		{
   417  			name:  "IPv4 localhost, IPv6 private, IPv6 enabled",
   418  			input: "nameserver 127.0.0.53\nnameserver fd3e:2d1a:1f5a::1",
   419  			ipv6:  true,
   420  			expExtServers: []ExtDNSEntry{
   421  				mke("127.0.0.53", true),
   422  				mke("fd3e:2d1a:1f5a::1", false),
   423  			},
   424  		},
   425  		{
   426  			name:  "IPv4 localhost, IPv6 private, IPv6 disabled",
   427  			input: "nameserver 127.0.0.53\nnameserver fd3e:2d1a:1f5a::1",
   428  			ipv6:  false,
   429  			expExtServers: []ExtDNSEntry{
   430  				mke("127.0.0.53", true),
   431  				mke("fd3e:2d1a:1f5a::1", true),
   432  			},
   433  		},
   434  		{
   435  			name:  "No host nameserver, no iv6",
   436  			input: "",
   437  			ipv6:  false,
   438  			expExtServers: []ExtDNSEntry{
   439  				mke("8.8.8.8", false),
   440  				mke("8.8.4.4", false),
   441  			},
   442  		},
   443  		{
   444  			name:  "No host nameserver, iv6",
   445  			input: "",
   446  			ipv6:  true,
   447  			expExtServers: []ExtDNSEntry{
   448  				mke("8.8.8.8", false),
   449  				mke("8.8.4.4", false),
   450  				mke("2001:4860:4860::8888", false),
   451  				mke("2001:4860:4860::8844", false),
   452  			},
   453  		},
   454  		{
   455  			name:          "ndots present and required",
   456  			input:         "nameserver 127.0.0.53\noptions ndots:1",
   457  			reqdOptions:   []string{"ndots:0"},
   458  			expExtServers: []ExtDNSEntry{mke("127.0.0.53", true)},
   459  		},
   460  		{
   461  			name:          "ndots missing but required",
   462  			input:         "nameserver 127.0.0.53",
   463  			reqdOptions:   []string{"ndots:0"},
   464  			expExtServers: []ExtDNSEntry{mke("127.0.0.53", true)},
   465  		},
   466  		{
   467  			name:            "ndots host, override and required",
   468  			input:           "nameserver 127.0.0.53",
   469  			reqdOptions:     []string{"ndots:0"},
   470  			overrideOptions: []string{"ndots:2"},
   471  			expExtServers:   []ExtDNSEntry{mke("127.0.0.53", true)},
   472  		},
   473  		{
   474  			name:          "Extra required options",
   475  			input:         "nameserver 127.0.0.53\noptions trust-ad",
   476  			reqdOptions:   []string{"ndots:0", "attempts:3", "edns0", "trust-ad"},
   477  			expExtServers: []ExtDNSEntry{mke("127.0.0.53", true)},
   478  		},
   479  	}
   480  
   481  	for _, tc := range testcases {
   482  		t.Run(tc.name, func(t *testing.T) {
   483  			tc := tc
   484  			rc, err := Parse(bytes.NewBuffer([]byte(tc.input)), "/etc/resolv.conf")
   485  			assert.NilError(t, err)
   486  
   487  			if tc.intNameServer == "" {
   488  				tc.intNameServer = "127.0.0.11"
   489  			}
   490  			if len(tc.overrideNS) > 0 {
   491  				rc.OverrideNameServers(s2a(tc.overrideNS))
   492  			}
   493  			if len(tc.overrideOptions) > 0 {
   494  				rc.OverrideOptions(tc.overrideOptions)
   495  			}
   496  			intNS := netip.MustParseAddr(tc.intNameServer)
   497  			extNameServers, err := rc.TransformForIntNS(tc.ipv6, intNS, tc.reqdOptions)
   498  			if tc.expErr != "" {
   499  				assert.Check(t, is.ErrorContains(err, tc.expErr))
   500  				return
   501  			}
   502  			assert.NilError(t, err)
   503  
   504  			d := t.TempDir()
   505  			path := filepath.Join(d, "resolv.conf")
   506  			err = rc.WriteFile(path, "", 0644)
   507  			assert.NilError(t, err)
   508  
   509  			content, err := os.ReadFile(path)
   510  			assert.NilError(t, err)
   511  			assert.Check(t, golden.String(string(content), t.Name()+".golden"))
   512  			assert.Check(t, is.DeepEqual(extNameServers, tc.expExtServers,
   513  				cmpopts.EquateComparable(netip.Addr{})))
   514  		})
   515  	}
   516  }
   517  
   518  // Check that invalid ndots options in the host's file are ignored, unless
   519  // starting the internal resolver (which requires an ndots option), in which
   520  // case invalid ndots should be replaced.
   521  func TestRCTransformForIntNSInvalidNdots(t *testing.T) {
   522  	testcases := []struct {
   523  		name         string
   524  		options      string
   525  		reqdOptions  []string
   526  		expVal       string
   527  		expOptions   []string
   528  		expNDotsFrom string
   529  	}{
   530  		{
   531  			name:         "Negative value",
   532  			options:      "options ndots:-1",
   533  			expOptions:   []string{"ndots:-1"},
   534  			expVal:       "-1",
   535  			expNDotsFrom: "host",
   536  		},
   537  		{
   538  			name:         "Invalid values with reqd ndots",
   539  			options:      "options ndots:-1 foo:bar ndots ndots:",
   540  			reqdOptions:  []string{"ndots:2"},
   541  			expVal:       "2",
   542  			expNDotsFrom: "internal",
   543  			expOptions:   []string{"foo:bar", "ndots:2"},
   544  		},
   545  		{
   546  			name:         "Valid value with reqd ndots",
   547  			options:      "options ndots:1 foo:bar ndots ndots:",
   548  			reqdOptions:  []string{"ndots:2"},
   549  			expVal:       "1",
   550  			expNDotsFrom: "host",
   551  			expOptions:   []string{"ndots:1", "foo:bar"},
   552  		},
   553  	}
   554  
   555  	for _, tc := range testcases {
   556  		t.Run(tc.name, func(t *testing.T) {
   557  			content := "nameserver 8.8.8.8\n" + tc.options
   558  			rc, err := Parse(bytes.NewBuffer([]byte(content)), "/etc/resolv.conf")
   559  			assert.NilError(t, err)
   560  			_, err = rc.TransformForIntNS(false, netip.MustParseAddr("127.0.0.11"), tc.reqdOptions)
   561  			assert.NilError(t, err)
   562  
   563  			val, found := rc.Option("ndots")
   564  			assert.Check(t, is.Equal(found, true))
   565  			assert.Check(t, is.Equal(val, tc.expVal))
   566  			assert.Check(t, is.Equal(rc.md.NDotsFrom, tc.expNDotsFrom))
   567  			assert.Check(t, is.DeepEqual(rc.options, tc.expOptions))
   568  		})
   569  	}
   570  }
   571  
   572  func TestRCRead(t *testing.T) {
   573  	d := t.TempDir()
   574  	path := filepath.Join(d, "resolv.conf")
   575  
   576  	// Try to read a nonexistent file, equivalent to an empty file.
   577  	_, err := Load(path)
   578  	assert.Check(t, is.ErrorIs(err, fs.ErrNotExist))
   579  
   580  	err = os.WriteFile(path, []byte("options edns0"), 0644)
   581  	assert.NilError(t, err)
   582  
   583  	// Read that file in the constructor.
   584  	rc, err := Load(path)
   585  	assert.NilError(t, err)
   586  	assert.Check(t, is.DeepEqual(rc.Options(), []string{"edns0"}))
   587  
   588  	// Pass in an os.File, check the path is extracted.
   589  	file, err := os.Open(path)
   590  	assert.NilError(t, err)
   591  	defer file.Close()
   592  	rc, err = Parse(file, "")
   593  	assert.NilError(t, err)
   594  	assert.Check(t, is.Equal(rc.md.SourcePath, path))
   595  }
   596  
   597  func TestRCInvalidNS(t *testing.T) {
   598  	d := t.TempDir()
   599  
   600  	// A resolv.conf with an invalid nameserver address.
   601  	rc, err := Parse(bytes.NewBuffer([]byte("nameserver 1.2.3.4.5")), "")
   602  	assert.NilError(t, err)
   603  
   604  	path := filepath.Join(d, "resolv.conf")
   605  	err = rc.WriteFile(path, "", 0644)
   606  	assert.NilError(t, err)
   607  
   608  	content, err := os.ReadFile(path)
   609  	assert.NilError(t, err)
   610  	assert.Check(t, golden.String(string(content), t.Name()+".golden"))
   611  }
   612  
   613  func TestRCSetHeader(t *testing.T) {
   614  	rc, err := Parse(bytes.NewBuffer([]byte("nameserver 127.0.0.53")), "/etc/resolv.conf")
   615  	assert.NilError(t, err)
   616  
   617  	rc.SetHeader("# This is a comment.")
   618  	d := t.TempDir()
   619  	path := filepath.Join(d, "resolv.conf")
   620  	err = rc.WriteFile(path, "", 0644)
   621  	assert.NilError(t, err)
   622  
   623  	content, err := os.ReadFile(path)
   624  	assert.NilError(t, err)
   625  	assert.Check(t, golden.String(string(content), t.Name()+".golden"))
   626  }
   627  
   628  func TestRCUnknownDirectives(t *testing.T) {
   629  	const input = `
   630  something unexpected
   631  nameserver 127.0.0.53
   632  options ndots:1
   633  unrecognised thing
   634  `
   635  	rc, err := Parse(bytes.NewBuffer([]byte(input)), "/etc/resolv.conf")
   636  	assert.NilError(t, err)
   637  
   638  	d := t.TempDir()
   639  	path := filepath.Join(d, "resolv.conf")
   640  	err = rc.WriteFile(path, "", 0644)
   641  	assert.NilError(t, err)
   642  
   643  	content, err := os.ReadFile(path)
   644  	assert.NilError(t, err)
   645  	assert.Check(t, golden.String(string(content), t.Name()+".golden"))
   646  }