]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
ntp: add client support for network correction
authorMiroslav Lichvar <mlichvar@redhat.com>
Tue, 26 Sep 2023 10:52:39 +0000 (12:52 +0200)
committerMiroslav Lichvar <mlichvar@redhat.com>
Tue, 26 Sep 2023 13:14:13 +0000 (15:14 +0200)
If the network correction is known for both the request and response,
and their sum is not larger that the measured peer delay, allowing the
transparent clocks to be running up to 100 ppm faster than the client's
clock, apply the corrections to the NTP offset and peer delay. Don't
correct the root delay to not change the estimated maximum error.

candm.h
client.c
cmdmon.c
cmdparse.c
doc/chrony.conf.adoc
ntp_core.c
test/simulation/110-chronyc
test/simulation/142-ntpoverptp [new file with mode: 0755]
test/simulation/142-ptpport [deleted file]
test/simulation/test.common

diff --git a/candm.h b/candm.h
index 909252f15eb14015d24f325e34d62a7ac52b134f..033cdb9e8924697f9912e73b0540c2958890b378 100644 (file)
--- a/candm.h
+++ b/candm.h
@@ -278,6 +278,7 @@ typedef struct {
 #define REQ_ADDSRC_NTS 0x200
 #define REQ_ADDSRC_COPY 0x400
 #define REQ_ADDSRC_EF_EXP_MONO_ROOT 0x800
+#define REQ_ADDSRC_EF_EXP_NET_CORRECTION 0x1000
 
 typedef struct {
   uint32_t type;
index ee606ab6e15c020592ff4c645d548f7699b7f4b7..7cfefba274e1bc343400f008b36a6933acbbcc5d 100644 (file)
--- a/client.c
+++ b/client.c
@@ -958,7 +958,10 @@ process_cmd_add_source(CMD_Request *msg, char *line)
           (data.params.burst ? REQ_ADDSRC_BURST : 0) |
           (data.params.nts ? REQ_ADDSRC_NTS : 0) |
           (data.params.copy ? REQ_ADDSRC_COPY : 0) |
-          (data.params.ext_fields & NTP_EF_FLAG_EXP_MONO_ROOT ? REQ_ADDSRC_EF_EXP_MONO_ROOT : 0) |
+          (data.params.ext_fields & NTP_EF_FLAG_EXP_MONO_ROOT ?
+           REQ_ADDSRC_EF_EXP_MONO_ROOT : 0) |
+          (data.params.ext_fields & NTP_EF_FLAG_EXP_NET_CORRECTION ?
+           REQ_ADDSRC_EF_EXP_NET_CORRECTION : 0) |
           convert_addsrc_sel_options(data.params.sel_options));
       msg->data.ntp_source.filter_length = htonl(data.params.filter_length);
       msg->data.ntp_source.cert_set = htonl(data.params.cert_set);
index 988bb427c006337f7257db54fa0bc22e8aadad29..9adc9d6edb3cb7460cbd2b1f55761899f0a38e55 100644 (file)
--- a/cmdmon.c
+++ b/cmdmon.c
@@ -783,8 +783,10 @@ handle_add_source(CMD_Request *rx_message, CMD_Reply *tx_message)
   params.burst = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_BURST ? 1 : 0;
   params.nts = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_NTS ? 1 : 0;
   params.copy = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_COPY ? 1 : 0;
-  params.ext_fields = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_EF_EXP_MONO_ROOT ?
-                      NTP_EF_FLAG_EXP_MONO_ROOT : 0;
+  params.ext_fields = (ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_EF_EXP_MONO_ROOT ?
+                      NTP_EF_FLAG_EXP_MONO_ROOT : 0) |
+                      (ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_EF_EXP_NET_CORRECTION ?
+                      NTP_EF_FLAG_EXP_NET_CORRECTION : 0);
   params.sel_options = convert_addsrc_select_options(ntohl(rx_message->data.ntp_source.flags));
 
   status = NSR_AddSourceByName(name, port, pool, type, &params, NULL);
index 57fea1ca92267a46976bd4c300bd29a6f018ff43..ac5ace279a7ae9b7c61569a086802e32f3b56168 100644 (file)
@@ -118,6 +118,9 @@ CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src)
         case NTP_EF_EXP_MONO_ROOT:
           src->params.ext_fields |= NTP_EF_FLAG_EXP_MONO_ROOT;
           break;
