Bring an administratively down interface up in AddAddress before writing its addresses and routes. The kernel installs an address's connected route only while its link is up, so adding a gateway to a down interface was rejected as unreachable. A link raised this way is part of the pre-change state AddAddress restores, so it is returned to down on any path that does not complete. Also fix the rollback masking its own error: deleting the address it added takes the default route with it, since that route resolved its nexthop through the address's connected subnet. The following RouteDel then answered ESRCH and was reported in place of "aborted operation due to loss of internet".
121 lines
3.5 KiB
Go
121 lines
3.5 KiB
Go
package netconfig
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// 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))
|
|
require.NoError(t, 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"})
|
|
require.NoError(t, err)
|
|
cfg := readIfcfg(t, dir, "eth0")
|
|
assert.Equal(t, "no", cfg["PEERDNS"])
|
|
assert.Equal(t, "1.1.1.1", cfg["DNS1"])
|
|
assert.Equal(t, "8.8.8.8", cfg["DNS2"])
|
|
assert.Equal(t, "example.com", cfg["DOMAIN"])
|
|
|
|
// Clearing DNS should drop PEERDNS so DHCP-provided resolvers resume.
|
|
err = ns.SetIfaceDNS(context.Background(), "eth0", nil, nil)
|
|
require.NoError(t, err)
|
|
cfg = readIfcfg(t, dir, "eth0")
|
|
_, ok := cfg["PEERDNS"]
|
|
assert.False(t, ok)
|
|
_, ok = cfg["DNS1"]
|
|
assert.False(t, ok)
|
|
}
|
|
|
|
// 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")
|
|
err := os.WriteFile(cfgPath, []byte("auto lo\niface lo inet loopback\n"), 0644)
|
|
require.NoError(t, err)
|
|
i := &ifUpDown{BaseConfig: cfgPath, BackupDir: cfgPath + ".backup"}
|
|
require.NoError(t, i.ReadFile(cfgPath))
|
|
|
|
// 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"})
|
|
require.NoError(t, err)
|
|
|
|
// Re-read from disk and confirm the stanzas were persisted.
|
|
i.Interfaces = nil
|
|
require.NoError(t, i.ReadFile(cfgPath))
|
|
interfaces, err := i.GetInterfaces()
|
|
require.NoError(t, err)
|
|
var found *Interface
|
|
for _, iface := range interfaces {
|
|
if iface.Name == "eth0" {
|
|
found = iface
|
|
break
|
|
}
|
|
}
|
|
require.NotNil(t, found, "eth0 was not persisted; SetIfaceDNS silently no-opped")
|
|
require.Len(t, found.DNS, 1)
|
|
assert.True(t, found.DNS[0].Equal(net.ParseIP("1.1.1.1")), "DNS = %v, want [1.1.1.1]", found.DNS)
|
|
assert.Equal(t, []string{"example.com"}, found.SearchDomains)
|
|
}
|