diff --git a/candm.h b/candm.h index c120b0b..34024ef 100644 --- a/candm.h +++ b/candm.h @@ -306,7 +306,7 @@ typedef struct { int32_t filter_length; uint32_t cert_set; Float max_delay_quant; - uint32_t reserved[1]; + int32_t max_unreach; int32_t EOR; } REQ_NTP_Source; diff --git a/client.c b/client.c index 1701dfd..10d846d 100644 --- a/client.c +++ b/client.c @@ -1104,7 +1104,7 @@ process_cmd_add_source(CMD_Request *msg, char *line) msg->data.ntp_source.cert_set = htonl(data.params.cert_set); msg->data.ntp_source.max_delay_quant = UTI_FloatHostToNetwork(data.params.max_delay_quant); - memset(msg->data.ntp_source.reserved, 0, sizeof (msg->data.ntp_source.reserved)); + msg->data.ntp_source.max_unreach = htonl(data.params.max_unreach); result = 1; diff --git a/cmdmon.c b/cmdmon.c index e90a949..a21c3ad 100644 --- a/cmdmon.c +++ b/cmdmon.c @@ -686,6 +686,7 @@ handle_add_source(CMD_Request *rx_message, CMD_Reply *tx_message) params.max_sources = ntohl(rx_message->data.ntp_source.max_sources); params.min_samples = ntohl(rx_message->data.ntp_source.min_samples); params.max_samples = ntohl(rx_message->data.ntp_source.max_samples); + params.max_unreach = ntohl(rx_message->data.ntp_source.max_unreach); params.filter_length = ntohl(rx_message->data.ntp_source.filter_length); params.authkey = ntohl(rx_message->data.ntp_source.authkey); params.nts_port = ntohl(rx_message->data.ntp_source.nts_port); diff --git a/cmdparse.c b/cmdparse.c index e84bca9..2e109fb 100644 --- a/cmdparse.c +++ b/cmdparse.c @@ -66,6 +66,7 @@ CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src) src->params.max_sources = SRC_DEFAULT_MAXSOURCES; src->params.min_samples = SRC_DEFAULT_MINSAMPLES; src->params.max_samples = SRC_DEFAULT_MAXSAMPLES; + src->params.max_unreach = SRC_DEFAULT_MAXUNREACH; src->params.filter_length = 0; src->params.interleaved = 0; src->params.sel_options = 0; @@ -158,6 +159,9 @@ CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src) } else if (!strcasecmp(cmd, "maxsources")) { if (!SSCANF_IN_RANGE(line, "%d%n", &src->params.max_sources, &n, 1, INT_MAX)) return CPS_InvalidValue; + } else if (!strcasecmp(cmd, "maxunreach")) { + if (!SSCANF_IN_RANGE(line, "%d%n", &src->params.max_unreach, &n, 0, INT_MAX)) + return CPS_InvalidValue; } else if (!strcasecmp(cmd, "mindelay")) { if (sscanf(line, "%lf%n", &src->params.min_delay, &n) != 1) return CPS_InvalidValue; diff --git a/conf.c b/conf.c index 3fe1d1f..1f362d7 100644 --- a/conf.c +++ b/conf.c @@ -958,7 +958,7 @@ static void parse_refclock(char *line) { int n, poll, dpoll, filter_length, pps_rate, min_samples, max_samples, sel_options; - int local, max_lock_age, pps_forced, sel_option, stratum, tai; + int local, max_lock_age, max_unreach, pps_forced, sel_option, stratum, tai; uint32_t ref_id, lock_ref_id; double offset, delay, precision, max_dispersion, pulse_width; char *p, *cmd, *name, *param; @@ -972,6 +972,7 @@ parse_refclock(char *line) pps_rate = 0; min_samples = SRC_DEFAULT_MINSAMPLES; max_samples = SRC_DEFAULT_MAXSAMPLES; + max_unreach = SRC_DEFAULT_MAXUNREACH; sel_options = 0; offset = 0.0; delay = 1e-9; @@ -1036,6 +1037,9 @@ parse_refclock(char *line) } else if (!strcasecmp(cmd, "maxsamples")) { if (!SSCANF_IN_RANGE(line, "%d%n", &max_samples, &n, 0, INT_MAX)) break; + } else if (!strcasecmp(cmd, "maxunreach")) { + if (!SSCANF_IN_RANGE(line, "%d%n", &max_unreach, &n, 0, INT_MAX)) + break; } else if (!strcasecmp(cmd, "offset")) { if (sscanf(line, "%lf%n", &offset, &n) != 1) break; @@ -1085,6 +1089,7 @@ parse_refclock(char *line) refclock->pps_rate = pps_rate; refclock->min_samples = min_samples; refclock->max_samples = max_samples; + refclock->max_unreach = max_unreach; refclock->sel_options = sel_options; refclock->stratum = stratum; refclock->tai = tai; diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc index b90cf90..fee500e 100644 --- a/doc/chrony.conf.adoc +++ b/doc/chrony.conf.adoc @@ -218,6 +218,12 @@ Set the minimum number of samples kept for this source. This overrides the *maxsamples* _samples_::: Set the maximum number of samples kept for this source. This overrides the <> directive. +*maxunreach* _polls_::: +This option specifies the maximum number of polls that this source can stay +selected for synchronisation when it is unreachable (i.e. no valid response was +received to the last 8 requests). Only sources with at least one sample more +recent than the oldest sample of all reachable sources can be selected. The +default is 100000. *filter* _polls_::: This option enables a median filter to reduce noise in NTP measurements. The filter will process samples collected in the specified number of polls @@ -731,6 +737,12 @@ Set the minimum number of samples kept for this source. This overrides the *maxsamples* _samples_::: Set the maximum number of samples kept for this source. This overrides the <> directive. +*maxunreach* _polls_::: +This option specifies the maximum number of polls that this source can stay +selected for synchronisation when it is unreachable (i.e. no valid sample was +received in the last 8 polls). Only sources with at least one sample more +recent than the oldest sample of all reachable sources can be selected. +The default is 100000. [[manual]]*manual*:: The *manual* directive enables support at run-time for the diff --git a/doc/chronyc.adoc b/doc/chronyc.adoc index 36790da..4f9e8be 100644 --- a/doc/chronyc.adoc +++ b/doc/chronyc.adoc @@ -479,7 +479,8 @@ synchronisation: * _~_ - has a jitter larger than the maximum jitter (configured by the <> directive). * _w_ - waits for other sources to get out of the _M_ state. -* _S_ - has older measurements than other sources. +* _S_ - has only measurements older than reachable sources, or is unreachable + for too many polls (configured by the *maxunreach* option). * _O_ - has a stratum equal or larger than the orphan stratum (configured by the <> directive). * _T_ - does not fully agree with sources that have the *trust* option. diff --git a/ntp_core.c b/ntp_core.c index 501ea6d..7395a63 100644 --- a/ntp_core.c +++ b/ntp_core.c @@ -686,7 +686,8 @@ NCR_CreateInstance(NTP_Remote_Address *remote_addr, NTP_Source_Type type, SRC_NTP, NAU_IsAuthEnabled(result->auth), params->sel_options, &result->remote_addr.ip_addr, params->min_samples, params->max_samples, - params->min_delay, params->asymmetry); + params->min_delay, params->asymmetry, + params->max_unreach); if (params->max_delay_quant > 0.0) { int k = round(CLAMP(0.05, params->max_delay_quant, 0.95) * DELAY_QUANT_Q); diff --git a/refclock.c b/refclock.c index 2cc4f62..c4f16f2 100644 --- a/refclock.c +++ b/refclock.c @@ -244,7 +244,7 @@ RCL_AddRefclock(RefclockParameters *params) inst->source = SRC_CreateNewInstance(inst->ref_id, SRC_REFCLOCK, 0, params->sel_options, NULL, params->min_samples, params->max_samples, - 0.0, 0.0); + 0.0, 0.0, params->max_unreach); DEBUG_LOG("refclock %s refid=%s poll=%d dpoll=%d filter=%d", params->driver_name, UTI_RefidToString(inst->ref_id), diff --git a/refclock.h b/refclock.h index 5fdbf9c..1f4d29c 100644 --- a/refclock.h +++ b/refclock.h @@ -42,6 +42,7 @@ typedef struct { int pps_rate; int min_samples; int max_samples; + int max_unreach; int sel_options; int max_lock_age; int stratum; diff --git a/sources.c b/sources.c index 58a7110..dc8de91 100644 --- a/sources.c +++ b/sources.c @@ -106,13 +106,20 @@ struct SRC_Instance_Record { /* Number of set bits in the reachability register */ int reachability_size; - /* Updates since last reference update */ + /* Number of reachability updates with cleared register */ + int unreachable_run; + + /* Maximum number of reachability updates with cleared register to still + allow selection */ + int max_unreachable_run; + + /* Number of selection updates since last reference update */ int updates; - /* Updates left before allowing combining */ + /* Number of selection updates left before allowing combining again */ int distant; - /* Updates with a status requiring source replacement */ + /* Number of selection updates with a status requiring source replacement */ int bad; /* Flag indicating the status of the source */ @@ -259,7 +266,8 @@ void SRC_Finalise(void) SRC_Instance SRC_CreateNewInstance(uint32_t ref_id, SRC_Type type, int authenticated, int sel_options, IPAddr *addr, int min_samples, - int max_samples, double min_delay, double asymmetry) + int max_samples, double min_delay, double asymmetry, + int max_unreach) { SRC_Instance result; @@ -295,6 +303,7 @@ SRC_Instance SRC_CreateNewInstance(uint32_t ref_id, SRC_Type type, int authentic result->authenticated = authenticated; result->conf_sel_options = sel_options; result->sel_options = sel_options; + result->max_unreachable_run = max_unreach; result->active = 0; SRC_SetRefid(result, ref_id, addr); @@ -351,6 +360,7 @@ SRC_ResetInstance(SRC_Instance instance) instance->updates = 0; instance->reachability = 0; instance->reachability_size = 0; + instance->unreachable_run = 0; instance->distant = 0; instance->bad = 0; instance->status = SRC_BAD_STATS; @@ -524,6 +534,13 @@ SRC_UpdateReachability(SRC_Instance inst, int reachable) if (inst->reachability_size < SOURCE_REACH_BITS) inst->reachability_size++; + if (inst->reachability == 0) { + if (inst->unreachable_run < INT_MAX) + inst->unreachable_run++; + } else { + inst->unreachable_run = 0; + } + /* Check if special reference update mode failed */ if (REF_GetMode() != REF_ModeNormal && special_mode_end()) { REF_SetUnsynchronised(); @@ -545,6 +562,7 @@ SRC_ResetReachability(SRC_Instance inst) { inst->reachability = 0; inst->reachability_size = 0; + inst->unreachable_run = 0; SRC_UpdateReachability(inst, 0); } @@ -1059,8 +1077,11 @@ SRC_SelectSource(SRC_Instance updated_inst) /* Reachability is not a requirement for selection. An unreachable source can still be selected if its newest sample is not older than the oldest - sample from reachable sources. */ - if (!sources[i]->reachability && max_reach_sample_ago < si->last_sample_ago) { + sample from reachable sources and the number of consecutive unreachable + updates does not exceed the configured maximum. */ + if (sources[i]->reachability == 0 && + (si->last_sample_ago > max_reach_sample_ago || + sources[i]->unreachable_run > sources[i]->max_unreachable_run)) { mark_source(sources[i], SRC_STALE); continue; } diff --git a/sources.h b/sources.h index da3a533..89cf0ad 100644 --- a/sources.h +++ b/sources.h @@ -69,7 +69,8 @@ typedef enum { extern SRC_Instance SRC_CreateNewInstance(uint32_t ref_id, SRC_Type type, int authenticated, int sel_options, IPAddr *addr, int min_samples, - int max_samples, double min_delay, double asymmetry); + int max_samples, double min_delay, double asymmetry, + int max_unreach); /* Function to get rid of a source when it is being unconfigured. This may cause the current reference source to be reselected, if this diff --git a/srcparams.h b/srcparams.h index 31baed7..8a5a999 100644 --- a/srcparams.h +++ b/srcparams.h @@ -49,6 +49,7 @@ typedef struct { int max_sources; int min_samples; int max_samples; + int max_unreach; int filter_length; int interleaved; int sel_options; @@ -79,6 +80,7 @@ typedef struct { #define SRC_DEFAULT_MAXSOURCES 4 #define SRC_DEFAULT_MINSAMPLES (-1) #define SRC_DEFAULT_MAXSAMPLES (-1) +#define SRC_DEFAULT_MAXUNREACH 100000 #define SRC_DEFAULT_ASYMMETRY 1.0 #define SRC_DEFAULT_NTSPORT 4460 #define SRC_DEFAULT_CERTSET 0 diff --git a/test/simulation/110-chronyc b/test/simulation/110-chronyc index 1872d5c..448c89b 100755 --- a/test/simulation/110-chronyc +++ b/test/simulation/110-chronyc @@ -114,7 +114,7 @@ limit=1 for chronyc_conf in \ "accheck 1.2.3.4" \ "add peer 10.0.0.0 minpoll 2 maxpoll 6" \ - "add server 10.0.0.0 minpoll 6 maxpoll 10 iburst burst key 1 certset 2 maxdelay 1e-3 maxdelayratio 10.0 maxdelaydevratio 10.0 maxdelayquant 0.5 mindelay 1e-4 asymmetry 0.5 offset 1e-5 minsamples 6 maxsamples 6 filter 3 offline auto_offline prefer noselect trust require xleave polltarget 20 port 123 presend 7 minstratum 3 version 4 nts ntsport 4460 copy extfield F323 extfield F324 ipv6 ipv4" \ + "add server 10.0.0.0 minpoll 6 maxpoll 10 iburst burst key 1 certset 2 maxdelay 1e-3 maxdelayratio 10.0 maxdelaydevratio 10.0 maxdelayquant 0.5 mindelay 1e-4 asymmetry 0.5 offset 1e-5 minsamples 6 maxsamples 6 maxunreach 8 filter 3 offline auto_offline prefer noselect trust require xleave polltarget 20 port 123 presend 7 minstratum 3 version 4 nts ntsport 4460 copy extfield F323 extfield F324 ipv6 ipv4" \ "add server node1.net1.clk" \ "allow 1.2.3.4" \ "allow 1.2" \ diff --git a/test/simulation/150-maxunreach b/test/simulation/150-maxunreach new file mode 100755 index 0000000..faf730c --- /dev/null +++ b/test/simulation/150-maxunreach @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "maxunreach option" + +limit=5000 +servers=2 +client_server_options="minpoll 6 maxpoll 6 minsamples 64" +base_delay=$(cat <<-EOF | tr -d '\n' + (+ 1e-4 + (* -1 + (equal 0.1 from 3) + (equal 0.1 to 1) + (equal 0.1 (min time 2000) 2000)) + (* 0.5 + (+ (equal 0.1 from 2) + (equal 0.1 to 2)))) +EOF +) + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +check_log_messages "Selected source 192.168.123.1" 1 1 || test_fail +check_log_messages "Selected source 192.168.123.2" 0 0 || test_fail + +client_server_options="minpoll 6 maxpoll 6 minsamples 64 maxunreach 10" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +check_log_messages "Selected source 192.168.123.1" 1 1 || test_fail +check_log_messages "Selected source 192.168.123.2" 1 1 || test_fail +check_log_messages "00:52:..Z Selected source 192.168.123.2" 1 1 || test_fail + +test_pass diff --git a/test/unit/sources.c b/test/unit/sources.c index 155e819..c7216bb 100644 --- a/test/unit/sources.c +++ b/test/unit/sources.c @@ -28,7 +28,8 @@ create_source(SRC_Type type, IPAddr *addr, int authenticated, int sel_options) return SRC_CreateNewInstance(UTI_IPToRefid(addr), type, authenticated, sel_options, type == SRC_NTP ? addr : NULL, - SRC_DEFAULT_MINSAMPLES, SRC_DEFAULT_MAXSAMPLES, 0.0, 1.0); + SRC_DEFAULT_MINSAMPLES, SRC_DEFAULT_MAXSAMPLES, 0.0, 1.0, + SRC_DEFAULT_MAXUNREACH); } void