From b86c50bb9f9062e1a02a8f35bd22b079dd5fdda9 Mon Sep 17 00:00:00 2001 From: Miroslav Lichvar Date: Tue, 20 Jun 2023 16:23:34 +0200 Subject: [PATCH] ntp: refresh IP addresses periodically MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refresh NTP sources specified by hostname periodically (every 2 weeks by default) to avoid long-running instances using a server which is no longer intended for service, even if it is still responding correctly and would not be replaced as unreachable, and help redistributing load in large pools like pool.ntp.org. Only one source is refreshed at a time to not interrupt clock updates if there are multiple selectable servers. The refresh directive configures the interval. A value of 0 disables the periodic refreshment. Suggested-by: Ask Bjørn Hansen --- conf.c | 13 ++++++++++ conf.h | 2 ++ doc/chrony.conf.adoc | 13 ++++++++++ ntp_sources.c | 48 ++++++++++++++++++++++++++++++++++++- test/simulation/147-refresh | 28 ++++++++++++++++++++++ 5 files changed, 103 insertions(+), 1 deletion(-) 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