]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
ntp: refresh IP addresses periodically
authorMiroslav Lichvar <mlichvar@redhat.com>
Tue, 20 Jun 2023 14:23:34 +0000 (16:23 +0200)
committerMiroslav Lichvar <mlichvar@redhat.com>
Wed, 21 Jun 2023 09:28:42 +0000 (11:28 +0200)
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 <ask@develooper.com>
conf.c
conf.h
doc/chrony.conf.adoc
ntp_sources.c
test/simulation/147-refresh

diff --git a/conf.c b/conf.c
index bce06fa54046da41c9cad035ac72e7ac77d2654f..f98406098b5b261d8b219fa80a8a4b9d560c1184 100644 (file)
--- 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 ca18abc65bbbb31a19f8b3b0e877d239a0de6dce..58ebdeb0cc0044524ecce67590a6925feb46a89a 100644 (file)
--- 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);
index 8bba2250b2dc6cde5dd6c114956b9511a4f44e75..e4d611818c991ed03c5726e078cf003f5a508724 100644 (file)
@@ -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 <<chronyc.adoc#refresh,*refresh*>> command can be used to refresh all
+sources immediately.
+
 === Source selection
 
 [[authselectmode]]*authselectmode* _mode_::
index 7c2a99dde47efd6301a0d1e1d5b8937314794298..0ffa972d7923f4409f4f38875125ce85eb18d3da 100644 (file)
@@ -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);
   }
index f83a9c6d548ad1d7ab5cd2b552a9ca95e6064484..ea091e6d8370f8679b2af7cfbd1f194a4e52ee64 100755 (executable)
@@ -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