diff --git a/conf.c b/conf.c index bce06fa..f984060 100644 --- a/conf.c +++ b/conf.c @@ -252,6 +252,9 @@ static char *leapsec_tz = NULL; /* Name of the user to which will be dropped root privileges. */ static char *user; +/* Address refresh interval */ +static int refresh = 1209600; /* 2 weeks */ + /* NTS server and client configuration */ static char *nts_dump_dir = NULL; static char *nts_ntp_server = NULL; @@ -702,6 +705,8 @@ CNF_ParseLine(const char *filename, int number, char *line) &ntp_ratelimit_burst, &ntp_ratelimit_leak); } else if (!strcasecmp(command, "refclock")) { parse_refclock(p); + } else if (!strcasecmp(command, "refresh")) { + parse_int(p, &refresh); } else if (!strcasecmp(command, "reselectdist")) { parse_double(p, &reselect_distance); } else if (!strcasecmp(command, "rtcautotrim")) { @@ -2533,6 +2538,14 @@ CNF_GetPtpPort(void) /* ================================================== */ +int +CNF_GetRefresh(void) +{ + return refresh; +} + +/* ================================================== */ + char * CNF_GetNtsDumpDir(void) { diff --git a/conf.h b/conf.h index ca18abc..58ebdeb 100644 --- a/conf.h +++ b/conf.h @@ -159,6 +159,8 @@ extern double CNF_GetHwTsTimeout(void); extern int CNF_GetPtpPort(void); +extern int CNF_GetRefresh(void); + extern char *CNF_GetNtsDumpDir(void); extern char *CNF_GetNtsNtpServer(void); extern int CNF_GetNtsServerCertAndKeyFiles(const char ***certs, const char ***keys); diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc index 8bba225..e4d6118 100644 --- a/doc/chrony.conf.adoc +++ b/doc/chrony.conf.adoc @@ -884,6 +884,19 @@ This would disable the time checks until the clock is updated for the first time, assuming the first update corrects the clock and later checks can work with correct time. +[[refresh]]*refresh* _interval_:: +This directive specifies the interval (in seconds) between refreshing IP +addresses of NTP sources specified by hostname. If the hostname no longer +resolves to the currently used address, it will be replaced with one of the new +addresses to avoid using a server which is no longer intended for service, even +if it is still responding correctly and would not be replaced as unreachable. +Only one source is refreshed at a time. The default value is 1209600 (2 weeks) +and the maximum value is 2^31-1 (68 years). A value of 0 disables the periodic +refreshment. ++ +The <> command can be used to refresh all +sources immediately. + === Source selection [[authselectmode]]*authselectmode* _mode_:: diff --git a/ntp_sources.c b/ntp_sources.c index 7c2a99d..0ffa972 100644 --- a/ntp_sources.c +++ b/ntp_sources.c @@ -32,6 +32,7 @@ #include "sysincl.h" #include "array.h" +#include "conf.h" #include "ntp_sources.h" #include "ntp_core.h" #include "ntp_io.h" @@ -64,6 +65,7 @@ typedef struct { received from the source yet */ uint32_t conf_id; /* Configuration ID, which can be shared with different sources in case of a pool */ + double last_resolving; /* Time of last name resolving (monotonic) */ } SourceRecord; /* Hash table of SourceRecord, its size is a power of two and it's never @@ -389,6 +391,7 @@ add_source(NTP_Remote_Address *remote_addr, char *name, NTP_Source_Type type, record->pool_id = pool_id; record->tentative = 1; record->conf_id = conf_id; + record->last_resolving = SCH_GetLastEventMonoTime(); record_lock = 0; @@ -985,9 +988,11 @@ resolve_source_replacement(SourceRecord *record, int refreshment) { struct UnresolvedSource *us; - DEBUG_LOG("trying to replace %s (%s)", + DEBUG_LOG("%s %s (%s)", refreshment ? "refreshing" : "trying to replace", UTI_IPToString(&record->remote_addr->ip_addr), record->name); + record->last_resolving = SCH_GetLastEventMonoTime(); + us = MallocNew(struct UnresolvedSource); us->name = Strdup(record->name); /* Ignore the order of addresses from the resolver to not get @@ -1042,6 +1047,45 @@ NSR_HandleBadSource(IPAddr *address) /* ================================================== */ +static void +maybe_refresh_source(void) +{ + static double last_refreshment = 0.0; + SourceRecord *record, *oldest_record; + int i, min_interval; + double now; + + min_interval = CNF_GetRefresh(); + + now = SCH_GetLastEventMonoTime(); + if (min_interval <= 0 || now < last_refreshment + min_interval) + return; + + last_refreshment = now; + + for (i = 0, oldest_record = NULL; i < ARR_GetSize(records); i++) { + record = get_record(i); + if (!record->remote_addr || UTI_IsStringIP(record->name)) + continue; + + if (!oldest_record || oldest_record->last_resolving > record->last_resolving) + oldest_record = record; + } + + if (!oldest_record) + return; + + /* Check if the name wasn't already resolved in the last interval */ + if (now < oldest_record->last_resolving + min_interval) { + last_refreshment = oldest_record->last_resolving; + return; + } + + resolve_source_replacement(oldest_record, 1); +} + +/* ================================================== */ + void NSR_RefreshAddresses(void) { @@ -1179,6 +1223,8 @@ NSR_ProcessRx(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr, remove_pool_sources(record->pool_id, 1, 0); } } + + maybe_refresh_source(); } else { NCR_ProcessRxUnknown(remote_addr, local_addr, rx_ts, message, length); } diff --git a/test/simulation/147-refresh b/test/simulation/147-refresh index f83a9c6..ea091e6 100755 --- a/test/simulation/147-refresh +++ b/test/simulation/147-refresh @@ -25,7 +25,35 @@ check_file_messages "20.*192.168.123.2" 15 17 measurements.log || test_fail check_file_messages "20.*192.168.123.[345]" 31 33 measurements.log || test_fail rm -f tmp/measurements.log if check_config_h 'FEAT_DEBUG 1'; then + check_log_messages "refreshing 192.168.123" 3 3 || test_fail check_log_messages "resolved_name.*still fresh" 3 3 || test_fail fi +limit=1100 +client_server_conf=" +server nodes-1-2.net1.clk maxpoll 6 +pool nodes-3-4-5.net1.clk maxpoll 6 maxsources 3" +client_conf+=" +refresh 128" +chronyc_conf="" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +check_file_messages "20.*192.168.123.1" 0 0 measurements.log || test_fail +check_file_messages "20.*192.168.123.2" 16 18 measurements.log || test_fail +check_file_messages "20.*192.168.123.[345]" 50 55 measurements.log || test_fail +rm -f tmp/measurements.log +if check_config_h 'FEAT_DEBUG 1'; then + check_log_messages "refreshing 192.168.123" 8 8 || test_fail + check_log_messages "resolved_name.*still fresh" 8 8 || test_fail + check_log_messages "refreshing 192.168.123.2" 2 2 || test_fail + check_log_messages "refreshing 192.168.123.3" 2 2 || test_fail + check_log_messages "refreshing 192.168.123.4" 2 2 || test_fail + check_log_messages "refreshing 192.168.123.5" 2 2 || test_fail +fi + test_pass