+        case NTP_EF_EXP_NET_CORRECTION:
+          src->params.ext_fields |= NTP_EF_FLAG_EXP_NET_CORRECTION;
+          break;
         default:
           return 0;
       }
index 0628832b4033f9ce5eaac46e1d40f3297551a8e5..1fdd5d75c55bccfc002ae4026ffebfc450b5349b 100644 (file)
@@ -322,7 +322,9 @@ server implementations do not respond to requests containing an unknown
 extension field (*chronyd* as a server responded to such requests since
 version 2.0).
 +
-The following extension field can be enabled by this option:
+This option can be used multiple times to enable multiple extension fields.
++
+The following extension fields are supported:
 +
 _F323_::::
 An experimental extension field to enable several improvements that were
@@ -331,6 +333,14 @@ root delay and dispersion in higher resolution and a monotonic receive
 timestamp, which enables a frequency transfer between the server and client to
 significantly improve stability of the synchronisation. This field should be
 enabled only for servers known to be running *chronyd* version 4.2 or later.
+_F324_::::
+An experimental extension field to enable the use of the Precision Time
+Protocol (PTP) correction field in NTP-over-PTP messages updated by one-step
+end-to-end transparent clocks in network switches and routers to significantly
+improve accuracy and stability of the synchronisation. NTP-over-PTP can be
+enabled by the <<ptpport,*ptpport*>> directive and setting the *port* option to
+the PTP port. This field should be enabled only for servers known to be running
+*chronyd* version 4.5 or later.
 {blank}:::
 
 [[pool]]*pool* _name_ [_option_]...::
@@ -2727,8 +2737,10 @@ pidfile /run/chronyd.pid
 The *ptpport* directive enables *chronyd* to send and receive NTP messages
 contained in PTP event messages (NTP-over-PTP) to enable hardware timestamping
 on NICs which cannot timestamp NTP packets, but can timestamp unicast PTP
-packets. The port recognized by the NICs is 319 (PTP event port). The default
-value is 0 (disabled).
+packets, and also use corrections provided by PTP one-step end-to-end
+transparent clocks in network switches and routers. The port recognized by the
+NICs and PTP transparent clocks is 319 (PTP event port). The default value is 0
+(disabled).
 +
 The NTP-over-PTP support is experimental. The protocol and configuration can
 change in future. It should be used only in local networks.
@@ -2738,12 +2750,14 @@ server or client. The directive does not change the default protocol of
 specified NTP sources. Each NTP source that should use NTP-over-PTP needs to
 be specified with the *port* option set to the PTP port. To actually enable
 hardware timestamping on NICs which can timestamp PTP packets only, the
-*rxfilter* option of the *hwtimestamp* directive needs to be set to _ptp_.
+*rxfilter* option of the *hwtimestamp* directive needs to be set to _ptp_. The
+extension field _F324_ needs to be enabled to use the corrections provided by
+the PTP transparent clocks.
 +
 An example of client configuration is:
 +
 ----
-server foo.example.net minpoll 0 maxpoll 0 xleave port 319
+server foo.example.net minpoll 0 maxpoll 0 xleave port 319 extfield F324
 hwtimestamp * rxfilter ptp
 ptpport 319
 ----
index 6cd7879c8fcf4e635b6027c5577b2351cd666586..2b2b78e692c2e06178a5d7d48a930468a285962a 100644 (file)
@@ -314,6 +314,9 @@ static ARR_Instance broadcasts;
 /* Maximum acceptable change in server mono<->real offset */
 #define MAX_MONO_DOFFSET 16.0
 
+/* Maximum assumed frequency error in network corrections */
+#define MAX_NET_CORRECTION_FREQ 100.0e-6
+
 /* Invalid socket, different from the one in ntp_io.c */
 #define INVALID_SOCK_FD -2
 
