From 84d2811800ea690e11154ffde391575dbd5a6abb Mon Sep 17 00:00:00 2001 From: Miroslav Lichvar Date: Wed, 14 Apr 2021 15:58:51 +0200 Subject: [PATCH] ntp: add copy option When separate client and server instances of chronyd are running on one computer (e.g. for security or performance reasons) and are synchronized to each other, the server instance provides a reference ID based on the local address used for synchronization of its NTP clock, which breaks detection of synchronization loops for its own clients. Add a "copy" option to specify that the server and client are closely related, no loop can form between them, and the client should assume the reference ID and stratum of the server to fix detection of loops between the server and clients of the client. --- candm.h | 1 + client.c | 1 + cmdmon.c | 1 + cmdparse.c | 3 +++ doc/chrony.conf.adoc | 11 ++++++++++- ntp_core.c | 15 +++++++++++++-- srcparams.h | 1 + test/simulation/141-copy | 19 +++++++++++++++++++ 8 files changed, 49 insertions(+), 3 deletions(-) create mode 100755 test/simulation/141-copy diff --git a/candm.h b/candm.h index 52edf29..fe4d5b4 100644 --- a/candm.h +++ b/candm.h @@ -269,6 +269,7 @@ typedef struct { #define REQ_ADDSRC_INTERLEAVED 0x80 #define REQ_ADDSRC_BURST 0x100 #define REQ_ADDSRC_NTS 0x200 +#define REQ_ADDSRC_COPY 0x400 typedef struct { uint32_t type; diff --git a/client.c b/client.c index c0e9022..17e4577 100644 --- a/client.c +++ b/client.c @@ -1098,6 +1098,7 @@ process_cmd_add_source(CMD_Request *msg, char *line) (data.params.interleaved ? REQ_ADDSRC_INTERLEAVED : 0) | (data.params.burst ? REQ_ADDSRC_BURST : 0) | (data.params.nts ? REQ_ADDSRC_NTS : 0) | + (data.params.copy ? REQ_ADDSRC_COPY : 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 3d17cb7..7ee8c12 100644 --- a/cmdmon.c +++ b/cmdmon.c @@ -767,6 +767,7 @@ handle_add_source(CMD_Request *rx_message, CMD_Reply *tx_message) params.interleaved = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_INTERLEAVED ? 1 : 0; 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.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 95d8953..e2c5115 100644 --- a/cmdparse.c +++ b/cmdparse.c @@ -64,6 +64,7 @@ CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src) src->params.sel_options = 0; src->params.nts = 0; src->params.nts_port = SRC_DEFAULT_NTSPORT; + src->params.copy = 0; src->params.authkey = INACTIVE_AUTHKEY; src->params.cert_set = SRC_DEFAULT_CERTSET; src->params.max_delay = SRC_DEFAULT_MAXDELAY; @@ -91,6 +92,8 @@ CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src) src->params.auto_offline = 1; } else if (!strcasecmp(cmd, "burst")) { src->params.burst = 1; + } else if (!strcasecmp(cmd, "copy")) { + src->params.copy = 1; } else if (!strcasecmp(cmd, "iburst")) { src->params.iburst = 1; } else if (!strcasecmp(cmd, "offline")) { diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc index 280278f..ff95ccc 100644 --- a/doc/chrony.conf.adoc +++ b/doc/chrony.conf.adoc @@ -278,6 +278,15 @@ specified by the *key* option and which authentication hash function the key is using. If the output size of the hash function is longer than 160 bits, the default version is 3 for compatibility with older *chronyd* servers. Otherwise, the default version is 4. +*copy*::: +This option specifies that the server and client are closely related, their +configuration does not allow a synchronisation loop to form between them, and +the client is allowed to assume the reference ID and stratum of the server. +This is useful when multiple instances of `chronyd` are running on one computer +(e.g. for security or performance reasons), one primarily operating as a client +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. [[pool]]*pool* _name_ [_option_]...:: The syntax of this directive is similar to that for the <> @@ -320,7 +329,7 @@ address of this host. *chronyd* does not support ephemeral associations. This directive can be used multiple times to specify multiple peers. + The following options of the *server* directive do not work in the *peer* -directive: *iburst*, *burst*, *nts*, *presend*. +directive: *iburst*, *burst*, *nts*, *presend*, *copy*. + When using the *xleave* option, both peers must support and have enabled the interleaved mode, otherwise the synchronisation will work in one direction diff --git a/ntp_core.c b/ntp_core.c index 51c11fd..5470022 100644 --- a/ntp_core.c +++ b/ntp_core.c @@ -107,6 +107,8 @@ struct NCR_Instance_Record { int min_stratum; /* Increase stratum in received packets to the minimum */ + int copy; /* Boolean suppressing own refid and stratum */ + int poll_target; /* Target number of sourcestats samples */ int version; /* Version set in packets for server/peer */ @@ -560,6 +562,7 @@ NCR_CreateInstance(NTP_Remote_Address *remote_addr, NTP_Source_Type type, result->auto_iburst = params->iburst; result->auto_burst = params->burst; result->auto_offline = params->auto_offline; + result->copy = params->copy && result->mode == MODE_CLIENT; result->poll_target = params->poll_target; if (params->nts) { @@ -1766,8 +1769,16 @@ process_response(NCR_Instance inst, NTP_Local_Address *local_addr, inst->tx_count = 0; SRC_UpdateReachability(inst->source, synced_packet); - if (synced_packet) - SRC_UpdateStatus(inst->source, MAX(message->stratum, inst->min_stratum), pkt_leap); + + if (synced_packet) { + if (inst->copy && inst->remote_stratum > 0) { + /* Assume the reference ID and stratum of the server */ + inst->remote_stratum--; + SRC_SetRefid(inst->source, ntohl(message->reference_id), &inst->remote_addr.ip_addr); + } + + SRC_UpdateStatus(inst->source, MAX(inst->remote_stratum, inst->min_stratum), pkt_leap); + } if (good_packet) { /* Adjust the polling interval, accumulate the sample, etc. */ diff --git a/srcparams.h b/srcparams.h index 81ad889..6211ec7 100644 --- a/srcparams.h +++ b/srcparams.h @@ -54,6 +54,7 @@ typedef struct { int sel_options; int nts; int nts_port; + int copy; uint32_t authkey; uint32_t cert_set; double max_delay; diff --git a/test/simulation/141-copy b/test/simulation/141-copy new file mode 100755 index 0000000..80e56bc --- /dev/null +++ b/test/simulation/141-copy @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "copy option" + +check_config_h 'FEAT_CMDMON 1' || test_skip + +client_server_options="copy" +chronyc_conf="tracking" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail +check_chronyc_output "^Reference ID *: 7F7F0101 \(192\.168\.123\.1\) +Stratum *: 1" || test_fail + +test_pass