#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;
(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);
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, ¶ms, NULL);
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;
}
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
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_]...::
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.
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
----
/* 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
/* ================================================== */
+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)
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;
/* ==================== */
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) {
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;
}
}
}
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
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);
}
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;
}
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) */
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;
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;
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" \
--- /dev/null
+#!/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
+++ /dev/null
-#!/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
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
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