397 lines
10 KiB
Go
397 lines
10 KiB
Go
package networking
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseInterfaces(t *testing.T) {
|
|
// Trimmed real output from `ip -j addr` on a typical host.
|
|
input := `[
|
|
{
|
|
"ifname": "lo",
|
|
"address": "00:00:00:00:00:00",
|
|
"mtu": 65536,
|
|
"operstate": "UNKNOWN",
|
|
"addr_info": [
|
|
{"family": "inet", "local": "127.0.0.1", "prefixlen": 8},
|
|
{"family": "inet6", "local": "::1", "prefixlen": 128}
|
|
]
|
|
},
|
|
{
|
|
"ifname": "eth0",
|
|
"address": "52:54:00:12:34:56",
|
|
"mtu": 1500,
|
|
"operstate": "UP",
|
|
"addr_info": [
|
|
{"family": "inet", "local": "192.168.1.10", "prefixlen": 24},
|
|
{"family": "inet6", "local": "fe80::1", "prefixlen": 64}
|
|
]
|
|
}
|
|
]`
|
|
|
|
ifaces, err := parseInterfaces(input)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(ifaces) != 2 {
|
|
t.Fatalf("expected 2 interfaces, got %d", len(ifaces))
|
|
}
|
|
|
|
lo := ifaces[0]
|
|
if lo.Name != "lo" || lo.State != "unknown" || lo.MTU != 65536 {
|
|
t.Errorf("lo: got %+v", lo)
|
|
}
|
|
if len(lo.IPv4) != 1 || lo.IPv4[0] != "127.0.0.1/8" {
|
|
t.Errorf("lo ipv4: got %v", lo.IPv4)
|
|
}
|
|
|
|
eth0 := ifaces[1]
|
|
if eth0.Name != "eth0" || eth0.State != "up" || eth0.MAC != "52:54:00:12:34:56" {
|
|
t.Errorf("eth0: got %+v", eth0)
|
|
}
|
|
if len(eth0.IPv4) != 1 || eth0.IPv4[0] != "192.168.1.10/24" {
|
|
t.Errorf("eth0 ipv4: got %v", eth0.IPv4)
|
|
}
|
|
if len(eth0.IPv6) != 1 || eth0.IPv6[0] != "fe80::1/64" {
|
|
t.Errorf("eth0 ipv6: got %v", eth0.IPv6)
|
|
}
|
|
}
|
|
|
|
func TestParseRoutes(t *testing.T) {
|
|
input := `[
|
|
{"dst": "default", "gateway": "192.168.1.1", "dev": "eth0", "prefsrc": "192.168.1.10", "metric": 100},
|
|
{"dst": "192.168.1.0/24", "dev": "eth0", "prefsrc": "192.168.1.10"}
|
|
]`
|
|
|
|
routes, err := parseRoutes(input)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 2 {
|
|
t.Fatalf("expected 2 routes, got %d", len(routes))
|
|
}
|
|
if routes[0].Destination != "default" || routes[0].Gateway != "192.168.1.1" || routes[0].Metric != 100 {
|
|
t.Errorf("route 0: got %+v", routes[0])
|
|
}
|
|
if routes[1].Destination != "192.168.1.0/24" || routes[1].Interface != "eth0" {
|
|
t.Errorf("route 1: got %+v", routes[1])
|
|
}
|
|
}
|
|
|
|
func TestParseResolv(t *testing.T) {
|
|
input := `# Generated by NetworkManager
|
|
nameserver 1.1.1.1
|
|
nameserver 8.8.8.8
|
|
; legacy comment
|
|
search example.com
|
|
nameserver 9.9.9.9
|
|
`
|
|
servers := parseResolv(input)
|
|
want := []string{"1.1.1.1", "8.8.8.8", "9.9.9.9"}
|
|
if !reflect.DeepEqual(servers, want) {
|
|
t.Errorf("parseResolv() = %v, want %v", servers, want)
|
|
}
|
|
}
|
|
|
|
func TestParseResolvEmpty(t *testing.T) {
|
|
servers := parseResolv("")
|
|
if len(servers) != 0 {
|
|
t.Errorf("expected empty, got %v", servers)
|
|
}
|
|
}
|
|
|
|
func TestParseNmcliSnapshot(t *testing.T) {
|
|
input := `ipv4.method:manual
|
|
ipv4.addresses:192.168.1.10/24
|
|
ipv4.gateway:192.168.1.1
|
|
ipv4.dns:1.1.1.1,8.8.8.8
|
|
ipv4.routes:--`
|
|
|
|
cfg := parseNmcliSnapshot(input)
|
|
if cfg.Method != "static" {
|
|
t.Errorf("method: got %q, want static", cfg.Method)
|
|
}
|
|
if cfg.Address != "192.168.1.10" || cfg.Prefix != 24 {
|
|
t.Errorf("address: got %s/%d", cfg.Address, cfg.Prefix)
|
|
}
|
|
if cfg.Gateway != "192.168.1.1" {
|
|
t.Errorf("gateway: got %q", cfg.Gateway)
|
|
}
|
|
if len(cfg.DNS) != 2 || cfg.DNS[0] != "1.1.1.1" || cfg.DNS[1] != "8.8.8.8" {
|
|
t.Errorf("dns: got %v", cfg.DNS)
|
|
}
|
|
}
|
|
|
|
func TestParseNmcliRoutes(t *testing.T) {
|
|
input := `dst=10.0.0.0/24, nh=192.168.1.1; dst=172.16.0.0/12, nh=10.0.0.1`
|
|
routes := parseNmcliRoutes(input)
|
|
if len(routes) != 2 {
|
|
t.Fatalf("expected 2 routes, got %d", len(routes))
|
|
}
|
|
if routes[0].Destination != "10.0.0.0/24" || routes[0].Gateway != "192.168.1.1" {
|
|
t.Errorf("route 0: got %v", routes[0])
|
|
}
|
|
if routes[1].Destination != "172.16.0.0/12" || routes[1].Gateway != "10.0.0.1" {
|
|
t.Errorf("route 1: got %v", routes[1])
|
|
}
|
|
}
|
|
|
|
func TestParseNmcliSnapshotDHCP(t *testing.T) {
|
|
input := `ipv4.method:auto
|
|
ipv4.addresses:--
|
|
ipv4.gateway:--
|
|
ipv4.dns:--
|
|
ipv4.routes:--`
|
|
|
|
cfg := parseNmcliSnapshot(input)
|
|
if cfg.Method != "dhcp" {
|
|
t.Errorf("method: got %q, want dhcp", cfg.Method)
|
|
}
|
|
}
|
|
|
|
func TestParseNetworkdFile(t *testing.T) {
|
|
input := `# Managed by nadir
|
|
[Match]
|
|
Name=eth0
|
|
|
|
[Network]
|
|
DHCP=no
|
|
Address=10.0.0.5/24
|
|
Gateway=10.0.0.1
|
|
DNS=1.1.1.1
|
|
DNS=8.8.8.8
|
|
|
|
[Route]
|
|
Destination=192.168.0.0/16
|
|
Gateway=10.0.0.254
|
|
`
|
|
|
|
cfg := parseNetworkdFile(input)
|
|
if cfg.Method != "static" {
|
|
t.Errorf("method: got %q, want static", cfg.Method)
|
|
}
|
|
if cfg.Address != "10.0.0.5" || cfg.Prefix != 24 {
|
|
t.Errorf("address: got %s/%d", cfg.Address, cfg.Prefix)
|
|
}
|
|
if cfg.Gateway != "10.0.0.1" {
|
|
t.Errorf("gateway: got %q", cfg.Gateway)
|
|
}
|
|
if len(cfg.DNS) != 2 {
|
|
t.Errorf("dns: got %v", cfg.DNS)
|
|
}
|
|
if len(cfg.Routes) != 1 || cfg.Routes[0].Destination != "192.168.0.0/16" || cfg.Routes[0].Gateway != "10.0.0.254" {
|
|
t.Errorf("routes: got %v", cfg.Routes)
|
|
}
|
|
}
|
|
|
|
func TestParseNetworkdFileDHCP(t *testing.T) {
|
|
input := `[Match]
|
|
Name=eth0
|
|
|
|
[Network]
|
|
DHCP=yes
|
|
`
|
|
cfg := parseNetworkdFile(input)
|
|
if cfg.Method != "dhcp" {
|
|
t.Errorf("method: got %q, want dhcp", cfg.Method)
|
|
}
|
|
}
|
|
|
|
func TestParseIfupdownStanza(t *testing.T) {
|
|
input := `# Managed by nadir
|
|
auto eth0
|
|
iface eth0 inet static
|
|
address 192.168.1.10/24
|
|
gateway 192.168.1.1
|
|
dns-nameservers 1.1.1.1 8.8.8.8
|
|
up ip route add 10.0.0.0/24 via 192.168.1.254
|
|
down ip route del 10.0.0.0/24 via 192.168.1.254
|
|
`
|
|
|
|
cfg := parseIfupdownStanza(input)
|
|
if cfg.Method != "static" {
|
|
t.Errorf("method: got %q, want static", cfg.Method)
|
|
}
|
|
if cfg.Address != "192.168.1.10" || cfg.Prefix != 24 {
|
|
t.Errorf("address: got %s/%d", cfg.Address, cfg.Prefix)
|
|
}
|
|
if cfg.Gateway != "192.168.1.1" {
|
|
t.Errorf("gateway: got %q", cfg.Gateway)
|
|
}
|
|
if len(cfg.DNS) != 2 || cfg.DNS[0] != "1.1.1.1" || cfg.DNS[1] != "8.8.8.8" {
|
|
t.Errorf("dns: got %v", cfg.DNS)
|
|
}
|
|
if len(cfg.Routes) != 1 || cfg.Routes[0].Destination != "10.0.0.0/24" || cfg.Routes[0].Gateway != "192.168.1.254" {
|
|
t.Errorf("routes: got %v", cfg.Routes)
|
|
}
|
|
}
|
|
|
|
func TestParseIfupdownStanzaDHCP(t *testing.T) {
|
|
input := `auto eth0
|
|
iface eth0 inet dhcp
|
|
`
|
|
cfg := parseIfupdownStanza(input)
|
|
if cfg.Method != "dhcp" {
|
|
t.Errorf("method: got %q, want dhcp", cfg.Method)
|
|
}
|
|
}
|
|
|
|
func TestRenderNetworkdFile(t *testing.T) {
|
|
cfg := IfaceConfig{
|
|
Method: "static",
|
|
Address: "10.0.0.5",
|
|
Prefix: 24,
|
|
Gateway: "10.0.0.1",
|
|
DNS: []string{"1.1.1.1"},
|
|
Routes: []Route{{Destination: "192.168.0.0/16", Gateway: "10.0.0.254"}},
|
|
}
|
|
out := renderNetworkdFile("eth0", cfg)
|
|
|
|
mustContain := []string{
|
|
"Name=eth0",
|
|
"DHCP=no",
|
|
"Address=10.0.0.5/24",
|
|
"Gateway=10.0.0.1",
|
|
"DNS=1.1.1.1",
|
|
"Destination=192.168.0.0/16",
|
|
}
|
|
for _, s := range mustContain {
|
|
if !strings.Contains(out, s) {
|
|
t.Errorf("renderNetworkdFile missing %q in:\n%s", s, out)
|
|
}
|
|
}
|
|
|
|
// Roundtrip test
|
|
parsed := parseNetworkdFile(out)
|
|
if !reflect.DeepEqual(parsed, cfg) {
|
|
t.Errorf("roundtrip failed: got %+v, want %+v", parsed, cfg)
|
|
}
|
|
}
|
|
|
|
func TestRenderIfupdownStanza(t *testing.T) {
|
|
cfg := IfaceConfig{
|
|
Method: "static",
|
|
Address: "192.168.1.10",
|
|
Prefix: 24,
|
|
Gateway: "192.168.1.1",
|
|
DNS: []string{"1.1.1.1", "8.8.8.8"},
|
|
Routes: []Route{{Destination: "10.0.0.0/24", Gateway: "192.168.1.254"}},
|
|
}
|
|
out := renderIfupdownStanza("eth0", cfg)
|
|
|
|
mustContain := []string{
|
|
"iface eth0 inet static",
|
|
"address 192.168.1.10/24",
|
|
"gateway 192.168.1.1",
|
|
"dns-nameservers 1.1.1.1 8.8.8.8",
|
|
"up ip route add 10.0.0.0/24 via 192.168.1.254",
|
|
"down ip route del 10.0.0.0/24 via 192.168.1.254",
|
|
}
|
|
for _, s := range mustContain {
|
|
if !strings.Contains(out, s) {
|
|
t.Errorf("renderIfupdownStanza missing %q in:\n%s", s, out)
|
|
}
|
|
}
|
|
|
|
// Roundtrip test
|
|
parsed := parseIfupdownStanza(out)
|
|
if !reflect.DeepEqual(parsed, cfg) {
|
|
t.Errorf("roundtrip failed: got %+v, want %+v", parsed, cfg)
|
|
}
|
|
}
|
|
|
|
func TestValidateIPv6(t *testing.T) {
|
|
ok := IfaceConfig{Method: "dhcp", IPv6: &IPv6Config{Method: "static", Address: "2001:db8::1", Prefix: 64, Gateway: "2001:db8::ff"}}
|
|
if err := ok.validate(); err != nil {
|
|
t.Errorf("valid ipv6 rejected: %v", err)
|
|
}
|
|
bad := []IfaceConfig{
|
|
{Method: "dhcp", IPv6: &IPv6Config{Method: "static", Address: "1.2.3.4", Prefix: 64}}, // v4 in v6 block
|
|
{Method: "dhcp", IPv6: &IPv6Config{Method: "static", Address: "2001:db8::1", Prefix: 0}},
|
|
{Method: "dhcp", IPv6: &IPv6Config{Method: "bogus"}},
|
|
}
|
|
for i, c := range bad {
|
|
if err := c.validate(); err == nil {
|
|
t.Errorf("bad ipv6 case %d accepted", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseNmcliSnapshotIPv6(t *testing.T) {
|
|
input := `ipv4.method:manual
|
|
ipv4.addresses:192.168.1.10/24
|
|
ipv6.method:manual
|
|
ipv6.addresses:2001:db8::10/64
|
|
ipv6.gateway:2001:db8::1`
|
|
cfg := parseNmcliSnapshot(input)
|
|
if cfg.IPv6 == nil {
|
|
t.Fatal("ipv6 not captured")
|
|
}
|
|
if cfg.IPv6.Method != "static" || cfg.IPv6.Address != "2001:db8::10" || cfg.IPv6.Prefix != 64 || cfg.IPv6.Gateway != "2001:db8::1" {
|
|
t.Errorf("ipv6 snapshot: %+v", cfg.IPv6)
|
|
}
|
|
}
|
|
|
|
func TestNetworkdIPv6RoundTrip(t *testing.T) {
|
|
cfg := IfaceConfig{
|
|
Method: "static",
|
|
Address: "10.0.0.5",
|
|
Prefix: 24,
|
|
IPv6: &IPv6Config{Method: "static", Address: "2001:db8::5", Prefix: 64, Gateway: "2001:db8::1"},
|
|
}
|
|
got := parseNetworkdFile(renderNetworkdFile("eth0", cfg))
|
|
if got.Address != "10.0.0.5" || got.Prefix != 24 {
|
|
t.Errorf("ipv4 lost in roundtrip: %+v", got)
|
|
}
|
|
if !reflect.DeepEqual(got.IPv6, cfg.IPv6) {
|
|
t.Errorf("ipv6 roundtrip: got %+v want %+v", got.IPv6, cfg.IPv6)
|
|
}
|
|
}
|
|
|
|
func TestIfupdownIPv6RoundTrip(t *testing.T) {
|
|
cfg := IfaceConfig{
|
|
Method: "static",
|
|
Address: "192.168.1.10",
|
|
Prefix: 24,
|
|
IPv6: &IPv6Config{Method: "static", Address: "2001:db8::10", Prefix: 64, Gateway: "2001:db8::1"},
|
|
}
|
|
got := parseIfupdownStanza(renderIfupdownStanza("eth0", cfg))
|
|
if got.Address != "192.168.1.10" || got.Prefix != 24 || got.Method != "static" {
|
|
t.Errorf("ipv4 lost in roundtrip: %+v", got)
|
|
}
|
|
if !reflect.DeepEqual(got.IPv6, cfg.IPv6) {
|
|
t.Errorf("ipv6 roundtrip: got %+v want %+v", got.IPv6, cfg.IPv6)
|
|
}
|
|
}
|
|
|
|
func TestNetworkdIPv6AutoIgnore(t *testing.T) {
|
|
auto := parseNetworkdFile(renderNetworkdFile("eth0", IfaceConfig{Method: "dhcp", IPv6: &IPv6Config{Method: "auto"}}))
|
|
if auto.IPv6 == nil || auto.IPv6.Method != "auto" {
|
|
t.Errorf("auto roundtrip: %+v", auto.IPv6)
|
|
}
|
|
ign := parseNetworkdFile(renderNetworkdFile("eth0", IfaceConfig{Method: "dhcp", IPv6: &IPv6Config{Method: "ignore"}}))
|
|
if ign.IPv6 == nil || ign.IPv6.Method != "ignore" {
|
|
t.Errorf("ignore roundtrip: %+v", ign.IPv6)
|
|
}
|
|
}
|
|
|
|
func TestSplitCIDR(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
wantAddr string
|
|
wantPrefix int
|
|
}{
|
|
{"192.168.1.10/24", "192.168.1.10", 24},
|
|
{"10.0.0.1/8", "10.0.0.1", 8},
|
|
{"10.0.0.1", "10.0.0.1", 0},
|
|
}
|
|
for _, tt := range tests {
|
|
addr, prefix := splitCIDR(tt.input)
|
|
if addr != tt.wantAddr || prefix != tt.wantPrefix {
|
|
t.Errorf("splitCIDR(%q) = (%q, %d), want (%q, %d)", tt.input, addr, prefix, tt.wantAddr, tt.wantPrefix)
|
|
}
|
|
}
|
|
}
|