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.
183 lines
6.4 KiB
183 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)
|
|
}
|
|
}
|
|
|