kvm/pkg/nmlite/static.go

347 lines
9.8 KiB
Go

package nmlite
import (
"fmt"
"net"
"github.com/jetkvm/kvm/internal/network/types"
"github.com/jetkvm/kvm/pkg/nmlite/link"
"github.com/rs/zerolog"
"github.com/vishvananda/netlink"
)
// StaticConfigManager manages static network configuration
type StaticConfigManager struct {
ifaceName string
logger *zerolog.Logger
}
// NewStaticConfigManager creates a new static configuration manager
func NewStaticConfigManager(ifaceName string, logger *zerolog.Logger) (*StaticConfigManager, error) {
if ifaceName == "" {
return nil, fmt.Errorf("interface name cannot be empty")
}
if logger == nil {
return nil, fmt.Errorf("logger cannot be nil")
}
return &StaticConfigManager{
ifaceName: ifaceName,
logger: logger,
}, nil
}
// ApplyIPv4Static applies static IPv4 configuration
func (scm *StaticConfigManager) ApplyIPv4Static(config *types.IPv4StaticConfig) error {
scm.logger.Info().Msg("applying static IPv4 configuration")
// Parse and validate configuration
ipv4Config, err := scm.parseIPv4Config(config)
if err != nil {
return fmt.Errorf("failed to parse IPv4 config: %w", err)
}
// Get interface
netlinkMgr := getNetlinkManager()
link, err := netlinkMgr.GetLinkByName(scm.ifaceName)
if err != nil {
return fmt.Errorf("failed to get interface: %w", err)
}
// Ensure interface is up
if err := netlinkMgr.EnsureInterfaceUp(link); err != nil {
return fmt.Errorf("failed to bring interface up: %w", err)
}
// Apply IP address
if err := scm.applyIPv4Address(link, ipv4Config); err != nil {
return fmt.Errorf("failed to apply IPv4 address: %w", err)
}
// Apply default route
if err := scm.applyIPv4Route(link, ipv4Config); err != nil {
return fmt.Errorf("failed to apply IPv4 route: %w", err)
}
scm.logger.Info().Msg("static IPv4 configuration applied successfully")
return nil
}
// ApplyIPv6Static applies static IPv6 configuration
func (scm *StaticConfigManager) ApplyIPv6Static(config *types.IPv6StaticConfig) error {
scm.logger.Info().Msg("applying static IPv6 configuration")
// Parse and validate configuration
ipv6Config, err := scm.parseIPv6Config(config)
if err != nil {
return fmt.Errorf("failed to parse IPv6 config: %w", err)
}
// Get interface
netlinkMgr := getNetlinkManager()
link, err := netlinkMgr.GetLinkByName(scm.ifaceName)
if err != nil {
return fmt.Errorf("failed to get interface: %w", err)
}
// Enable IPv6
if err := scm.enableIPv6(); err != nil {
return fmt.Errorf("failed to enable IPv6: %w", err)
}
// Ensure interface is up
if err := netlinkMgr.EnsureInterfaceUp(link); err != nil {
return fmt.Errorf("failed to bring interface up: %w", err)
}
// Apply IP address
if err := scm.applyIPv6Address(link, ipv6Config); err != nil {
return fmt.Errorf("failed to apply IPv6 address: %w", err)
}
// Apply default route
if err := scm.applyIPv6Route(link, ipv6Config); err != nil {
return fmt.Errorf("failed to apply IPv6 route: %w", err)
}
scm.logger.Info().Msg("static IPv6 configuration applied successfully")
return nil
}
// DisableIPv4 disables IPv4 on the interface
func (scm *StaticConfigManager) DisableIPv4() error {
scm.logger.Info().Msg("disabling IPv4")
netlinkMgr := getNetlinkManager()
iface, err := netlinkMgr.GetLinkByName(scm.ifaceName)
if err != nil {
return fmt.Errorf("failed to get interface: %w", err)
}
// Remove all IPv4 addresses
if err := netlinkMgr.RemoveAllAddresses(iface, link.AfInet); err != nil {
return fmt.Errorf("failed to remove IPv4 addresses: %w", err)
}
// Remove default route
if err := scm.removeIPv4DefaultRoute(); err != nil {
scm.logger.Warn().Err(err).Msg("failed to remove IPv4 default route")
}
scm.logger.Info().Msg("IPv4 disabled")
return nil
}
// DisableIPv6 disables IPv6 on the interface
func (scm *StaticConfigManager) DisableIPv6() error {
scm.logger.Info().Msg("disabling IPv6")
netlinkMgr := getNetlinkManager()
return netlinkMgr.DisableIPv6(scm.ifaceName)
}
// EnableIPv6SLAAC enables IPv6 SLAAC
func (scm *StaticConfigManager) EnableIPv6SLAAC() error {
scm.logger.Info().Msg("enabling IPv6 SLAAC")
netlinkMgr := getNetlinkManager()
return netlinkMgr.EnableIPv6SLAAC(scm.ifaceName)
}
// EnableIPv6LinkLocal enables IPv6 link-local only
func (scm *StaticConfigManager) EnableIPv6LinkLocal() error {
scm.logger.Info().Msg("enabling IPv6 link-local only")
netlinkMgr := getNetlinkManager()
if err := netlinkMgr.EnableIPv6LinkLocal(scm.ifaceName); err != nil {
return err
}
// Remove all non-link-local IPv6 addresses
link, err := netlinkMgr.GetLinkByName(scm.ifaceName)
if err != nil {
return fmt.Errorf("failed to get interface: %w", err)
}
if err := netlinkMgr.RemoveNonLinkLocalIPv6Addresses(link); err != nil {
return fmt.Errorf("failed to remove non-link-local IPv6 addresses: %w", err)
}
return netlinkMgr.EnsureInterfaceUp(link)
}
// parseIPv4Config parses and validates IPv4 static configuration
func (scm *StaticConfigManager) parseIPv4Config(config *types.IPv4StaticConfig) (*parsedIPv4Config, error) {
if config == nil {
return nil, fmt.Errorf("config is nil")
}
// Parse IP address and netmask
ipNet, err := link.ParseIPv4Netmask(config.Address.String, config.Netmask.String)
if err != nil {
return nil, err
}
scm.logger.Info().Str("ipNet", ipNet.String()).Interface("ipc", config).Msg("parsed IPv4 address and netmask")
// Parse gateway
gateway := net.ParseIP(config.Gateway.String)
if gateway == nil {
return nil, fmt.Errorf("invalid gateway: %s", config.Gateway.String)
}
// Parse DNS servers
var dns []net.IP
for _, dnsStr := range config.DNS {
if err := link.ValidateIPAddress(dnsStr, false); err != nil {
return nil, fmt.Errorf("invalid DNS server: %w", err)
}
dns = append(dns, net.ParseIP(dnsStr))
}
return &parsedIPv4Config{
network: *ipNet,
gateway: gateway,
dns: dns,
}, nil
}
// parseIPv6Config parses and validates IPv6 static configuration
func (scm *StaticConfigManager) parseIPv6Config(config *types.IPv6StaticConfig) (*parsedIPv6Config, error) {
if config == nil {
return nil, fmt.Errorf("config is nil")
}
// Parse IP address and prefix
ipNet, err := link.ParseIPv6Prefix(config.Prefix.String, 64) // Default to /64 if not specified
if err != nil {
return nil, err
}
// Parse gateway
gateway := net.ParseIP(config.Gateway.String)
if gateway == nil {
return nil, fmt.Errorf("invalid gateway: %s", config.Gateway.String)
}
// Parse DNS servers
var dns []net.IP
for _, dnsStr := range config.DNS {
dnsIP := net.ParseIP(dnsStr)
if dnsIP == nil {
return nil, fmt.Errorf("invalid DNS server: %s", dnsStr)
}
dns = append(dns, dnsIP)
}
return &parsedIPv6Config{
prefix: *ipNet,
gateway: gateway,
dns: dns,
}, nil
}
// applyIPv4Address applies IPv4 address to interface
func (scm *StaticConfigManager) applyIPv4Address(iface *link.Link, config *parsedIPv4Config) error {
netlinkMgr := getNetlinkManager()
// Remove existing IPv4 addresses
if err := netlinkMgr.RemoveAllAddresses(iface, link.AfInet); err != nil {
return fmt.Errorf("failed to remove existing IPv4 addresses: %w", err)
}
// Add new address
addr := &netlink.Addr{
IPNet: &config.network,
}
if err := netlinkMgr.AddrAdd(iface, addr); err != nil {
return fmt.Errorf("failed to add IPv4 address: %w", err)
}
scm.logger.Info().Str("address", config.network.String()).Msg("IPv4 address applied")
return nil
}
// applyIPv6Address applies IPv6 address to interface
func (scm *StaticConfigManager) applyIPv6Address(iface *link.Link, config *parsedIPv6Config) error {
netlinkMgr := getNetlinkManager()
// Remove existing global IPv6 addresses
if err := netlinkMgr.RemoveNonLinkLocalIPv6Addresses(iface); err != nil {
return fmt.Errorf("failed to remove existing IPv6 addresses: %w", err)
}
// Add new address
addr := &netlink.Addr{
IPNet: &config.prefix,
}
if err := netlinkMgr.AddrAdd(iface, addr); err != nil {
return fmt.Errorf("failed to add IPv6 address: %w", err)
}
scm.logger.Info().Str("address", config.prefix.String()).Msg("IPv6 address applied")
return nil
}
// applyIPv4Route applies IPv4 default route
func (scm *StaticConfigManager) applyIPv4Route(iface *link.Link, config *parsedIPv4Config) error {
netlinkMgr := getNetlinkManager()
// Check if default route already exists
if netlinkMgr.HasDefaultRoute(link.AfInet) {
scm.logger.Info().Msg("IPv4 default route already exists")
return nil
}
// Add default route
if err := netlinkMgr.AddDefaultRoute(iface, config.gateway, link.AfInet); err != nil {
return fmt.Errorf("failed to add IPv4 default route: %w", err)
}
scm.logger.Info().Str("gateway", config.gateway.String()).Msg("IPv4 default route applied")
return nil
}
// applyIPv6Route applies IPv6 default route
func (scm *StaticConfigManager) applyIPv6Route(iface *link.Link, config *parsedIPv6Config) error {
netlinkMgr := getNetlinkManager()
// Check if default route already exists
if netlinkMgr.HasDefaultRoute(link.AfInet6) {
scm.logger.Info().Msg("IPv6 default route already exists")
return nil
}
// Add default route
if err := netlinkMgr.AddDefaultRoute(iface, config.gateway, link.AfInet6); err != nil {
return fmt.Errorf("failed to add IPv6 default route: %w", err)
}
scm.logger.Info().Str("gateway", config.gateway.String()).Msg("IPv6 default route applied")
return nil
}
// removeIPv4DefaultRoute removes IPv4 default route
func (scm *StaticConfigManager) removeIPv4DefaultRoute() error {
netlinkMgr := getNetlinkManager()
return netlinkMgr.RemoveDefaultRoute(link.AfInet)
}
// enableIPv6 enables IPv6 on the interface
func (scm *StaticConfigManager) enableIPv6() error {
netlinkMgr := getNetlinkManager()
return netlinkMgr.EnableIPv6(scm.ifaceName)
}
// parsedIPv4Config represents parsed IPv4 configuration
type parsedIPv4Config struct {
network net.IPNet
gateway net.IP
dns []net.IP
}
// parsedIPv6Config represents parsed IPv6 configuration
type parsedIPv6Config struct {
prefix net.IPNet
gateway net.IP
dns []net.IP
}