@@ -1661,6 +1664,53 @@ parse_packet(NTP_Packet *packet, int length, NTP_PacketInfo *info)
 
 /* ================================================== */
 
+static void
+apply_net_correction(NTP_Sample *sample, NTP_Local_Timestamp *rx, NTP_Local_Timestamp *tx,
+                     double precision)
+{
+  double rx_correction, tx_correction, low_delay_correction;
+
+  /* Require some correction from transparent clocks to be present
+     in both directions (not just the local RX timestamp correction) */
+  if (rx->net_correction <= rx->rx_duration || tx->net_correction <= 0.0)
+    return;
+
+  /* With perfect corrections from PTP transparent clocks and short cables
+     the peer delay would be close to zero, or even negative if the server or
+     transparent clocks were running faster than client, which would invert the
+     sample weighting.  Adjust the correction to get a delay corresponding to
+     a direct connection to the server.  For simplicity, assume the TX and RX
+     link speeds are equal.  If not, the reported delay will be wrong, but it
+     will not cause an error in the offset. */
+  rx_correction = rx->net_correction - rx->rx_duration;
+  tx_correction = tx->net_correction - rx->rx_duration;
+
+  /* Use a slightly smaller value in the correction of delay to not overcorrect
+     if the transparent clocks run up to 100 ppm fast and keep a part of the
+     uncorrected delay for the sample weighting */
+  low_delay_correction = (rx_correction + tx_correction) *
+                         (1.0 - MAX_NET_CORRECTION_FREQ);
+
+  /* Make sure the correction is sane.  The values are not authenticated! */
+  if (low_delay_correction < 0.0 || low_delay_correction > sample->peer_delay) {
+    DEBUG_LOG("Invalid correction %.9f peer_delay=%.9f",
+              low_delay_correction, sample->peer_delay);
+    return;
+  }
+
+  /* Correct the offset and peer delay, but not the root delay to not
+     change the estimated maximum error */
+  sample->offset += (rx_correction - tx_correction) / 2.0;
+  sample->peer_delay -= low_delay_correction;
+  if (sample->peer_delay < precision)
+    sample->peer_delay = precision;
+
+  DEBUG_LOG("Applied correction rx=%.9f tx=%.9f dur=%.9f",
+            rx->net_correction, tx->net_correction, rx->rx_duration);
+}
+
+/* ================================================== */
+
 static int
 check_delay_ratio(NCR_Instance inst, SST_Stats stats,
                 struct timespec *sample_time, double delay)
@@ -1923,10 +1973,11 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
   int parsed, ef_length, ef_type, ef_body_length;
   void *ef_body;
   NTP_EFExpMonoRoot *ef_mono_root;
+  NTP_EFExpNetCorrection *ef_net_correction;
 
   NTP_Local_Timestamp local_receive, local_transmit;
   double remote_interval, local_interval, response_time;
-  double delay_time, precision, mono_doffset;
+  double delay_time, precision, mono_doffset, net_correction;
   int updated_timestamps;
 
   /* ==================== */
@@ -1934,6 +1985,7 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
   stats = SRC_GetSourcestats(inst->source);
 
   ef_mono_root = NULL;
+  ef_net_correction = NULL;
 
   /* Find requested non-authentication extension fields */
   if (inst->ext_field_flags & info->ext_field_flags) {
@@ -1949,6 +2001,12 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
                         NTP_EF_EXP_MONO_ROOT_MAGIC))
             ef_mono_root = ef_body;
           break;
+        case NTP_EF_EXP_NET_CORRECTION:
+          if (inst->ext_field_flags & NTP_EF_FLAG_EXP_NET_CORRECTION &&
+              is_exp_ef(ef_body, ef_body_length, sizeof (*ef_net_correction),
+                        NTP_EF_EXP_NET_CORRECTION_MAGIC))
+            ef_net_correction = ef_body;
+          break;
       }
     }
   }
@@ -2055,6 +2113,12 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
       mono_doffset = 0.0;
     }
 
