diff --git a/candm.h b/candm.h index a8004e2..c120b0b 100644 --- a/candm.h +++ b/candm.h @@ -233,7 +233,8 @@ typedef struct { Float distance; int32_t orphan; Float activate; - uint32_t reserved[2]; + Float wait_synced; + Float wait_unsynced; int32_t EOR; } REQ_Local; diff --git a/client.c b/client.c index 787559a..32ca958 100644 --- a/client.c +++ b/client.c @@ -753,12 +753,13 @@ process_cmd_burst(CMD_Request *msg, char *line) static int process_cmd_local(CMD_Request *msg, char *line) { + double distance = 0.0, activate = 0.0, wait_synced = 0.0, wait_unsynced = 0.0; int on_off, stratum = 0, orphan = 0; - double distance = 0.0, activate = 0.0; if (!strcmp(line, "off")) { on_off = 0; - } else if (CPS_ParseLocal(line, &stratum, &orphan, &distance, &activate)) { + } else if (CPS_ParseLocal(line, &stratum, &orphan, &distance, &activate, + &wait_synced, &wait_unsynced)) { on_off = 1; } else { LOG(LOGS_ERR, "Invalid syntax for local command"); @@ -771,7 +772,8 @@ process_cmd_local(CMD_Request *msg, char *line) msg->data.local.distance = UTI_FloatHostToNetwork(distance); msg->data.local.orphan = htonl(orphan); msg->data.local.activate = UTI_FloatHostToNetwork(activate); - memset(msg->data.local.reserved, 0, sizeof (msg->data.local.reserved)); + msg->data.local.wait_synced = UTI_FloatHostToNetwork(wait_synced); + msg->data.local.wait_unsynced = UTI_FloatHostToNetwork(wait_unsynced); return 1; } diff --git a/cmdmon.c b/cmdmon.c index 18d955f..a651c82 100644 --- a/cmdmon.c +++ b/cmdmon.c @@ -449,7 +449,9 @@ handle_local(CMD_Request *rx_message, CMD_Reply *tx_message) REF_EnableLocal(ntohl(rx_message->data.local.stratum), UTI_FloatNetworkToHost(rx_message->data.local.distance), ntohl(rx_message->data.local.orphan), - UTI_FloatNetworkToHost(rx_message->data.local.activate)); + UTI_FloatNetworkToHost(rx_message->data.local.activate), + UTI_FloatNetworkToHost(rx_message->data.local.wait_synced), + UTI_FloatNetworkToHost(rx_message->data.local.wait_unsynced)); } else { REF_DisableLocal(); } diff --git a/cmdparse.c b/cmdparse.c index 77447dc..1cf3740 100644 --- a/cmdparse.c +++ b/cmdparse.c @@ -296,7 +296,8 @@ CPS_ParseAllowDeny(char *line, int *all, IPAddr *ip, int *subnet_bits) /* ================================================== */ int -CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance, double *activate) +CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance, double *activate, + double *wait_synced, double *wait_unsynced) { int n; char *cmd; @@ -305,6 +306,8 @@ CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance, double * *distance = 1.0; *activate = 0.0; *orphan = 0; + *wait_synced = 0; + *wait_unsynced = -1.0; while (*line) { cmd = line; @@ -323,6 +326,12 @@ CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance, double * } else if (!strcasecmp(cmd, "activate")) { if (sscanf(line, "%lf%n", activate, &n) != 1) return 0; + } else if (!strcasecmp(cmd, "waitsynced")) { + if (sscanf(line, "%lf%n", wait_synced, &n) != 1) + return 0; + } else if (!strcasecmp(cmd, "waitunsynced")) { + if (sscanf(line, "%lf%n", wait_unsynced, &n) != 1) + return 0; } else { return 0; } @@ -330,6 +339,9 @@ CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance, double * line += n; } + if (*wait_unsynced < 0.0) + *wait_unsynced = *orphan ? 300 : 0.0; + return 1; } diff --git a/cmdparse.h b/cmdparse.h index f0bc7a4..dd82a39 100644 --- a/cmdparse.h +++ b/cmdparse.h @@ -47,7 +47,8 @@ extern int CPS_GetSelectOption(char *option); extern int CPS_ParseAllowDeny(char *line, int *all, IPAddr *ip, int *subnet_bits); /* Parse a command to enable local reference */ -extern int CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance, double *activate); +extern int CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance, + double *activate, double *wait_synced, double *wait_unsynced); /* Remove extra white-space and comments */ extern void CPS_NormalizeLine(char *line); diff --git a/conf.c b/conf.c index 7c84e13..e85c775 100644 --- a/conf.c +++ b/conf.c @@ -135,6 +135,8 @@ static int local_stratum; static int local_orphan; static double local_distance; static double local_activate; +static double local_wait_synced; +static double local_wait_unsynced; /* Threshold (in seconds) - if absolute value of initial error is less than this, slew instead of stepping */ @@ -1123,7 +1125,8 @@ parse_log(char *line) static void parse_local(char *line) { - if (!CPS_ParseLocal(line, &local_stratum, &local_orphan, &local_distance, &local_activate)) + if (!CPS_ParseLocal(line, &local_stratum, &local_orphan, &local_distance, + &local_activate, &local_wait_synced, &local_wait_unsynced)) command_parse_error(); enable_local = 1; } @@ -2326,13 +2329,16 @@ CNF_GetCommandPort(void) { /* ================================================== */ int -CNF_AllowLocalReference(int *stratum, int *orphan, double *distance, double *activate) +CNF_AllowLocalReference(int *stratum, int *orphan, double *distance, double *activate, + double *wait_synced, double *wait_unsynced) { if (enable_local) { *stratum = local_stratum; *orphan = local_orphan; *distance = local_distance; *activate = local_activate; + *wait_synced = local_wait_synced; + *wait_unsynced = local_wait_unsynced; return 1; } else { return 0; diff --git a/conf.h b/conf.h index 0eee007..00a1170 100644 --- a/conf.h +++ b/conf.h @@ -110,7 +110,8 @@ extern double CNF_GetReselectDistance(void); extern double CNF_GetStratumWeight(void); extern double CNF_GetCombineLimit(void); -extern int CNF_AllowLocalReference(int *stratum, int *orphan, double *distance, double *activate); +extern int CNF_AllowLocalReference(int *stratum, int *orphan, double *distance, double *activate, + double *wait_synced, double *wait_unsynced); extern void CNF_SetupAccessRestrictions(void); diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc index eb8c1d5..872fe11 100644 --- a/doc/chrony.conf.adoc +++ b/doc/chrony.conf.adoc @@ -1722,10 +1722,13 @@ of 10 indicates that the clock is so many hops away from a reference clock that its time is fairly unreliable. *distance* _distance_::: This option sets the threshold for the root distance which will activate the local -reference. If *chronyd* was synchronised to some source, the local reference -will not be activated until its root distance reaches the specified value (the -rate at which the distance is increasing depends on how well the clock was -tracking the source). The default value is 1 second. +reference. If *chronyd* was synchronised to a configured time source, the local +reference will not be activated until its root distance reaches the specified +value (the rate at which the distance is increasing depends on how well the +clock was tracking the source). When the clock is not synchronised, it is +considered to have an infinite root distance, i.e. the local reference +activates as soon as allowed by the *waitunsynced* option. The default +threshold is 1 second. + The current root distance can be calculated from root delay and root dispersion (reported by the <> command in *chronyc*) as: @@ -1758,12 +1761,30 @@ smallest reference ID will take over when its local reference mode activates + The *orphan* mode is compatible with the *ntpd*'s orphan mode (enabled by the *tos orphan* command). +*waitsynced* _interval_::: +This option specifies the minimum interval (in seconds) between the last update +of the clock and activation of the local reference as configured by the +*distance* and *activate* options. The *distance* option can be set to 0 to +ignore the root distance and control the activation only by the interval. In +such case it should be at least as long as the maximum expected polling +interval to prevent frequent activation in normal polling of the source. +The default minimum interval is 0. +*waitunsynced* _interval_::: +This option specifies how long (in seconds) *chronyd* needs to wait before +activating the local reference when the clock is not considered to be +synchronised (e.g. after start or the source selection failing due to no +majority). This delay prevents *chronyd* from serving incorrect time to clients +before the configured time sources are given a chance to synchronise the local +clock. The default interval is 300 seconds if the *orphan* option is set, +otherwise it is 0 (i.e. local reference activates immediately). {blank}:: + -An example of the directive is: +Examples of the directive are: + ---- +local stratum 5 local stratum 10 orphan distance 0.1 activate 0.5 +local stratum 10 orphan distance 0.0 waitsynced 7200 waitunsynced 300 ---- [[ntpsigndsocket]]*ntpsigndsocket* _directory_:: diff --git a/reference.c b/reference.c index efce367..e7dc116 100644 --- a/reference.c +++ b/reference.c @@ -54,6 +54,8 @@ static int local_orphan; static double local_distance; static int local_activate_ok; static double local_activate; +static double local_wait_synced; +static double local_wait_unsynced; static struct timespec local_ref_time; static NTP_Leap our_leap_status; static int our_leap_sec; @@ -62,6 +64,7 @@ static int our_stratum; static uint32_t our_ref_id; static IPAddr our_ref_ip; static struct timespec our_ref_time; +static double unsynchronised_since; static double our_skew; static double our_residual_freq; static double our_root_delay; @@ -248,8 +251,11 @@ REF_Initialise(void) correction_time_ratio = CNF_GetCorrectionTimeRatio(); enable_local_stratum = CNF_AllowLocalReference(&local_stratum, &local_orphan, - &local_distance, &local_activate); + &local_distance, &local_activate, + &local_wait_synced, + &local_wait_unsynced); UTI_ZeroTimespec(&local_ref_time); + unsynchronised_since = SCH_GetLastEventMonoTime(); leap_when = 0; leap_timeout_id = 0; @@ -1093,6 +1099,9 @@ REF_SetUnsynchronised(void) our_ref_ip.family = IPADDR_INET4; our_ref_ip.addr.in4 = 0; our_stratum = 0; + + if (are_we_synchronised) + unsynchronised_since = SCH_GetLastEventMonoTime(); are_we_synchronised = 0; LCL_SetSyncStatus(0, 0.0, 0.0); @@ -1136,13 +1145,16 @@ REF_GetReferenceParams ) { double dispersion, delta, distance; + int wait_local_ok; assert(initialised); if (are_we_synchronised) { dispersion = get_root_dispersion(local_time); + wait_local_ok = UTI_DiffTimespecsToDouble(local_time, &our_ref_time) >= local_wait_synced; } else { dispersion = 0.0; + wait_local_ok = SCH_GetLastEventMonoTime() - unsynchronised_since >= local_wait_unsynced; } distance = our_root_delay / 2 + dispersion; @@ -1154,7 +1166,8 @@ REF_GetReferenceParams or the root distance exceeds the threshold */ if (are_we_synchronised && - !(enable_local_stratum && local_activate_ok && distance > local_distance)) { + !(enable_local_stratum && local_activate_ok && wait_local_ok && + distance > local_distance)) { *is_synchronised = 1; @@ -1166,7 +1179,7 @@ REF_GetReferenceParams *root_delay = our_root_delay; *root_dispersion = dispersion; - } else if (enable_local_stratum && local_activate_ok) { + } else if (enable_local_stratum && local_activate_ok && wait_local_ok) { *is_synchronised = 0; @@ -1266,13 +1279,16 @@ REF_ModifyMakestep(int limit, double threshold) /* ================================================== */ void -REF_EnableLocal(int stratum, double distance, int orphan, double activate) +REF_EnableLocal(int stratum, double distance, int orphan, double activate, + double wait_synced, double wait_unsynced) { enable_local_stratum = 1; local_stratum = CLAMP(1, stratum, NTP_MAX_STRATUM - 1); local_distance = distance; local_orphan = !!orphan; local_activate = activate; + local_wait_synced = wait_synced; + local_wait_unsynced = wait_unsynced; LOG(LOGS_INFO, "%s local reference mode", "Enabled"); } diff --git a/reference.h b/reference.h index 2eddcae..aac0b41 100644 --- a/reference.h +++ b/reference.h @@ -185,7 +185,8 @@ extern void REF_ModifyMaxupdateskew(double new_max_update_skew); /* Modify makestep settings */ extern void REF_ModifyMakestep(int limit, double threshold); -extern void REF_EnableLocal(int stratum, double distance, int orphan, double activate); +extern void REF_EnableLocal(int stratum, double distance, int orphan, double activate, + double wait_synced, double wait_unsynced); extern void REF_DisableLocal(void); /* Check if either of the current raw and cooked time, and optionally a diff --git a/test/simulation/110-chronyc b/test/simulation/110-chronyc index e096cd0..9635d69 100755 --- a/test/simulation/110-chronyc +++ b/test/simulation/110-chronyc @@ -145,7 +145,7 @@ for chronyc_conf in \ "dfreq 1.0e-3" \ "doffset -1.0" \ "dump" \ - "local stratum 5 distance 1.0 activate 0.5 orphan" \ + "local stratum 5 distance 1.0 activate 0.5 orphan waitsynced 100 waitunsynced 20" \ "local off" \ "makestep 10.0 3" \ "makestep" \ @@ -419,7 +419,7 @@ cyclelogs dump dfreq 1.0e-3 doffset -0.01 -local stratum 5 distance 1.0 orphan +local stratum 5 distance 1.0 orphan waitsynced 100 waitunsynced 10 local off makestep 10.0 3 makestep diff --git a/test/simulation/121-local b/test/simulation/121-local index ba99efc..e6d132a 100755 --- a/test/simulation/121-local +++ b/test/simulation/121-local @@ -7,7 +7,7 @@ test_start "local options" check_config_h 'FEAT_CMDMON 1' || test_skip server_strata=3 -server_conf="local stratum 5 orphan +server_conf="local stratum 5 orphan waitunsynced 0 server 192.168.123.1 server 192.168.123.2 server 192.168.123.3" @@ -23,11 +23,62 @@ check_source_selection || test_fail check_sync || test_fail check_chronyc_output "^.*Stratum *: 7.*$" || test_fail +limit=1000 +server_conf="local stratum 5 orphan +server 192.168.123.1 minpoll 6 maxpoll 6 +server 192.168.123.2 minpoll 6 maxpoll 6 +server 192.168.123.3 minpoll 6 maxpoll 6" +server_server_options="minpoll 6 maxpoll 6" +client_start=0 +client_server_conf=" +server 192.168.123.1 minpoll 6 maxpoll 6 +server 192.168.123.2 minpoll 6 maxpoll 6 +server 192.168.123.3 minpoll 6 maxpoll 6" +client_conf="logdir tmp +log measurements" +chronyc_start=700 +chronyc_conf="" + +run_test || test_fail +check_chronyd_exit || test_fail +check_sync || test_fail +check_file_messages "20.*123\.1.* 5 111 " 10 11 measurements.log || test_fail +check_file_messages "20.*123\.1.* [6-9] 111 " 0 0 measurements.log || test_fail +check_file_messages "20.*123\.2.* 5 111 " 2 4 measurements.log || test_fail +check_file_messages "20.*123\.2.* 6 111 " 7 9 measurements.log || test_fail +check_file_messages "20.*123\.2.* [7-9] 111 " 0 0 measurements.log || test_fail +check_file_messages "20.*123\.3.* 5 111 " 2 4 measurements.log || test_fail +check_file_messages "20.*123\.3.* 6 111 " 7 9 measurements.log || test_fail +check_file_messages "20.*123\.3.* [7-9] 111 " 0 0 measurements.log || test_fail +rm -f tmp/measurements.log + +server_conf="local stratum 5 orphan distance 0.0 waitsynced 150 waitunsynced 0" +base_delay=$(cat <<-EOF | tr -d '\n' + (+ 1e-4 + (* -1 + (equal 0.1 from 1) + (equal 0.1 to 2) + (equal 0.1 (min time 500) 500))) +EOF +) + +run_test || test_fail +check_chronyd_exit || test_fail +check_sync || test_fail +check_file_messages "20.*:1.:.*123\.1.* 5 111 " 6 6 measurements.log || test_fail +check_file_messages "20.*:0.:.*123\.2.* 5 111 " 2 3 measurements.log || test_fail +check_file_messages "20.*:1.:.*123\.2.* 5 111 " 6 7 measurements.log || test_fail +check_file_messages "20.*:0.:.*123\.3.* 5 111 " 7 10 measurements.log || test_fail +check_file_messages "20.*:1.:.*123\.3.* 5 111 " 0 1 measurements.log || test_fail +rm -f tmp/measurements.log + limit=4000 wander=0.0 jitter=0.0 server_strata=1 server_conf="" +server_server_options="" +client_server_conf="" client_server_options="minpoll 6 maxpoll 6 minsamples 64" chronyc_start=1 chronyc_conf="timeout 1000000