ntp: refresh IP addresses periodically

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>
This commit is contained in:
Miroslav Lichvar 2023-06-20 16:23:34 +02:00
parent 36f9b24dfe
commit b86c50bb9f
5 changed files with 103 additions and 1 deletions

13
conf.c
View file

@ -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)
{

2
conf.h
View file

@ -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);

View 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_::

View 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);
}

View file

@ -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