]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
reference: add wait options for local reference activation
authorMiroslav Lichvar <mlichvar@redhat.com>
Thu, 13 Mar 2025 14:00:06 +0000 (15:00 +0100)
committerMiroslav Lichvar <mlichvar@redhat.com>
Thu, 13 Mar 2025 15:30:27 +0000 (16:30 +0100)
Add "waitunsynced" option to specify how long chronyd needs to wait
before it can activate the local reference when the clock is not
synchronized to give the configured sources a chance to synchronize the
local clock after start. The default is 300 seconds when the orphan
option is enabled (same as the ntpd's default orphanwait), 0 otherwise.

Add "waitsynced" option to specify how long it should wait when the
clock is synchronized. It is an additional requirement to the distance
and activate options.

12 files changed:
candm.h
client.c
cmdmon.c
cmdparse.c
cmdparse.h
conf.c
conf.h
doc/chrony.conf.adoc
reference.c
reference.h
test/simulation/110-chronyc
test/simulation/121-local

diff --git a/candm.h b/candm.h
index a8004e25d2dc9517fbaa17ff8b4f937e91b327ad..c120b0b4a3add8c5905b3e0bd1dbc49e796b2fb0 100644 (file)
--- 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;
 
index 787559a961e816c70f9889f5cadee87054c55feb..32ca958cec01aa9eea71979fd385b58ddbaab323 100644 (file)
--- 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;
 }
index 18d955f78e33616f2638963697c8a46bc01b4658..a651c82cdfa92b5d5a3e25d75366da8dd6e1ae7a 100644 (file)
--- 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();
   }
index 77447dc206801deaac639cfaa59a52a192ea07d2..1cf3740bb875c7c74fd78689e4a3d5cb51cf9cea 100644 (file)
@@ -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;
 }
 
index f0bc7a4fb0918e6b78d12186e5ab551c639f3aca..dd82a394a4cc9dd346ecf267f34e6d02225e8929 100644 (file)
@@ -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 7c84e13bacf72ec282b7bb42cb130b5b08d303b0..e85c775ba49e06718e1dacd3ff19c5c69bec3609 100644 (file)
--- 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 0eee0070474b54f1b267d897a9bdee56a2611eee..00a117022f140360f3f5fe0bc9e3680d75231ddf 100644 (file)
--- 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);
 
index eb8c1d5992c610358ad23fd7ffb070e241a4f310..872fe11c05e0cef305b6244ec2b73f3659b5a528 100644 (file)
@@ -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 <<chronyc.adoc#tracking,*tracking*>> 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_::
index efce3679311901459704c2ec86c1418faeb06fc7..e7dc116bc0e791017cb4ea815c35064befd179b4 100644 (file)
@@ -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");
 }
 
index 2eddcae9b46c20ffe12fa2d6ae8448abffa832aa..aac0b41df27bf1f5b9e6d3fdd9a9c02d05036ecc 100644 (file)
@@ -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
index e096cd03d92ebd07c29b8f48684aa4f49cfa0154..9635d69f062dc17f2093ae5061420b35a2378bc7 100755 (executable)
@@ -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
index ba99efc81fec786f97355dd4a71fdd98b91c67cc..e6d132a96b4c15f50a9ab24fa8a25ba928e4b510 100755 (executable)
@@ -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