mirror of https://github.com/jetkvm/kvm.git
821 lines
20 KiB
Go
821 lines
20 KiB
Go
package nmlite
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
|
|
"time"
|
|
|
|
"github.com/jetkvm/kvm/internal/sync"
|
|
|
|
"github.com/jetkvm/kvm/internal/confparser"
|
|
"github.com/jetkvm/kvm/internal/logging"
|
|
"github.com/jetkvm/kvm/internal/network/types"
|
|
"github.com/jetkvm/kvm/pkg/nmlite/link"
|
|
"github.com/mdlayher/ndp"
|
|
"github.com/rs/zerolog"
|
|
"github.com/vishvananda/netlink"
|
|
)
|
|
|
|
// InterfaceManager manages a single network interface
|
|
type InterfaceManager struct {
|
|
ctx context.Context
|
|
ifaceName string
|
|
config *types.NetworkConfig
|
|
logger *zerolog.Logger
|
|
state *types.InterfaceState
|
|
linkState *link.Link
|
|
stateMu sync.RWMutex
|
|
|
|
// Network components
|
|
staticConfig *StaticConfigManager
|
|
dhcpClient *DHCPClient
|
|
resolvConf *ResolvConfManager
|
|
hostname *HostnameManager
|
|
|
|
// Callbacks
|
|
onStateChange func(state *types.InterfaceState)
|
|
onConfigChange func(config *types.NetworkConfig)
|
|
onDHCPLeaseChange func(lease *types.DHCPLease)
|
|
|
|
// Control
|
|
stopCh chan struct{}
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
// NewInterfaceManager creates a new interface manager
|
|
func NewInterfaceManager(ctx context.Context, ifaceName string, config *types.NetworkConfig, logger *zerolog.Logger) (*InterfaceManager, error) {
|
|
if config == nil {
|
|
return nil, fmt.Errorf("config cannot be nil")
|
|
}
|
|
|
|
if logger == nil {
|
|
logger = logging.GetSubsystemLogger("interface")
|
|
}
|
|
|
|
scopedLogger := logger.With().Str("interface", ifaceName).Logger()
|
|
|
|
// Validate and set defaults
|
|
if err := confparser.SetDefaultsAndValidate(config); err != nil {
|
|
return nil, fmt.Errorf("invalid config: %w", err)
|
|
}
|
|
|
|
im := &InterfaceManager{
|
|
ctx: ctx,
|
|
ifaceName: ifaceName,
|
|
config: config,
|
|
logger: &scopedLogger,
|
|
state: &types.InterfaceState{
|
|
InterfaceName: ifaceName,
|
|
// LastUpdated: time.Now(),
|
|
},
|
|
stopCh: make(chan struct{}),
|
|
}
|
|
|
|
// Initialize components
|
|
var err error
|
|
im.staticConfig, err = NewStaticConfigManager(ifaceName, &scopedLogger)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create static config manager: %w", err)
|
|
}
|
|
|
|
// create the dhcp client
|
|
im.dhcpClient, err = NewDHCPClient(ctx, ifaceName, &scopedLogger, config.DHCPClient.String)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create DHCP client: %w", err)
|
|
}
|
|
|
|
im.resolvConf = NewResolvConfManager(&scopedLogger)
|
|
im.hostname = NewHostnameManager(&scopedLogger)
|
|
|
|
// Set up DHCP client callbacks
|
|
im.dhcpClient.SetOnLeaseChange(func(lease *types.DHCPLease) {
|
|
if err := im.applyDHCPLease(lease); err != nil {
|
|
im.logger.Error().Err(err).Msg("failed to apply DHCP lease")
|
|
}
|
|
im.updateStateFromDHCPLease(lease)
|
|
if im.onDHCPLeaseChange != nil {
|
|
im.onDHCPLeaseChange(lease)
|
|
}
|
|
})
|
|
|
|
return im, nil
|
|
}
|
|
|
|
// Start starts managing the interface
|
|
func (im *InterfaceManager) Start() error {
|
|
im.logger.Info().Msg("starting interface manager")
|
|
|
|
// Start monitoring interface state
|
|
im.wg.Add(1)
|
|
go im.monitorInterfaceState()
|
|
|
|
nl := getNetlinkManager()
|
|
nl.AddStateChangeCallback(im.ifaceName, link.StateChangeCallback{
|
|
Async: true,
|
|
Func: func(link *link.Link) {
|
|
im.handleLinkStateChange(link)
|
|
},
|
|
})
|
|
|
|
// Apply initial configuration
|
|
if err := im.applyConfiguration(); err != nil {
|
|
im.logger.Error().Err(err).Msg("failed to apply initial configuration")
|
|
return err
|
|
}
|
|
|
|
im.logger.Info().Msg("interface manager started")
|
|
return nil
|
|
}
|
|
|
|
// Stop stops managing the interface
|
|
func (im *InterfaceManager) Stop() error {
|
|
im.logger.Info().Msg("stopping interface manager")
|
|
|
|
close(im.stopCh)
|
|
im.wg.Wait()
|
|
|
|
// Stop DHCP client
|
|
if im.dhcpClient != nil {
|
|
im.dhcpClient.Stop()
|
|
}
|
|
|
|
im.logger.Info().Msg("interface manager stopped")
|
|
return nil
|
|
}
|
|
|
|
func (im *InterfaceManager) link() (*link.Link, error) {
|
|
nl := getNetlinkManager()
|
|
if nl == nil {
|
|
return nil, fmt.Errorf("netlink manager not initialized")
|
|
}
|
|
return nl.GetLinkByName(im.ifaceName)
|
|
}
|
|
|
|
// IsUp returns true if the interface is up
|
|
func (im *InterfaceManager) IsUp() bool {
|
|
im.stateMu.RLock()
|
|
defer im.stateMu.RUnlock()
|
|
|
|
return im.state.Up
|
|
}
|
|
|
|
// IsOnline returns true if the interface is online
|
|
func (im *InterfaceManager) IsOnline() bool {
|
|
im.stateMu.RLock()
|
|
defer im.stateMu.RUnlock()
|
|
|
|
return im.state.Online
|
|
}
|
|
|
|
// IPv4Ready returns true if the interface has an IPv4 address
|
|
func (im *InterfaceManager) IPv4Ready() bool {
|
|
im.stateMu.RLock()
|
|
defer im.stateMu.RUnlock()
|
|
|
|
return im.state.IPv4Ready
|
|
}
|
|
|
|
// IPv6Ready returns true if the interface has an IPv6 address
|
|
func (im *InterfaceManager) IPv6Ready() bool {
|
|
im.stateMu.RLock()
|
|
defer im.stateMu.RUnlock()
|
|
|
|
return im.state.IPv6Ready
|
|
}
|
|
|
|
// GetIPv4Addresses returns the IPv4 addresses of the interface
|
|
func (im *InterfaceManager) GetIPv4Addresses() []string {
|
|
im.stateMu.RLock()
|
|
defer im.stateMu.RUnlock()
|
|
|
|
return im.state.IPv4Addresses
|
|
}
|
|
|
|
// GetIPv4Address returns the IPv4 address of the interface
|
|
func (im *InterfaceManager) GetIPv4Address() string {
|
|
im.stateMu.RLock()
|
|
defer im.stateMu.RUnlock()
|
|
|
|
return im.state.IPv4Address
|
|
}
|
|
|
|
// GetIPv6Address returns the IPv6 address of the interface
|
|
func (im *InterfaceManager) GetIPv6Address() string {
|
|
im.stateMu.RLock()
|
|
defer im.stateMu.RUnlock()
|
|
|
|
return im.state.IPv6Address
|
|
}
|
|
|
|
// GetIPv6Addresses returns the IPv6 addresses of the interface
|
|
func (im *InterfaceManager) GetIPv6Addresses() []string {
|
|
im.stateMu.RLock()
|
|
defer im.stateMu.RUnlock()
|
|
|
|
addresses := []string{}
|
|
for _, addr := range im.state.IPv6Addresses {
|
|
addresses = append(addresses, addr.Address.String())
|
|
}
|
|
|
|
return []string{}
|
|
}
|
|
|
|
// GetMACAddress returns the MAC address of the interface
|
|
func (im *InterfaceManager) GetMACAddress() string {
|
|
return im.state.MACAddress
|
|
}
|
|
|
|
// GetState returns the current interface state
|
|
func (im *InterfaceManager) GetState() *types.InterfaceState {
|
|
im.stateMu.RLock()
|
|
defer im.stateMu.RUnlock()
|
|
|
|
// Return a copy to avoid race conditions
|
|
im.logger.Debug().Interface("state", im.state).Msg("getting interface state")
|
|
|
|
state := *im.state
|
|
return &state
|
|
}
|
|
|
|
// NTPServers returns the NTP servers of the interface
|
|
func (im *InterfaceManager) NTPServers() []net.IP {
|
|
im.stateMu.RLock()
|
|
defer im.stateMu.RUnlock()
|
|
|
|
return im.state.NTPServers
|
|
}
|
|
|
|
// GetConfig returns the current interface configuration
|
|
func (im *InterfaceManager) GetConfig() *types.NetworkConfig {
|
|
// Return a copy to avoid race conditions
|
|
config := *im.config
|
|
return &config
|
|
}
|
|
|
|
// ApplyConfiguration applies the current configuration to the interface
|
|
func (im *InterfaceManager) ApplyConfiguration() error {
|
|
return im.applyConfiguration()
|
|
}
|
|
|
|
// SetConfig updates the interface configuration
|
|
func (im *InterfaceManager) SetConfig(config *types.NetworkConfig) error {
|
|
if config == nil {
|
|
return fmt.Errorf("config cannot be nil")
|
|
}
|
|
|
|
// Validate and set defaults
|
|
if err := confparser.SetDefaultsAndValidate(config); err != nil {
|
|
return fmt.Errorf("invalid config: %w", err)
|
|
}
|
|
|
|
im.config = config
|
|
|
|
// Apply the new configuration
|
|
if err := im.applyConfiguration(); err != nil {
|
|
im.logger.Error().Err(err).Msg("failed to apply new configuration")
|
|
return err
|
|
}
|
|
|
|
// Notify callback
|
|
if im.onConfigChange != nil {
|
|
im.onConfigChange(config)
|
|
}
|
|
|
|
im.logger.Info().Msg("configuration updated")
|
|
return nil
|
|
}
|
|
|
|
// RenewDHCPLease renews the DHCP lease
|
|
func (im *InterfaceManager) RenewDHCPLease() error {
|
|
if im.dhcpClient == nil {
|
|
return fmt.Errorf("DHCP client not available")
|
|
}
|
|
|
|
return im.dhcpClient.Renew()
|
|
}
|
|
|
|
// SetOnStateChange sets the callback for state changes
|
|
func (im *InterfaceManager) SetOnStateChange(callback func(state *types.InterfaceState)) {
|
|
im.onStateChange = callback
|
|
}
|
|
|
|
// SetOnConfigChange sets the callback for configuration changes
|
|
func (im *InterfaceManager) SetOnConfigChange(callback func(config *types.NetworkConfig)) {
|
|
im.onConfigChange = callback
|
|
}
|
|
|
|
// SetOnDHCPLeaseChange sets the callback for DHCP lease changes
|
|
func (im *InterfaceManager) SetOnDHCPLeaseChange(callback func(lease *types.DHCPLease)) {
|
|
im.onDHCPLeaseChange = callback
|
|
}
|
|
|
|
// applyConfiguration applies the current configuration to the interface
|
|
func (im *InterfaceManager) applyConfiguration() error {
|
|
im.logger.Info().Msg("applying configuration")
|
|
|
|
// Apply IPv4 configuration
|
|
if err := im.applyIPv4Config(); err != nil {
|
|
return fmt.Errorf("failed to apply IPv4 config: %w", err)
|
|
}
|
|
|
|
// Apply IPv6 configuration
|
|
if err := im.applyIPv6Config(); err != nil {
|
|
return fmt.Errorf("failed to apply IPv6 config: %w", err)
|
|
}
|
|
|
|
// Update hostname
|
|
if err := im.updateHostname(); err != nil {
|
|
im.logger.Warn().Err(err).Msg("failed to update hostname")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// applyIPv4Config applies IPv4 configuration
|
|
func (im *InterfaceManager) applyIPv4Config() error {
|
|
mode := im.config.IPv4Mode.String
|
|
im.logger.Info().Str("mode", mode).Msg("applying IPv4 configuration")
|
|
|
|
switch mode {
|
|
case "static":
|
|
return im.applyIPv4Static()
|
|
case "dhcp":
|
|
return im.applyIPv4DHCP()
|
|
case "disabled":
|
|
return im.disableIPv4()
|
|
default:
|
|
return fmt.Errorf("invalid IPv4 mode: %s", mode)
|
|
}
|
|
}
|
|
|
|
// applyIPv6Config applies IPv6 configuration
|
|
func (im *InterfaceManager) applyIPv6Config() error {
|
|
mode := im.config.IPv6Mode.String
|
|
im.logger.Info().Str("mode", mode).Msg("applying IPv6 configuration")
|
|
|
|
switch mode {
|
|
case "static":
|
|
return im.applyIPv6Static()
|
|
case "dhcpv6":
|
|
return im.applyIPv6DHCP()
|
|
case "slaac":
|
|
return im.applyIPv6SLAAC()
|
|
case "slaac_and_dhcpv6":
|
|
return im.applyIPv6SLAACAndDHCP()
|
|
case "link_local":
|
|
return im.applyIPv6LinkLocal()
|
|
case "disabled":
|
|
return im.disableIPv6()
|
|
default:
|
|
return fmt.Errorf("invalid IPv6 mode: %s", mode)
|
|
}
|
|
}
|
|
|
|
// applyIPv4Static applies static IPv4 configuration
|
|
func (im *InterfaceManager) applyIPv4Static() error {
|
|
if im.config.IPv4Static == nil {
|
|
return fmt.Errorf("IPv4 static configuration is nil")
|
|
}
|
|
|
|
// Disable DHCP
|
|
if im.dhcpClient != nil {
|
|
im.dhcpClient.SetIPv4(false)
|
|
}
|
|
|
|
// Apply static configuration
|
|
return im.staticConfig.ApplyIPv4Static(im.config.IPv4Static)
|
|
}
|
|
|
|
// applyIPv4DHCP applies DHCP IPv4 configuration
|
|
func (im *InterfaceManager) applyIPv4DHCP() error {
|
|
if im.dhcpClient == nil {
|
|
return fmt.Errorf("DHCP client not available")
|
|
}
|
|
|
|
// Enable DHCP
|
|
im.dhcpClient.SetIPv4(true)
|
|
return im.dhcpClient.Start()
|
|
}
|
|
|
|
// disableIPv4 disables IPv4
|
|
func (im *InterfaceManager) disableIPv4() error {
|
|
// Disable DHCP
|
|
if im.dhcpClient != nil {
|
|
im.dhcpClient.SetIPv4(false)
|
|
}
|
|
|
|
// Remove all IPv4 addresses
|
|
return im.staticConfig.DisableIPv4()
|
|
}
|
|
|
|
// applyIPv6Static applies static IPv6 configuration
|
|
func (im *InterfaceManager) applyIPv6Static() error {
|
|
if im.config.IPv6Static == nil {
|
|
return fmt.Errorf("IPv6 static configuration is nil")
|
|
}
|
|
|
|
// Disable DHCPv6
|
|
if im.dhcpClient != nil {
|
|
im.dhcpClient.SetIPv6(false)
|
|
}
|
|
|
|
// Apply static configuration
|
|
return im.staticConfig.ApplyIPv6Static(im.config.IPv6Static)
|
|
}
|
|
|
|
// applyIPv6DHCP applies DHCPv6 configuration
|
|
func (im *InterfaceManager) applyIPv6DHCP() error {
|
|
if im.dhcpClient == nil {
|
|
return fmt.Errorf("DHCP client not available")
|
|
}
|
|
|
|
// Enable DHCPv6
|
|
im.dhcpClient.SetIPv6(true)
|
|
return im.dhcpClient.Start()
|
|
}
|
|
|
|
// applyIPv6SLAAC applies SLAAC configuration
|
|
func (im *InterfaceManager) applyIPv6SLAAC() error {
|
|
// Disable DHCPv6
|
|
if im.dhcpClient != nil {
|
|
im.dhcpClient.SetIPv6(false)
|
|
}
|
|
|
|
// Remove static IPv6 configuration
|
|
l, err := im.link()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get interface: %w", err)
|
|
}
|
|
|
|
netlinkMgr := getNetlinkManager()
|
|
if err := netlinkMgr.RemoveNonLinkLocalIPv6Addresses(l); err != nil {
|
|
return fmt.Errorf("failed to remove non-link-local IPv6 addresses: %w", err)
|
|
}
|
|
|
|
if err := im.SendRouterSolicitation(); err != nil {
|
|
return fmt.Errorf("failed to send router solicitation: %w", err)
|
|
}
|
|
|
|
// Enable SLAAC
|
|
return im.staticConfig.EnableIPv6SLAAC()
|
|
}
|
|
|
|
// applyIPv6SLAACAndDHCP applies SLAAC + DHCPv6 configuration
|
|
func (im *InterfaceManager) applyIPv6SLAACAndDHCP() error {
|
|
// Enable both SLAAC and DHCPv6
|
|
if im.dhcpClient != nil {
|
|
im.dhcpClient.SetIPv6(true)
|
|
im.dhcpClient.Start()
|
|
}
|
|
|
|
return im.staticConfig.EnableIPv6SLAAC()
|
|
}
|
|
|
|
// applyIPv6LinkLocal applies link-local only IPv6 configuration
|
|
func (im *InterfaceManager) applyIPv6LinkLocal() error {
|
|
// Disable DHCPv6
|
|
if im.dhcpClient != nil {
|
|
im.dhcpClient.SetIPv6(false)
|
|
}
|
|
|
|
// Enable link-local only
|
|
return im.staticConfig.EnableIPv6LinkLocal()
|
|
}
|
|
|
|
// disableIPv6 disables IPv6
|
|
func (im *InterfaceManager) disableIPv6() error {
|
|
// Disable DHCPv6
|
|
if im.dhcpClient != nil {
|
|
im.dhcpClient.SetIPv6(false)
|
|
}
|
|
|
|
// Disable IPv6
|
|
return im.staticConfig.DisableIPv6()
|
|
}
|
|
|
|
// updateHostname updates the system hostname
|
|
func (im *InterfaceManager) updateHostname() error {
|
|
hostname := im.getHostname()
|
|
domain := im.getDomain()
|
|
fqdn := fmt.Sprintf("%s.%s", hostname, domain)
|
|
|
|
return im.hostname.SetHostname(hostname, fqdn)
|
|
}
|
|
|
|
// getHostname returns the configured hostname or default
|
|
func (im *InterfaceManager) getHostname() string {
|
|
if im.config.Hostname.String != "" {
|
|
return im.config.Hostname.String
|
|
}
|
|
return "jetkvm"
|
|
}
|
|
|
|
// getDomain returns the configured domain or default
|
|
func (im *InterfaceManager) getDomain() string {
|
|
if im.config.Domain.String != "" {
|
|
return im.config.Domain.String
|
|
}
|
|
|
|
// Try to get domain from DHCP lease
|
|
if im.dhcpClient != nil {
|
|
if lease := im.dhcpClient.Lease4(); lease != nil && lease.Domain != "" {
|
|
return lease.Domain
|
|
}
|
|
}
|
|
|
|
return "local"
|
|
}
|
|
|
|
func (im *InterfaceManager) handleLinkStateChange(link *link.Link) {
|
|
{
|
|
im.stateMu.Lock()
|
|
defer im.stateMu.Unlock()
|
|
|
|
if link.IsSame(im.linkState) {
|
|
return
|
|
}
|
|
|
|
im.linkState = link
|
|
}
|
|
|
|
im.logger.Info().Interface("link", link).Msg("link state changed")
|
|
|
|
operState := link.Attrs().OperState
|
|
if operState == netlink.OperUp {
|
|
im.handleLinkUp()
|
|
} else {
|
|
im.handleLinkDown()
|
|
}
|
|
}
|
|
|
|
// SendRouterSolicitation sends a router solicitation
|
|
func (im *InterfaceManager) SendRouterSolicitation() error {
|
|
im.logger.Info().Msg("sending router solicitation")
|
|
m := &ndp.RouterSolicitation{}
|
|
|
|
l, err := im.link()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get interface: %w", err)
|
|
}
|
|
|
|
iface := l.Interface()
|
|
if iface == nil {
|
|
return fmt.Errorf("failed to get net.Interface for %s", im.ifaceName)
|
|
}
|
|
|
|
hwAddr := l.HardwareAddr()
|
|
if hwAddr == nil {
|
|
return fmt.Errorf("failed to get hardware address for %s", im.ifaceName)
|
|
}
|
|
|
|
c, _, err := ndp.Listen(iface, ndp.LinkLocal)
|
|
defer c.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create NDP listener on %s: %w", im.ifaceName, err)
|
|
}
|
|
|
|
m.Options = append(m.Options, &ndp.LinkLayerAddress{
|
|
Addr: hwAddr,
|
|
Direction: ndp.Source,
|
|
})
|
|
|
|
targetAddr := netip.MustParseAddr("ff02::2")
|
|
|
|
if err := c.WriteTo(m, nil, targetAddr); err != nil {
|
|
return fmt.Errorf("failed to write to %s: %w", targetAddr.String(), err)
|
|
}
|
|
|
|
im.logger.Info().Msg("router solicitation sent")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (im *InterfaceManager) handleLinkUp() {
|
|
im.logger.Info().Msg("link up")
|
|
|
|
im.applyConfiguration()
|
|
|
|
if im.config.IPv4Mode.String == "dhcp" {
|
|
im.dhcpClient.Renew()
|
|
}
|
|
|
|
if im.config.IPv6Mode.String == "slaac" {
|
|
im.staticConfig.EnableIPv6SLAAC()
|
|
im.SendRouterSolicitation()
|
|
}
|
|
}
|
|
|
|
func (im *InterfaceManager) handleLinkDown() {
|
|
im.logger.Info().Msg("link down")
|
|
|
|
if im.config.IPv4Mode.String == "dhcp" {
|
|
im.dhcpClient.Stop()
|
|
}
|
|
|
|
netlinkMgr := getNetlinkManager()
|
|
if err := netlinkMgr.RemoveAllAddresses(im.linkState, link.AfInet); err != nil {
|
|
im.logger.Error().Err(err).Msg("failed to remove all IPv4 addresses")
|
|
}
|
|
|
|
if err := netlinkMgr.RemoveNonLinkLocalIPv6Addresses(im.linkState); err != nil {
|
|
im.logger.Error().Err(err).Msg("failed to remove non-link-local IPv6 addresses")
|
|
}
|
|
}
|
|
|
|
// monitorInterfaceState monitors the interface state and updates accordingly
|
|
func (im *InterfaceManager) monitorInterfaceState() {
|
|
defer im.wg.Done()
|
|
|
|
im.logger.Debug().Msg("monitoring interface state")
|
|
// TODO: use netlink subscription instead of polling
|
|
ticker := time.NewTicker(5 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-im.ctx.Done():
|
|
return
|
|
case <-im.stopCh:
|
|
return
|
|
case <-ticker.C:
|
|
if err := im.updateInterfaceState(); err != nil {
|
|
im.logger.Error().Err(err).Msg("failed to update interface state")
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// updateInterfaceState updates the current interface state
|
|
func (im *InterfaceManager) updateInterfaceState() error {
|
|
nl, err := im.link()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get interface: %w", err)
|
|
}
|
|
|
|
attrs := nl.Attrs()
|
|
isUp := attrs.OperState == netlink.OperUp
|
|
|
|
hasAddrs := false
|
|
addrs, err := nl.AddrList(link.AfUnspec)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get addresses: %w", err)
|
|
}
|
|
if len(addrs) > 0 {
|
|
hasAddrs = true
|
|
}
|
|
|
|
// Check if state changed
|
|
stateChanged := false
|
|
// We should release the lock before calling the callbacks
|
|
// to avoid deadlocks
|
|
im.stateMu.Lock()
|
|
if im.state.Up != isUp {
|
|
im.state.Up = isUp
|
|
stateChanged = true
|
|
}
|
|
if im.state.Online != hasAddrs {
|
|
im.state.Online = hasAddrs
|
|
stateChanged = true
|
|
}
|
|
|
|
if im.state.MACAddress != attrs.HardwareAddr.String() {
|
|
im.state.MACAddress = attrs.HardwareAddr.String()
|
|
stateChanged = true
|
|
}
|
|
|
|
// Update IP addresses
|
|
if err := im.updateIPAddresses(nl); err != nil {
|
|
im.logger.Error().Err(err).Msg("failed to update IP addresses")
|
|
}
|
|
|
|
im.state.LastUpdated = time.Now()
|
|
im.stateMu.Unlock()
|
|
|
|
// Notify callback if state changed
|
|
if stateChanged && im.onStateChange != nil {
|
|
state := *im.state
|
|
im.logger.Debug().Interface("state", state).Msg("notifying state change")
|
|
im.onStateChange(&state)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// updateIPAddresses updates the IP addresses in the state
|
|
func (im *InterfaceManager) updateIPAddresses(nl *link.Link) error {
|
|
if err := nl.Refresh(); err != nil {
|
|
return fmt.Errorf("failed to refresh link: %w", err)
|
|
}
|
|
|
|
addrs, err := nl.AddrList(link.AfUnspec)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get addresses: %w", err)
|
|
}
|
|
|
|
var (
|
|
ipv4Addresses []string
|
|
ipv6Addresses []types.IPv6Address
|
|
ipv4Addr, ipv6Addr string
|
|
ipv6LinkLocal string
|
|
ipv4Ready, ipv6Ready = false, false
|
|
)
|
|
|
|
for _, addr := range addrs {
|
|
im.logger.Debug().Str("address", addr.IP.String()).Msg("checking address")
|
|
if addr.IP.To4() != nil {
|
|
// IPv4 address
|
|
ipv4Addresses = append(ipv4Addresses, addr.IPNet.String())
|
|
if ipv4Addr == "" {
|
|
ipv4Addr = addr.IP.String()
|
|
ipv4Ready = true
|
|
}
|
|
} else if addr.IP.To16() != nil {
|
|
// IPv6 address
|
|
if addr.IP.IsLinkLocalUnicast() {
|
|
ipv6LinkLocal = addr.IP.String()
|
|
} else if addr.IP.IsGlobalUnicast() {
|
|
ipv6Addresses = append(ipv6Addresses, types.IPv6Address{
|
|
Address: addr.IP,
|
|
Prefix: *addr.IPNet,
|
|
Scope: addr.Scope,
|
|
})
|
|
if ipv6Addr == "" {
|
|
ipv6Addr = addr.IP.String()
|
|
ipv6Ready = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
im.state.IPv4Addresses = ipv4Addresses
|
|
im.state.IPv6Addresses = ipv6Addresses
|
|
im.state.IPv6LinkLocal = ipv6LinkLocal
|
|
im.state.IPv4Address = ipv4Addr
|
|
im.state.IPv6Address = ipv6Addr
|
|
im.state.IPv4Ready = ipv4Ready
|
|
im.state.IPv6Ready = ipv6Ready
|
|
|
|
return nil
|
|
}
|
|
|
|
// updateStateFromDHCPLease updates the state from a DHCP lease
|
|
func (im *InterfaceManager) updateStateFromDHCPLease(lease *types.DHCPLease) {
|
|
im.stateMu.Lock()
|
|
im.state.DHCPLease4 = lease
|
|
im.stateMu.Unlock()
|
|
|
|
// Update resolv.conf with DNS information
|
|
if im.resolvConf != nil {
|
|
im.resolvConf.UpdateFromLease(lease)
|
|
}
|
|
}
|
|
|
|
func (im *InterfaceManager) ReconcileLinkAddrs(addrs []*types.IPAddress) error {
|
|
nl := getNetlinkManager()
|
|
link, err := im.link()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get interface: %w", err)
|
|
}
|
|
if link == nil {
|
|
return fmt.Errorf("failed to get interface: %w", err)
|
|
}
|
|
return nl.ReconcileLink(link, addrs)
|
|
}
|
|
|
|
// applyDHCPLease applies DHCP lease configuration using ReconcileLinkAddrs
|
|
func (im *InterfaceManager) applyDHCPLease(lease *types.DHCPLease) error {
|
|
// Convert DHCP lease to IPv4Config
|
|
ipv4Config := im.convertDHCPLeaseToIPv4Config(lease)
|
|
|
|
// Apply the configuration using ReconcileLinkAddrs
|
|
return im.ReconcileLinkAddrs([]*types.IPAddress{ipv4Config})
|
|
}
|
|
|
|
// convertDHCPLeaseToIPv4Config converts a DHCP lease to IPv4Config
|
|
func (im *InterfaceManager) convertDHCPLeaseToIPv4Config(lease *types.DHCPLease) *types.IPAddress {
|
|
ipNet := lease.IPNet()
|
|
if ipNet == nil {
|
|
return nil
|
|
}
|
|
|
|
// Create IPv4Address
|
|
ipv4Addr := types.IPAddress{
|
|
Address: *ipNet,
|
|
Gateway: lease.Routers[0],
|
|
Secondary: false,
|
|
Permanent: false,
|
|
}
|
|
|
|
im.logger.Trace().
|
|
Interface("ipv4Addr", ipv4Addr).
|
|
Interface("lease", lease).
|
|
Msg("converted DHCP lease to IPv4Config")
|
|
|
|
// Create IPv4Config
|
|
return &ipv4Addr
|
|
}
|