diff --git a/internal/confparser/confparser_test.go b/internal/confparser/confparser_test.go index 166cdd7..e14a1ea 100644 --- a/internal/confparser/confparser_test.go +++ b/internal/confparser/confparser_test.go @@ -46,8 +46,8 @@ type testNetworkConfig struct { TimeSyncOrdering []string `json:"time_sync_ordering,omitempty" one_of:"http,ntp,ntp_dhcp,ntp_user_provided,http_user_provided" default:"ntp,http"` TimeSyncDisableFallback null.Bool `json:"time_sync_disable_fallback,omitempty" default:"false"` TimeSyncParallel null.Int `json:"time_sync_parallel,omitempty" default:"4"` - TimeSyncNTPServers []string `json:"time_sync_ntp_servers,omitempty" validate_type:"ipv4_or_ipv6" required_if:"TimeSyncOrdering=ntp_custom"` - TimeSyncHTTPUrls []string `json:"time_sync_http_urls,omitempty" validate_type:"url" required_if:"TimeSyncOrdering=http_custom"` + TimeSyncNTPServers []string `json:"time_sync_ntp_servers,omitempty" validate_type:"ipv4_or_ipv6" required_if:"TimeSyncOrdering=ntp_user_provided"` + TimeSyncHTTPUrls []string `json:"time_sync_http_urls,omitempty" validate_type:"url" required_if:"TimeSyncOrdering=http_user_provided"` } func TestValidateConfig(t *testing.T) { diff --git a/internal/network/config.go b/internal/network/config.go index 34d412e..c8fe582 100644 --- a/internal/network/config.go +++ b/internal/network/config.go @@ -45,11 +45,11 @@ type NetworkConfig struct { LLDPTxTLVs []string `json:"lldp_tx_tlvs,omitempty" one_of:"chassis,port,system,vlan" default:"chassis,port,system,vlan"` MDNSMode null.String `json:"mdns_mode,omitempty" one_of:"disabled,auto,ipv4_only,ipv6_only" default:"auto"` TimeSyncMode null.String `json:"time_sync_mode,omitempty" one_of:"ntp_only,ntp_and_http,http_only,custom" default:"ntp_and_http"` - TimeSyncOrdering []string `json:"time_sync_ordering,omitempty" one_of:"http,ntp,ntp_dhcp,ntp_custom,http_custom" default:"ntp,http"` + TimeSyncOrdering []string `json:"time_sync_ordering,omitempty" one_of:"http,ntp,ntp_dhcp,ntp_user_provided,http_user_provided" default:"ntp,http"` TimeSyncDisableFallback null.Bool `json:"time_sync_disable_fallback,omitempty" default:"false"` TimeSyncParallel null.Int `json:"time_sync_parallel,omitempty" default:"4"` - TimeSyncNTPServers []string `json:"time_sync_ntp_servers,omitempty" validate_type:"ipv4_or_ipv6" required_if:"TimeSyncOrdering=ntp_custom"` - TimeSyncHTTPUrls []string `json:"time_sync_http_urls,omitempty" validate_type:"url" required_if:"TimeSyncOrdering=http_custom"` + TimeSyncNTPServers []string `json:"time_sync_ntp_servers,omitempty" validate_type:"ipv4_or_ipv6" required_if:"TimeSyncOrdering=ntp_user_provided"` + TimeSyncHTTPUrls []string `json:"time_sync_http_urls,omitempty" validate_type:"url" required_if:"TimeSyncOrdering=http_user_provided"` } func (c *NetworkConfig) GetMDNSMode() *mdns.MDNSListenOptions { diff --git a/internal/network/netif.go b/internal/network/netif.go index c5db806..5a8dab6 100644 --- a/internal/network/netif.go +++ b/internal/network/netif.go @@ -21,6 +21,7 @@ type NetworkInterfaceState struct { ipv6Addr *net.IP ipv6Addresses []IPv6Address ipv6LinkLocal *net.IP + ntpAddresses []*net.IP macAddr *net.HardwareAddr l *zerolog.Logger @@ -76,6 +77,7 @@ func NewNetworkInterfaceState(opts *NetworkInterfaceOptions) (*NetworkInterfaceS onInitialCheck: opts.OnInitialCheck, cbConfigChange: opts.OnConfigChange, config: opts.NetworkConfig, + ntpAddresses: make([]*net.IP, 0), } // create the dhcp client @@ -89,7 +91,7 @@ func NewNetworkInterfaceState(opts *NetworkInterfaceOptions) (*NetworkInterfaceS opts.Logger.Error().Err(err).Msg("failed to update network state") return } - + _ = s.updateNtpServersFromLease(lease) _ = s.setHostnameIfNotSame() opts.OnDhcpLeaseChange(lease) @@ -135,6 +137,27 @@ func (s *NetworkInterfaceState) IPv6String() string { return s.ipv6Addr.String() } +func (s *NetworkInterfaceState) NtpAddresses() []*net.IP { + return s.ntpAddresses +} + +func (s *NetworkInterfaceState) NtpAddressesString() []string { + ntpServers := []string{} + + if s != nil { + s.l.Debug().Any("s", s).Msg("getting NTP address strings") + + if len(s.ntpAddresses) > 0 { + for _, server := range s.ntpAddresses { + s.l.Debug().IPAddr("server", *server).Msg("converting NTP address") + ntpServers = append(ntpServers, server.String()) + } + } + } + + return ntpServers +} + func (s *NetworkInterfaceState) MAC() *net.HardwareAddr { return s.macAddr } @@ -318,6 +341,25 @@ func (s *NetworkInterfaceState) update() (DhcpTargetState, error) { return dhcpTargetState, nil } +func (s *NetworkInterfaceState) updateNtpServersFromLease(lease *udhcpc.Lease) error { + if lease != nil && len(lease.NTPServers) > 0 { + s.l.Info().Msg("lease found, updating DHCP NTP addresses") + s.ntpAddresses = make([]*net.IP, 0, len(lease.NTPServers)) + + for _, ntpServer := range lease.NTPServers { + if ntpServer != nil { + s.l.Info().IPAddr("ntp_server", ntpServer).Msg("NTP server found in lease") + s.ntpAddresses = append(s.ntpAddresses, &ntpServer) + } + } + } else { + s.l.Info().Msg("no NTP servers found in lease") + s.ntpAddresses = make([]*net.IP, 0, len(s.config.TimeSyncNTPServers)) + } + + return nil +} + func (s *NetworkInterfaceState) CheckAndUpdateDhcp() error { dhcpTargetState, err := s.update() if err != nil { diff --git a/internal/timesync/timesync.go b/internal/timesync/timesync.go index 64bfcd1..db1c96e 100644 --- a/internal/timesync/timesync.go +++ b/internal/timesync/timesync.go @@ -28,7 +28,8 @@ type TimeSync struct { syncLock *sync.Mutex l *zerolog.Logger - networkConfig *network.NetworkConfig + networkConfig *network.NetworkConfig + dhcpNtpAddresses []string rtcDevicePath string rtcDevice *os.File //nolint:unused @@ -62,12 +63,13 @@ func NewTimeSync(opts *TimeSyncOptions) *TimeSync { } t := &TimeSync{ - syncLock: &sync.Mutex{}, - l: opts.Logger, - rtcDevicePath: rtcDevice, - rtcLock: &sync.Mutex{}, - preCheckFunc: opts.PreCheckFunc, - networkConfig: opts.NetworkConfig, + syncLock: &sync.Mutex{}, + l: opts.Logger, + dhcpNtpAddresses: []string{}, + rtcDevicePath: rtcDevice, + rtcLock: &sync.Mutex{}, + preCheckFunc: opts.PreCheckFunc, + networkConfig: opts.NetworkConfig, } if t.rtcDevicePath != "" { @@ -78,11 +80,15 @@ func NewTimeSync(opts *TimeSyncOptions) *TimeSync { return t } +func (t *TimeSync) SetDhcpNtpAddresses(addresses []string) { + t.dhcpNtpAddresses = addresses +} + func (t *TimeSync) getSyncMode() SyncMode { syncMode := SyncMode{ Ntp: true, Http: true, - Ordering: []string{"ntp_dhcp", "ntp_fallback", "http_fallback"}, + Ordering: []string{"ntp_dhcp", "ntp", "http"}, NtpUseFallback: true, HttpUseFallback: true, } @@ -161,7 +167,7 @@ func (t *TimeSync) Sync() error { Orders: for _, mode := range syncMode.Ordering { switch mode { - case "ntp_custom": + case "ntp_user_provided": if syncMode.Ntp { t.l.Info().Msg("using NTP custom servers") now, offset = t.queryNetworkTime(t.networkConfig.TimeSyncNTPServers) @@ -170,7 +176,15 @@ Orders: break Orders } } - case "ntp_fallback": + case "ntp_dhcp": + if syncMode.Ntp { + t.l.Info().Msg("using NTP servers from DHCP") + now, offset = t.queryNetworkTime(t.dhcpNtpAddresses) + if now != nil { + t.l.Info().Str("source", "NTP DHCP").Time("now", *now).Msg("time obtained") + break Orders + } + } case "ntp": if syncMode.Ntp && syncMode.NtpUseFallback { t.l.Info().Msg("using NTP fallback") @@ -180,7 +194,7 @@ Orders: break Orders } } - case "http_custom": + case "http_user_provided": if syncMode.Http { t.l.Info().Msg("using HTTP custom URLs") now = t.queryAllHttpTime(t.networkConfig.TimeSyncHTTPUrls) diff --git a/network.go b/network.go index 2208a47..211b860 100644 --- a/network.go +++ b/network.go @@ -19,6 +19,14 @@ func networkStateChanged() { // do not block the main thread go waitCtrlAndRequestDisplayUpdate(true) + if timeSync != nil { + if networkState != nil { + timeSync.SetDhcpNtpAddresses(networkState.NtpAddressesString()) + } + + timeSync.Sync() + } + // always restart mDNS when the network state changes if mDNS != nil { _ = mDNS.SetListenOptions(config.NetworkConfig.GetMDNSMode())