From 2319f72b29a97059c759902bab410a425a9035e9 Mon Sep 17 00:00:00 2001 From: Miroslav Lichvar Date: Mon, 15 Nov 2021 10:08:34 +0100 Subject: [PATCH] ntp: add client support for experimental extension field Add "extfield F323" option to include the new extension field in requests. If the server responds with this field, use the root delay/dispersion and monotonic timestamp. Accumulate changes in the offset between the monotonic and real-time receive timestamps and use it for the correction of previous offsets in sourcestats. In the interleaved mode, cancel out the latest change in the offset in timestamps of the previous request and response, which were captured before the change actually happened. --- candm.h | 1 + client.c | 1 + cmdmon.c | 2 ++ cmdparse.c | 12 +++++++ doc/chrony.conf.adoc | 19 +++++++++++ ntp_core.c | 79 ++++++++++++++++++++++++++++++++++++++++---- srcparams.h | 1 + 7 files changed, 108 insertions(+), 7 deletions(-) diff --git a/candm.h b/candm.h index 86d8107..dd99413 100644 --- a/candm.h +++ b/candm.h @@ -270,6 +270,7 @@ typedef struct { #define REQ_ADDSRC_BURST 0x100 #define REQ_ADDSRC_NTS 0x200 #define REQ_ADDSRC_COPY 0x400 +#define REQ_ADDSRC_EF_EXP1 0x800 typedef struct { uint32_t type; diff --git a/client.c b/client.c index a9aa6b5..ed551c6 100644 --- a/client.c +++ b/client.c @@ -942,6 +942,7 @@ 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_EXP1 ? REQ_ADDSRC_EF_EXP1 : 0) | (data.params.sel_options & SRC_SELECT_PREFER ? REQ_ADDSRC_PREFER : 0) | (data.params.sel_options & SRC_SELECT_NOSELECT ? REQ_ADDSRC_NOSELECT : 0) | (data.params.sel_options & SRC_SELECT_TRUST ? REQ_ADDSRC_TRUST : 0) | diff --git a/cmdmon.c b/cmdmon.c index fee398b..cd06470 100644 --- a/cmdmon.c +++ b/cmdmon.c @@ -768,6 +768,8 @@ 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_EXP1 ? NTP_EF_FLAG_EXP1 : 0; params.sel_options = (ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_PREFER ? SRC_SELECT_PREFER : 0) | (ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_NOSELECT ? SRC_SELECT_NOSELECT : 0) | diff --git a/cmdparse.c b/cmdparse.c index d44b426..f9fe68e 100644 --- a/cmdparse.c +++ b/cmdparse.c @@ -43,6 +43,7 @@ int CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src) { char *hostname, *cmd; + uint32_t ef_type; int n; src->port = SRC_DEFAULT_PORT; @@ -65,6 +66,7 @@ CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src) src->params.nts = 0; src->params.nts_port = SRC_DEFAULT_NTSPORT; src->params.copy = 0; + src->params.ext_fields = 0; src->params.authkey = INACTIVE_AUTHKEY; src->params.cert_set = SRC_DEFAULT_CERTSET; src->params.max_delay = SRC_DEFAULT_MAXDELAY; @@ -116,6 +118,16 @@ CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src) } else if (!strcasecmp(cmd, "asymmetry")) { if (sscanf(line, "%lf%n", &src->params.asymmetry, &n) != 1) return 0; + } else if (!strcasecmp(cmd, "extfield")) { + if (sscanf(line, "%"SCNx32"%n", &ef_type, &n) != 1) + return 0; + switch (ef_type) { + case NTP_EF_EXP1: + src->params.ext_fields |= NTP_EF_FLAG_EXP1; + break; + default: + return 0; + } } else if (!strcasecmp(cmd, "filter")) { if (sscanf(line, "%d%n", &src->params.filter_length, &n) != 1) return 0; diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc index d76d867..6f734d2 100644 --- a/doc/chrony.conf.adoc +++ b/doc/chrony.conf.adoc @@ -287,6 +287,25 @@ This is useful when multiple instances of `chronyd` are running on one computer to synchronise the system clock and other instances started with the *-x* option to operate as NTP servers for other computers with their NTP clocks synchronised to the first instance. +*extfield* _type_::: +This option enables an NTPv4 extension field specified by its type as a +hexadecimal number. It will be included in requests sent to the server and +processed in received responses if the server supports it. Note that some +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: ++ +_F323_:::: +This is an experimental extension field for some improvements that were +proposed for the next version of the NTP protocol (NTPv5). The field contains +root delay and dispersion in higher resolution and a monotonic receive +timestamp, which enables a frequency transfer between the server and client. It +can significantly improve stability of the synchronization. Generally, it +should be expected to work only between servers and clients running the same +version of *chronyd*. +{blank}::: [[pool]]*pool* _name_ [_option_]...:: The syntax of this directive is similar to that for the <> diff --git a/ntp_core.c b/ntp_core.c index bb92af5..cda4c9c 100644 --- a/ntp_core.c +++ b/ntp_core.c @@ -134,6 +134,10 @@ struct NCR_Instance_Record { int ext_field_flags; /* Enabled extension fields */ + uint32_t remote_mono_epoch; /* ID of the source's monotonic scale */ + double mono_doffset; /* Accumulated offset between source's + real-time and monotonic scales */ + NAU_Instance auth; /* Authentication */ /* Count of transmitted packets since last valid response */ @@ -147,6 +151,7 @@ struct NCR_Instance_Record { int valid_timestamps; /* Receive and transmit timestamps from the last valid response */ + NTP_int64 remote_ntp_monorx; NTP_int64 remote_ntp_rx; NTP_int64 remote_ntp_tx; @@ -283,6 +288,9 @@ static ARR_Instance broadcasts; interleaved mode to prefer a sample using previous timestamps */ #define MAX_INTERLEAVED_L2L_RATIO 0.1 +/* Maximum acceptable change in server mono<->real offset */ +#define MAX_MONO_DOFFSET 16.0 + /* Invalid socket, different from the one in ntp_io.c */ #define INVALID_SOCK_FD -2 @@ -593,7 +601,7 @@ NCR_CreateInstance(NTP_Remote_Address *remote_addr, NTP_Source_Type type, result->auto_offline = params->auto_offline; result->copy = params->copy && result->mode == MODE_CLIENT; result->poll_target = params->poll_target; - result->ext_field_flags = 0; + result->ext_field_flags = params->ext_fields; if (params->nts) { IPSockAddr nts_address; @@ -699,9 +707,12 @@ NCR_ResetInstance(NCR_Instance instance) instance->remote_stratum = 0; instance->remote_root_delay = 0.0; instance->remote_root_dispersion = 0.0; + instance->remote_mono_epoch = 0; + instance->mono_doffset = 0.0; instance->valid_rx = 0; instance->valid_timestamps = 0; + UTI_ZeroNtp64(&instance->remote_ntp_monorx); UTI_ZeroNtp64(&instance->remote_ntp_rx); UTI_ZeroNtp64(&instance->remote_ntp_tx); UTI_ZeroNtp64(&instance->local_ntp_rx); @@ -1641,6 +1652,12 @@ process_sample(NCR_Instance inst, NTP_Sample *sample) error_in_estimate = fabs(-sample->offset - estimated_offset); + if (inst->mono_doffset != 0.0) { + DEBUG_LOG("Monotonic correction offset=%.9f", inst->mono_doffset); + SST_CorrectOffset(SRC_GetSourcestats(inst->source), inst->mono_doffset); + inst->mono_doffset = 0.0; + } + SRC_AccumulateSample(inst->source, sample); SRC_SelectSource(inst->source); @@ -1679,16 +1696,19 @@ process_response(NCR_Instance inst, NTP_Local_Address *local_addr, /* Extension fields */ int parsed, ef_length, ef_type, ef_body_length; void *ef_body; + NTP_ExtFieldExp1 *ef_exp1; NTP_Local_Timestamp local_receive, local_transmit; double remote_interval, local_interval, response_time; - double delay_time, precision; + double delay_time, precision, mono_doffset; int updated_timestamps; /* ==================== */ stats = SRC_GetSourcestats(inst->source); + ef_exp1 = NULL; + /* Find requested non-authentication extension fields */ if (inst->ext_field_flags & info->ext_field_flags) { for (parsed = NTP_HEADER_LENGTH; parsed < info->length; parsed += ef_length) { @@ -1697,6 +1717,10 @@ process_response(NCR_Instance inst, NTP_Local_Address *local_addr, break; switch (ef_type) { + case NTP_EF_EXP1: + if (inst->ext_field_flags & NTP_EF_FLAG_EXP1 && ef_body_length == sizeof (*ef_exp1)) + ef_exp1 = ef_body; + break; } } } @@ -1704,8 +1728,13 @@ process_response(NCR_Instance inst, NTP_Local_Address *local_addr, pkt_leap = NTP_LVM_TO_LEAP(message->lvm); pkt_version = NTP_LVM_TO_VERSION(message->lvm); pkt_refid = ntohl(message->reference_id); - pkt_root_delay = UTI_Ntp32ToDouble(message->root_delay); - pkt_root_dispersion = UTI_Ntp32ToDouble(message->root_dispersion); + if (ef_exp1) { + pkt_root_delay = UTI_Ntp32f28ToDouble(ef_exp1->root_delay); + pkt_root_dispersion = UTI_Ntp32f28ToDouble(ef_exp1->root_dispersion); + } else { + pkt_root_delay = UTI_Ntp32ToDouble(message->root_delay); + pkt_root_dispersion = UTI_Ntp32ToDouble(message->root_dispersion); + } /* Check if the packet is valid per RFC 5905, section 8. The test values are 1 when passed and 0 when failed. */ @@ -1762,6 +1791,26 @@ process_response(NCR_Instance inst, NTP_Local_Address *local_addr, struct timespec local_average, remote_average, prev_remote_transmit; double prev_remote_poll_interval, root_delay, root_dispersion; + /* If the remote monotonic timestamps are available and are from the same + epoch, calculate the change in the offset between the monotonic and + real-time clocks, i.e. separate the source's time corrections from + frequency corrections. The offset is accumulated between measurements. + It will correct old measurements kept in sourcestats before accumulating + the new sample. In the interleaved mode, cancel the correction out in + remote timestamps of the previous request and response, which were + captured before the source accumulated the new time corrections. */ + if (ef_exp1 && inst->remote_mono_epoch == ntohl(ef_exp1->mono_epoch) && + !UTI_IsZeroNtp64(&ef_exp1->mono_receive_ts) && + !UTI_IsZeroNtp64(&inst->remote_ntp_monorx)) { + mono_doffset = + UTI_DiffNtp64ToDouble(&ef_exp1->mono_receive_ts, &inst->remote_ntp_monorx) - + UTI_DiffNtp64ToDouble(&message->receive_ts, &inst->remote_ntp_rx); + if (fabs(mono_doffset) > MAX_MONO_DOFFSET) + mono_doffset = 0.0; + } else { + mono_doffset = 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 @@ -1772,6 +1821,7 @@ process_response(NCR_Instance inst, NTP_Local_Address *local_addr, UTI_DiffTimespecsToDouble(&inst->local_tx.ts, &inst->local_rx.ts) > UTI_DiffTimespecsToDouble(&inst->local_rx.ts, &inst->prev_local_tx.ts)) { UTI_Ntp64ToTimespec(&inst->remote_ntp_rx, &remote_receive); + UTI_AddDoubleToTimespec(&remote_receive, -mono_doffset, &remote_receive); remote_request_receive = remote_receive; local_transmit = inst->prev_local_tx; root_delay = inst->remote_root_delay; @@ -1784,6 +1834,7 @@ process_response(NCR_Instance inst, NTP_Local_Address *local_addr, root_dispersion = MAX(pkt_root_dispersion, inst->remote_root_dispersion); } UTI_Ntp64ToTimespec(&message->transmit_ts, &remote_transmit); + UTI_AddDoubleToTimespec(&remote_transmit, -mono_doffset, &remote_transmit); UTI_Ntp64ToTimespec(&inst->remote_ntp_tx, &prev_remote_transmit); local_receive = inst->local_rx; } else { @@ -1879,6 +1930,7 @@ process_response(NCR_Instance inst, NTP_Local_Address *local_addr, sample.offset = sample.peer_delay = sample.peer_dispersion = 0.0; sample.root_delay = sample.root_dispersion = 0.0; sample.time = rx_ts->ts; + mono_doffset = 0.0; local_receive = *rx_ts; local_transmit = inst->local_tx; testA = testB = testC = testD = 0; @@ -1909,6 +1961,19 @@ process_response(NCR_Instance inst, NTP_Local_Address *local_addr, inst->updated_init_timestamps = 0; updated_timestamps = 2; + /* If available, update the monotonic timestamp and accumulate the offset. + This needs to be done here to no lose changes in remote_ntp_rx in + symmetric mode when there are multiple responses per request. */ + if (ef_exp1 && !UTI_IsZeroNtp64(&ef_exp1->mono_receive_ts)) { + inst->remote_mono_epoch = ntohl(ef_exp1->mono_epoch); + inst->remote_ntp_monorx = ef_exp1->mono_receive_ts; + inst->mono_doffset += mono_doffset; + } else { + inst->remote_mono_epoch = 0; + UTI_ZeroNtp64(&inst->remote_ntp_monorx); + inst->mono_doffset = 0.0; + } + /* Don't use the same set of timestamps for the next sample */ if (interleaved_packet) inst->prev_local_tx = inst->local_tx; @@ -1945,7 +2010,7 @@ process_response(NCR_Instance inst, NTP_Local_Address *local_addr, (unsigned int)local_transmit.source >= sizeof (tss_chars)) assert(0); - DEBUG_LOG("NTP packet lvm=%o stratum=%d poll=%d prec=%d root_delay=%f root_disp=%f refid=%"PRIx32" [%s]", + DEBUG_LOG("NTP packet lvm=%o stratum=%d poll=%d prec=%d root_delay=%.9f root_disp=%.9f refid=%"PRIx32" [%s]", message->lvm, message->stratum, message->poll, message->precision, pkt_root_delay, pkt_root_dispersion, pkt_refid, message->stratum == NTP_INVALID_STRATUM || message->stratum == 1 ? @@ -1958,8 +2023,8 @@ process_response(NCR_Instance inst, NTP_Local_Address *local_addr, DEBUG_LOG("offset=%.9f delay=%.9f dispersion=%f root_delay=%f root_dispersion=%f", sample.offset, sample.peer_delay, sample.peer_dispersion, sample.root_delay, sample.root_dispersion); - DEBUG_LOG("remote_interval=%.9f local_interval=%.9f response_time=%.9f txs=%c rxs=%c", - remote_interval, local_interval, response_time, + DEBUG_LOG("remote_interval=%.9f local_interval=%.9f response_time=%.9f mono_doffset=%.9f txs=%c rxs=%c", + remote_interval, local_interval, response_time, mono_doffset, tss_chars[local_transmit.source], tss_chars[local_receive.source]); DEBUG_LOG("test123=%d%d%d test567=%d%d%d testABCD=%d%d%d%d kod_rate=%d interleaved=%d" " presend=%d valid=%d good=%d updated=%d", diff --git a/srcparams.h b/srcparams.h index 6211ec7..f811146 100644 --- a/srcparams.h +++ b/srcparams.h @@ -55,6 +55,7 @@ typedef struct { int nts; int nts_port; int copy; + int ext_fields; uint32_t authkey; uint32_t cert_set; double max_delay;