diff --git a/internal/timesync/ntp.go b/internal/timesync/ntp.go index dcadaa96..e5a18865 100644 --- a/internal/timesync/ntp.go +++ b/internal/timesync/ntp.go @@ -3,6 +3,7 @@ package timesync import ( "context" "math/rand/v2" + "net" "strconv" "time" @@ -37,7 +38,47 @@ var DefaultNTPServerHostnames = []string{ "pool.ntp.org", } +func (t *TimeSync) filterNTPServers(ntpServers []string) ([]string, error) { + if len(ntpServers) == 0 { + return nil, nil + } + + hasIPv4, err := t.preCheckIPv4() + if err != nil { + t.l.Error().Err(err).Msg("failed to check IPv4") + return nil, err + } + + hasIPv6, err := t.preCheckIPv6() + if err != nil { + t.l.Error().Err(err).Msg("failed to check IPv6") + return nil, err + } + + filteredServers := []string{} + for _, server := range ntpServers { + ip := net.ParseIP(server) + t.l.Trace().Str("server", server).Interface("ip", ip).Msg("checking NTP server") + if ip == nil { + continue + } + if hasIPv4 && ip.To4() != nil { + filteredServers = append(filteredServers, server) + } + if hasIPv6 && ip.To16() != nil { + filteredServers = append(filteredServers, server) + } + } + return filteredServers, nil +} + func (t *TimeSync) queryNetworkTime(ntpServers []string) (now *time.Time, offset *time.Duration) { + ntpServers, err := t.filterNTPServers(ntpServers) + if err != nil { + t.l.Error().Err(err).Msg("failed to filter NTP servers") + return nil, nil + } + chunkSize := int(t.networkConfig.TimeSyncParallel.ValueOr(4)) t.l.Info().Strs("servers", ntpServers).Int("chunkSize", chunkSize).Msg("querying NTP servers") diff --git a/internal/timesync/timesync.go b/internal/timesync/timesync.go index ad5f6f49..d93401e0 100644 --- a/internal/timesync/timesync.go +++ b/internal/timesync/timesync.go @@ -24,6 +24,8 @@ var ( timeSyncRetryInterval = 0 * time.Second ) +type PreCheckFunc func() (bool, error) + type TimeSync struct { syncLock *sync.Mutex l *zerolog.Logger @@ -37,11 +39,15 @@ type TimeSync struct { syncSuccess bool - preCheckFunc func() (bool, error) + preCheckFunc PreCheckFunc + preCheckIPv4 PreCheckFunc + preCheckIPv6 PreCheckFunc } type TimeSyncOptions struct { - PreCheckFunc func() (bool, error) + PreCheckFunc PreCheckFunc + PreCheckIPv4 PreCheckFunc + PreCheckIPv6 PreCheckFunc Logger *zerolog.Logger NetworkConfig *types.NetworkConfig } @@ -69,6 +75,8 @@ func NewTimeSync(opts *TimeSyncOptions) *TimeSync { rtcDevicePath: rtcDevice, rtcLock: &sync.Mutex{}, preCheckFunc: opts.PreCheckFunc, + preCheckIPv4: opts.PreCheckIPv4, + preCheckIPv6: opts.PreCheckIPv6, networkConfig: opts.NetworkConfig, } @@ -112,7 +120,13 @@ func (t *TimeSync) getSyncMode() SyncMode { } } - t.l.Debug().Strs("Ordering", syncMode.Ordering).Bool("Ntp", syncMode.Ntp).Bool("Http", syncMode.Http).Bool("NtpUseFallback", syncMode.NtpUseFallback).Bool("HttpUseFallback", syncMode.HttpUseFallback).Msg("sync mode") + t.l.Debug(). + Strs("Ordering", syncMode.Ordering). + Bool("Ntp", syncMode.Ntp). + Bool("Http", syncMode.Http). + Bool("NtpUseFallback", syncMode.NtpUseFallback). + Bool("HttpUseFallback", syncMode.HttpUseFallback). + Msg("sync mode") return syncMode } diff --git a/pkg/nmlite/interface.go b/pkg/nmlite/interface.go index 102dc864..324a0bf0 100644 --- a/pkg/nmlite/interface.go +++ b/pkg/nmlite/interface.go @@ -152,30 +152,63 @@ func (im *InterfaceManager) link() (*link.Link, error) { // IsUp returns true if the interface is up func (im *InterfaceManager) IsUp() bool { + im.stateMu.RLock() + defer im.stateMu.RUnlock() + return im.state.Up } func (im *InterfaceManager) IsOnline() bool { - return im.IsUp() + im.stateMu.RLock() + defer im.stateMu.RUnlock() + + return im.state.Online +} + +func (im *InterfaceManager) IPv4Ready() bool { + im.stateMu.RLock() + defer im.stateMu.RUnlock() + + return im.state.IPv4Ready +} + +func (im *InterfaceManager) IPv6Ready() bool { + im.stateMu.RLock() + defer im.stateMu.RUnlock() + + return im.state.IPv6Ready } func (im *InterfaceManager) GetIPv4Addresses() []string { + im.stateMu.RLock() + defer im.stateMu.RUnlock() + return im.state.IPv4Addresses } func (im *InterfaceManager) GetIPv4Address() string { + im.stateMu.RLock() + defer im.stateMu.RUnlock() + return im.state.IPv4Address } func (im *InterfaceManager) GetIPv6Address() string { + im.stateMu.RLock() + defer im.stateMu.RUnlock() + return im.state.IPv6Address } 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{} } @@ -569,11 +602,11 @@ func (im *InterfaceManager) updateInterfaceState() error { hasAddrs = true } - im.stateMu.Lock() - defer im.stateMu.Unlock() - // 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 @@ -594,6 +627,7 @@ func (im *InterfaceManager) updateInterfaceState() error { } im.state.LastUpdated = time.Now() + im.stateMu.Unlock() // Notify callback if state changed if stateChanged && im.onStateChange != nil { @@ -661,9 +695,8 @@ func (im *InterfaceManager) updateIPAddresses(nl *link.Link) error { // updateStateFromDHCPLease updates the state from a DHCP lease func (im *InterfaceManager) updateStateFromDHCPLease(lease *types.DHCPLease) { im.stateMu.Lock() - defer im.stateMu.Unlock() - im.state.DHCPLease4 = lease + im.stateMu.Unlock() // Update resolv.conf with DNS information if im.resolvConf != nil { diff --git a/pkg/nmlite/state.go b/pkg/nmlite/state.go index 48c080bd..517a44ac 100644 --- a/pkg/nmlite/state.go +++ b/pkg/nmlite/state.go @@ -12,7 +12,12 @@ func (nm *NetworkManager) IsOnline() bool { } func (nm *NetworkManager) IsUp() bool { - return nm.IsOnline() + for _, iface := range nm.interfaces { + if iface.IsUp() { + return true + } + } + return false } func (nm *NetworkManager) GetHostname() string { @@ -74,6 +79,20 @@ func (nm *NetworkManager) GetMACAddress() string { return "" } +func (nm *NetworkManager) IPv4Ready() bool { + for _, iface := range nm.interfaces { + return iface.IPv4Ready() + } + return false +} + +func (nm *NetworkManager) IPv6Ready() bool { + for _, iface := range nm.interfaces { + return iface.IPv6Ready() + } + return false +} + func (nm *NetworkManager) IPv4String() string { return nm.GetIPv4Address() } diff --git a/timesync.go b/timesync.go index abb427e0..956011b3 100644 --- a/timesync.go +++ b/timesync.go @@ -43,6 +43,18 @@ func initTimeSync() { timeSync = timesync.NewTimeSync(×ync.TimeSyncOptions{ Logger: timesyncLogger, NetworkConfig: config.NetworkConfig, + PreCheckIPv4: func() (bool, error) { + if !networkManager.IPv4Ready() { + return false, nil + } + return true, nil + }, + PreCheckIPv6: func() (bool, error) { + if !networkManager.IPv6Ready() { + return false, nil + } + return true, nil + }, PreCheckFunc: func() (bool, error) { if !networkManager.IsOnline() { return false, nil