+    if (ef_net_correction) {
+      net_correction = UTI_Ntp64ToDouble(&ef_net_correction->correction);
+    } else {
+      net_correction = 0.0;
+    }
+
     /* Select remote and local timestamps for the new sample */
     if (interleaved_packet) {
       /* Prefer previous local TX and remote RX timestamps if it will make
@@ -2074,6 +2138,7 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
         UTI_Ntp64ToTimespec(&message->receive_ts, &remote_receive);
         UTI_Ntp64ToTimespec(&inst->remote_ntp_rx, &remote_request_receive);
         local_transmit = inst->local_tx;
+        local_transmit.net_correction = net_correction;
         root_delay = MAX(pkt_root_delay, inst->remote_root_delay);
         root_dispersion = MAX(pkt_root_dispersion, inst->remote_root_dispersion);
       }
@@ -2088,6 +2153,7 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
       remote_request_receive = remote_receive;
       local_receive = *rx_ts;
       local_transmit = inst->local_tx;
+      local_transmit.net_correction = net_correction;
       root_delay = pkt_root_delay;
       root_dispersion = pkt_root_dispersion;
     }
@@ -2131,6 +2197,9 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
                              skew * fabs(local_interval);
     sample.root_delay = root_delay + sample.peer_delay;
     sample.root_dispersion = root_dispersion + sample.peer_dispersion;
+
+    /* Apply corrections from PTP transparent clocks if available and sane */
+    apply_net_correction(&sample, &local_receive, &local_transmit, precision);
     
     /* If the source is an active peer, this is the minimum assumed interval
        between previous two transmissions (if not constrained by minpoll) */
@@ -2186,6 +2255,7 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
     sample.root_delay = sample.root_dispersion = 0.0;
     sample.time = rx_ts->ts;
     mono_doffset = 0.0;
+    net_correction = 0.0;
     local_receive = *rx_ts;
     local_transmit = inst->local_tx;
     testA = testB = testC = testD = 0;
@@ -2229,6 +2299,8 @@ process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
       inst->mono_doffset = 0.0;
     }
 
+    inst->local_tx.net_correction = net_correction;
+
     /* Don't use the same set of timestamps for the next sample */
     if (interleaved_packet)
       inst->prev_local_tx = inst->local_tx;
