go-network-configurator/dns_backends_test.go
2026-07-01 15:17:11 -05:00

145 lines
3.9 KiB
Go

package netconfig
import (
"context"
"net"
"os"
"path/filepath"
"testing"
)
// readIfcfg parses a written ifcfg-<iface> file back into a key/value map so
// tests can assert on individual directives (PEERDNS, DNS1, ...).
func readIfcfg(t *testing.T, dir, iface string) map[string]string {
t.Helper()
data, err := os.ReadFile(filepath.Join(dir, "ifcfg-"+iface))
if err != nil {
t.Fatalf("read ifcfg-%s: %v", iface, err)
}
out := make(map[string]string)
for _, line := range splitLines(string(data)) {
k, v, ok := cutKV(line)
if ok {
out[k] = v
}
}
return out
}
func splitLines(s string) []string {
var lines []string
cur := ""
for _, r := range s {
if r == '\n' {
lines = append(lines, cur)
cur = ""
continue
}
cur += string(r)
}
if cur != "" {
lines = append(lines, cur)
}
return lines
}
func cutKV(line string) (string, string, bool) {
for i := 0; i < len(line); i++ {
if line[i] == '=' {
v := line[i+1:]
// Strip surrounding quotes written for values with whitespace.
if len(v) >= 2 && v[0] == '"' && v[len(v)-1] == '"' {
v = v[1 : len(v)-1]
}
return line[:i], v, true
}
}
return "", "", false
}
// TestNetworkScriptsDNSPeerDNS verifies that setting DNS on a network-scripts
// interface writes PEERDNS=no (so dhclient does not override the static
// resolvers) and that clearing DNS removes PEERDNS again.
func TestNetworkScriptsDNSPeerDNS(t *testing.T) {
dir := t.TempDir()
ns := &networkScripts{ConfigDir: dir}
// Set DNS servers.
err := ns.SetIfaceDNS(context.Background(), "eth0",
[]net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("8.8.8.8")},
[]string{"example.com"})
if err != nil {
t.Fatalf("SetIfaceDNS: %v", err)
}
cfg := readIfcfg(t, dir, "eth0")
if cfg["PEERDNS"] != "no" {
t.Errorf("PEERDNS = %q, want \"no\"", cfg["PEERDNS"])
}
if cfg["DNS1"] != "1.1.1.1" || cfg["DNS2"] != "8.8.8.8" {
t.Errorf("DNS1/DNS2 = %q/%q, want 1.1.1.1/8.8.8.8", cfg["DNS1"], cfg["DNS2"])
}
if cfg["DOMAIN"] != "example.com" {
t.Errorf("DOMAIN = %q, want example.com", cfg["DOMAIN"])
}
// Clearing DNS should drop PEERDNS so DHCP-provided resolvers resume.
err = ns.SetIfaceDNS(context.Background(), "eth0", nil, nil)
if err != nil {
t.Fatalf("SetIfaceDNS clear: %v", err)
}
cfg = readIfcfg(t, dir, "eth0")
if _, ok := cfg["PEERDNS"]; ok {
t.Errorf("PEERDNS still present after clear: %q", cfg["PEERDNS"])
}
if _, ok := cfg["DNS1"]; ok {
t.Errorf("DNS1 still present after clear: %q", cfg["DNS1"])
}
}
// TestIfUpDownDNSEnsuresInterface verifies that SetIfaceDNS creates the
// interface block when it has not been seen before, rather than silently
// no-opping and reporting success with nothing persisted.
func TestIfUpDownDNSEnsuresInterface(t *testing.T) {
dir := t.TempDir()
cfgPath := filepath.Join(dir, "interfaces")
if err := os.WriteFile(cfgPath, []byte("auto lo\niface lo inet loopback\n"), 0644); err != nil {
t.Fatal(err)
}
i := &ifUpDown{BaseConfig: cfgPath, BackupDir: cfgPath + ".backup"}
if err := i.ReadFile(cfgPath); err != nil {
t.Fatal(err)
}
// eth0 is not present in the file; SetIfaceDNS must create it.
err := i.SetIfaceDNS(context.Background(), "eth0",
[]net.IP{net.ParseIP("1.1.1.1")}, []string{"example.com"})
if err != nil {
t.Fatalf("SetIfaceDNS: %v", err)
}
// Re-read from disk and confirm the stanzas were persisted.
i.Interfaces = nil
if err := i.ReadFile(cfgPath); err != nil {
t.Fatal(err)
}
interfaces, err := i.GetInterfaces()
if err != nil {
t.Fatal(err)
}
var found *Interface
for _, iface := range interfaces {
if iface.Name == "eth0" {
found = iface
break
}
}
if found == nil {
t.Fatal("eth0 was not persisted; SetIfaceDNS silently no-opped")
}
if len(found.DNS) != 1 || !found.DNS[0].Equal(net.ParseIP("1.1.1.1")) {
t.Errorf("DNS = %v, want [1.1.1.1]", found.DNS)
}
if len(found.SearchDomains) != 1 || found.SearchDomains[0] != "example.com" {
t.Errorf("SearchDomains = %v, want [example.com]", found.SearchDomains)
}
}