kvm/internal/network/validate.go

188 lines
4.2 KiB
Go

package network
import (
"bytes"
"fmt"
"net"
)
var (
netMask32 = net.IPv4Mask(255, 255, 255, 255)
)
func parseIP(ip string, family int) (net.IP, error) {
addr := net.ParseIP(ip)
if addr == nil {
return nil, fmt.Errorf("failed to parse IP: %s", ip)
}
switch family {
case AF_INET:
ia := addr.To4()
if ia == nil {
return nil, fmt.Errorf("address is not a valid IPv4 address")
}
return ia, nil
case AF_INET6:
ia := addr.To16()
if ia == nil {
return nil, fmt.Errorf("address is not a valid IPv6 address")
}
return ia, nil
default:
return nil, fmt.Errorf("invalid family: %d", family)
}
}
func parseAndValidateUnicastIP(ip string, family int) (net.IP, error) {
addr, err := parseIP(ip, family)
if err != nil {
return nil, err
}
if !addr.IsGlobalUnicast() {
return nil, fmt.Errorf("address is not a global unicast address")
}
return addr, nil
}
func isValidNetMask(s string) bool {
ip := net.ParseIP(s)
if ip == nil {
return false
}
var m net.IPMask
if v4 := ip.To4(); v4 != nil {
m = net.IPMask(v4) // 4 bytes
} else {
v6 := ip.To16()
if v6 == nil {
return false
}
m = net.IPMask(v6) // 16 bytes
}
ones, bits := m.Size()
// Non-contiguous masks return (0,0). /0 is valid and returns (0, 32|128).
return !(ones == 0 && bits == 0)
}
type parsedIPv4Config struct {
address net.IP
netmask net.IPMask
network net.IPNet
gateway net.IP
}
type parsedIPv6Config struct {
address net.IP
prefix net.IPNet
gateway net.IP
}
func parseAndValidateStaticIPv4Config(config *IPv4StaticConfig) (*parsedIPv4Config, error) {
addr, err := parseAndValidateUnicastIP(config.Address.String, AF_INET)
if err != nil {
return nil, fmt.Errorf("failed to parse address: %w", err)
}
netmask, err := parseIP(config.Netmask.String, AF_INET)
if err != nil {
return nil, fmt.Errorf("failed to parse netmask: %w", err)
}
if !isValidNetMask(config.Netmask.String) {
return nil, fmt.Errorf("netmask is not a valid netmask")
}
gateway, err := parseAndValidateUnicastIP(config.Gateway.String, AF_INET)
if err != nil {
return nil, fmt.Errorf("failed to parse gateway: %w", err)
}
if addr.Equal(gateway) {
return nil, fmt.Errorf("address and gateway cannot be the same")
}
netMask := net.IPMask(netmask)
ipNet := net.IPNet{
IP: addr,
Mask: netMask,
}
if !ipNet.Contains(gateway) && !bytes.Equal(ipNet.Mask, netMask32) {
return nil, fmt.Errorf("address is not in the same subnet as the gateway")
}
return &parsedIPv4Config{
address: addr,
netmask: netMask,
network: ipNet,
gateway: gateway,
}, nil
}
func parseAndValidateStaticIPv6Config(config *IPv6StaticConfig) (*parsedIPv6Config, error) {
addr, prefix, err := net.ParseCIDR(fmt.Sprintf("%s/%d", config.Address.String, config.PrefixLength.Int64))
if err != nil {
return nil, fmt.Errorf("failed to parse prefix: %w", err)
}
if !addr.IsGlobalUnicast() {
return nil, fmt.Errorf("address is not a global unicast address")
}
gateway, err := parseIP(config.Gateway.String, AF_INET6)
if err != nil {
return nil, fmt.Errorf("failed to parse gateway: %w", err)
}
if addr.Equal(gateway) {
return nil, fmt.Errorf("address and gateway cannot be the same")
}
if !prefix.Contains(gateway) && !gateway.IsLinkLocalUnicast() {
return nil, fmt.Errorf("gateway is not in the same subnet as the address or is a link-local address")
}
return &parsedIPv6Config{
address: addr,
prefix: *prefix,
gateway: gateway,
}, nil
}
func validateIPv4Config(config *NetworkConfig) error {
switch config.IPv4Mode.String {
case "static":
_, err := parseAndValidateStaticIPv4Config(config.IPv4Static)
return err
case "dhcp":
return nil
case "disabled":
return nil
default:
return fmt.Errorf("invalid IPv4 mode: %s", config.IPv4Mode.String)
}
}
func validateIPv6Config(config *NetworkConfig) error {
switch config.IPv6Mode.String {
case "static":
_, err := parseAndValidateStaticIPv6Config(config.IPv6Static)
return err
case "slaac":
return nil
case "dhcpv6":
return fmt.Errorf("not implemented")
case "slaac_and_dhcpv6":
return fmt.Errorf("not implemented")
case "link_local":
return nil
case "disabled":
return nil
default:
return fmt.Errorf("invalid IPv6 mode: %s", config.IPv6Mode.String)
}
}