index 97abc217d2e5f9fe2ee35523f307a756992d1260..46b0a3fde276a5a9e1d43afeb94efc5fe653370a 100755 (executable)
@@ -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" \
+       "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" \
        "add server node1.net1.clk" \
        "allow 1.2.3.4" \
        "allow 1.2" \
diff --git a/test/simulation/142-ntpoverptp b/test/simulation/142-ntpoverptp
new file mode 100755 (executable)
index 0000000..2996dc0
--- /dev/null
@@ -0,0 +1,106 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "NTP over PTP"
+
+# Block communication between 3 and 1
+base_delay="(+ 1e-4 (* -1 (equal 0.1 from 3) (equal 0.1 to 1)))"
+
+cat > tmp/peer.keys <<-EOF
+1 MD5 1234567890
+EOF
+
+clients=2
+peers=2
+max_sync_time=420
+
+server_conf="
+ptpport 319"
+client_conf="
+ptpport 319
+authselectmode ignore
+keyfile tmp/peer.keys"
+client_server_options="minpoll 6 maxpoll 6 port 319"
+client_peer_options="minpoll 6 maxpoll 6 port 319 key 1"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+check_file_messages "  2       1       .*      319     319     1       96      " 150 160 \
+       log.packets || test_fail
+check_file_messages "  1       2       .*      319     319     1       96      " 150 160 \
+       log.packets || test_fail
+check_file_messages "  2       3       .*      319     319     1       116     " 150 160 \
+       log.packets || test_fail
+check_file_messages "  3       2       .*      319     319     1       116     " 150 160 \
+       log.packets || test_fail
+
+check_config_h 'HAVE_LINUX_TIMESTAMPING 1' || test_skip
+
+export CLKNETSIM_TIMESTAMPING=2
+export CLKNETSIM_LINK_SPEED=100
+
+client_server_options+=" extfield F324 minpoll 0 maxpoll 0"
+client_peer_options+=" extfield F324 minpoll 0 maxpoll 0 maxdelaydevratio 1e6"
+server_conf+="
+clockprecision 1e-9
+hwtimestamp eth0"
+client_conf+="
+clockprecision 1e-9
+hwtimestamp eth0"
+delay_correction="(+ delay (* -8e-8 (+ length 46)))"
+wander=1e-9
+limit=1000
+freq_offset=-1e-4
+min_sync_time=5
+max_sync_time=20
+time_max_limit=1e-7
+time_rms_limit=2e-8
+freq_max_limit=1e-7
+freq_rms_limit=5e-8
+client_chronyd_options="-d"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+if check_config_h 'FEAT_DEBUG 1'; then
+       check_log_messages "apply_net_correction.*Applied" 900 2100 || test_fail
+       check_log_messages "apply_net_correction.*Invalid" 0 4 || test_fail
+fi
+
+client_server_options+=" xleave"
+client_peer_options+=" xleave"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+if check_config_h 'FEAT_DEBUG 1'; then
+       check_log_messages "apply_net_correction.*Applied" 900 2100 || test_fail
+       check_log_messages "apply_net_correction.*Invalid" 0 4 || test_fail
+
+       freq_offset=0.0
+       delay_correction="(+ -1.0e-9 (* 1.0001 delay))"
+
+       run_test || test_fail
+       check_chronyd_exit || test_fail
+
+       check_log_messages "apply_net_correction.*Applied" 350 1400 || test_fail
+       check_log_messages "apply_net_correction.*Invalid" 350 1400 || test_fail
+
+       server_conf="ptpport 319"
+       client_conf="ptpport 319"
+
+       run_test || test_fail
+       check_chronyd_exit || test_fail
+
+       check_log_messages "apply_net_correction.*Applied" 0 0 || test_fail
+fi
+
+test_pass
diff --git a/test/simulation/142-ptpport b/test/simulation/142-ptpport
deleted file mode 100755 (executable)
index 060932c..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env bash
-
-. ./test.common
-
-test_start "PTP port"
-
-# Block communication between 3 and 1
-base_delay="(+ 1e-4 (* -1 (equal 0.1 from 3) (equal 0.1 to 1)))"
-
-cat > tmp/peer.keys <<-EOF
-1 MD5 1234567890
-EOF
-
-clients=2
-peers=2
-max_sync_time=420
-
-server_conf="
-ptpport 319"
-client_conf="
-ptpport 319
-authselectmode ignore
-keyfile tmp/peer.keys"
-client_server_options="minpoll 6 maxpoll 6 port 319"
-client_peer_options="minpoll 6 maxpoll 6 port 319 key 1"
-
-run_test || test_fail
-check_chronyd_exit || test_fail
-check_source_selection || test_fail
-check_sync || test_fail
-
-check_file_messages "  2       1       .*      319     319     1       96      " 150 160 \
-       log.packets || test_fail
-check_file_messages "  1       2       .*      319     319     1       96      " 150 160 \
-       log.packets || test_fail
-check_file_messages "  2       3       .*      319     319     1       116     " 150 160 \
-       log.packets || test_fail
-check_file_messages "  3       2       .*      319     319     1       116     " 150 160 \
-       log.packets || test_fail
-
-test_pass
index 3f6e80be16a52c32473a4953b1bcd95babe0a149..42a2917b7d15f58d4707bbe3c881a5b3fe9d8d7e 100644 (file)
@@ -31,6 +31,7 @@ default_primary_time_offset=0.0
 default_time_offset=1e-1
 default_freq_offset=1e-4
 default_base_delay=1e-4
+default_delay_correction=""
 default_jitter=1e-4
 default_jitter_asymmetry=0.0
 default_wander=1e-9
@@ -460,6 +461,10 @@ run_test() {
                for j in $(seq 1 $nodes); do
                        echo "node${i}_delay${j} = $(get_delay_expr up)"
                        echo "node${j}_delay${i} = $(get_delay_expr down)"
+                       if [ -n "$delay_correction" ]; then
+                               echo "node${i}_delay_correction${j} = $delay_correction"
+                               echo "node${j}_delay_correction${i} = $delay_correction"
+                       fi
                done
        done > tmp/conf