You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
6.4 KiB

package networkconfig
import (
"net/netip"
"reflect"
"testing"
)
func testConfig() Config {
return Config{Interfaces: []Interface{
{Name: "Interface-1", Device: "eth0", Role: RoleInternal, Address: "192.168.0.223/24", Weight: 1},
{Name: "Interface-2", Device: "eth1", Role: RoleExternal, Address: "192.168.78.55/24", Gateway: "192.168.78.1", Weight: 1},
}}
}
func TestNormalizeAndAutomaticTrafficClassification(t *testing.T) {
config, err := Normalize(testConfig())
if err != nil {
t.Fatal(err)
}
if config.RoutingMode != RoutingModeActive || config.RollbackTimeoutSecond != 120 {
t.Fatalf("normalized defaults = %+v", config)
}
internal, err := RouteFor(config, netip.MustParseAddr("192.168.0.11"))
if err != nil || !reflect.DeepEqual(internal, []string{"eth0"}) {
t.Fatalf("syslog route = %v, %v", internal, err)
}
external, err := RouteFor(config, netip.MustParseAddr("203.248.240.140"))
if err != nil || !reflect.DeepEqual(external, []string{"eth1"}) {
t.Fatalf("NTP route = %v, %v", external, err)
}
}
func TestNormalizeKeepsFixedLogicalInterfaceMapping(t *testing.T) {
config := testConfig()
config.Interfaces[0].Device = Interface2Device
config.Interfaces[1].Device = Interface1Device
if _, err := Normalize(config); err == nil {
t.Fatal("Normalize accepted reversed Interface-1/Interface-2 device mapping")
}
}
func TestTwoExternalInterfacesUseBothDefaultNextHops(t *testing.T) {
config := testConfig()
config.Interfaces[0].Role = RoleExternal
config.Interfaces[0].Gateway = "192.168.0.1"
route, err := RouteFor(config, netip.MustParseAddr("8.8.8.8"))
if err != nil || !reflect.DeepEqual(route, []string{"eth0", "eth1"}) {
t.Fatalf("active/active route = %v, %v", route, err)
}
}
func TestTwoInternalInterfacesHaveNoDefaultRoute(t *testing.T) {
config := testConfig()
config.Interfaces[1].Role = RoleInternal
config.Interfaces[1].Gateway = ""
route, err := RouteFor(config, netip.MustParseAddr("8.8.8.8"))
if err != nil || len(route) != 0 {
t.Fatalf("internal-only route = %v, %v", route, err)
}
}
func TestNormalizeRejectsAmbiguousAndUnsafeInputs(t *testing.T) {
tests := []struct {
name string
mutate func(*Config)
}{
{"overlapping subnets", func(c *Config) { c.Interfaces[1].Address = "192.168.0.55/24"; c.Interfaces[1].Gateway = "192.168.0.1" }},
{"external without gateway", func(c *Config) { c.Interfaces[1].Gateway = "" }},
{"gateway outside subnet", func(c *Config) { c.Interfaces[1].Gateway = "192.168.79.1" }},
{"unknown device", func(c *Config) { c.Interfaces[1].Device = "eth2" }},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
config := testConfig()
test.mutate(&config)
if _, err := Normalize(config); err == nil {
t.Fatal("Normalize accepted invalid configuration")
}
})
}
}
func TestNormalizeSupportsHostDHCPDNSIPv6AndOOB(t *testing.T) {
config := testConfig()
config.HostName = "Console-Gateway"
config.OperatingMode = OperatingModeOOB
config.OOBPrimaryInterface = "Interface-1"
config.OOBFallbackInterface = "Interface-2"
config.OOBHealthCheckTarget = "9.9.9.9"
config.OOBHealthCheckSecond = 15
config.OOBFailureThreshold = 4
config.Interfaces[0].DNSServers = []string{"192.168.0.1", "8.8.8.8"}
config.Interfaces[0].IPv6Mode = IPv6ModeStatic
config.Interfaces[0].IPv6Address = "2001:db8::223/64"
config.Interfaces[0].IPv6Gateway = "2001:db8::1"
config.Interfaces[0].IPv6DNSServers = []string{"2001:4860:4860::8888"}
config.Interfaces[0].Gateway = "192.168.0.1"
normalized, err := Normalize(config)
if err != nil {
t.Fatal(err)
}
if normalized.HostName != "console-gateway" || normalized.OperatingMode != OperatingModeOOB || normalized.RoutingMode != OperatingModeOOB {
t.Fatalf("normalized host/mode = %+v", normalized)
}
if normalized.OOBPrimaryInterface != "Interface-1" || normalized.OOBFallbackInterface != "Interface-2" {
t.Fatalf("normalized OOB interfaces = %+v", normalized)
}
if normalized.Interfaces[0].IPv6Address != "2001:db8::223/64" || len(normalized.Interfaces[0].IPv6DNSServers) != 1 {
t.Fatalf("normalized IPv6 interface = %+v", normalized.Interfaces[0])
}
}
func TestNormalizeRejectsOOBWithoutTwoStaticGatewayPaths(t *testing.T) {
tests := []struct {
name string
mutate func(*Config)
}{
{"primary dhcp", func(c *Config) {
c.Interfaces[0].AddressMode = AddressModeDHCP
c.Interfaces[0].Address = ""
c.Interfaces[0].Gateway = ""
}},
{"fallback dhcp", func(c *Config) {
c.Interfaces[1].AddressMode = AddressModeDHCP
c.Interfaces[1].Address = ""
c.Interfaces[1].Gateway = ""
}},
{"primary gateway missing", func(c *Config) { c.Interfaces[0].Gateway = "" }},
{"fallback gateway missing", func(c *Config) { c.Interfaces[1].Gateway = "" }},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
config := testConfig()
config.OperatingMode = OperatingModeOOB
config.Interfaces[0].Gateway = "192.168.0.1"
test.mutate(&config)
if _, err := Normalize(config); err == nil {
t.Fatal("Normalize accepted OOB without two usable static gateway paths")
}
})
}
}
func TestNormalizeRejectsInvalidHostDNSAndIPv6(t *testing.T) {
for _, test := range []struct {
name string
mutate func(*Config)
}{
{"host", func(c *Config) { c.HostName = "invalid host" }},
{"ipv4 dns family", func(c *Config) { c.Interfaces[0].DNSServers = []string{"2001:db8::1"} }},
{"ipv6 dns family", func(c *Config) { c.Interfaces[0].IPv6DNSServers = []string{"8.8.8.8"} }},
{"static ipv6 missing address", func(c *Config) { c.Interfaces[0].IPv6Mode = IPv6ModeStatic }},
} {
t.Run(test.name, func(t *testing.T) {
config := testConfig()
test.mutate(&config)
if _, err := Normalize(config); err == nil {
t.Fatal("Normalize accepted invalid extended network configuration")
}
})
}
}
func TestNormalizeRejectsOOBWithAggregateMode(t *testing.T) {
config := testConfig()
config.OperatingMode = OperatingModeOOB
config.Advanced.Mode = AdvancedModeBonding
config.Interfaces[0].Gateway = "192.168.0.1"
if _, err := Normalize(config); err == nil {
t.Fatal("Normalize accepted OOB together with bonding")
}
}
func TestRouteForUsesAggregateDevice(t *testing.T) {
config := testConfig()
config.Advanced = AdvancedConfig{Mode: AdvancedModeBridge}
devices, err := RouteFor(config, netip.MustParseAddr("192.168.0.10"))
if err != nil || !reflect.DeepEqual(devices, []string{BridgeDevice}) {
t.Fatalf("bridge route = %v, %v", devices, err)
}
}