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.
This commit is contained in:
Miroslav Lichvar 2021-11-15 10:08:34 +01:00
parent 72f7d09f58
commit 2319f72b29
7 changed files with 108 additions and 7 deletions

View file

@ -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;

View file

@ -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) |

View file

@ -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) |

View file

@ -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;

View file

@ -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 <<server,*server*>>

View file

@ -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);
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",

View file

@ -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;