diff --git a/client.c b/client.c index 66f23b7..439c72c 100644 --- a/client.c +++ b/client.c @@ -2576,7 +2576,7 @@ process_cmd_selectdata(char *line) n_sources = ntohl(reply.data.n_sources.n_sources); if (verbose) { - printf( " . State: N - noselect, s - unsynchronised, M - missing samples,\n"); + printf( " . State: N - noselect, s - unsynchronised, M - missing samples, r - stratum\n"); printf( " / d/D - large distance, ~ - jittery, w/W - waits for others,\n"); printf( "| S - stale, O - orphan, T - not trusted, P - not preferred,\n"); printf( "| U - waits for update,, x - falseticker, + - combined, * - best.\n"); diff --git a/conf.c b/conf.c index 1f362d7..d081759 100644 --- a/conf.c +++ b/conf.c @@ -115,6 +115,8 @@ static double clock_precision = 0.0; /* in seconds */ static SRC_AuthSelectMode authselect_mode = SRC_AUTHSELECT_MIX; static double max_distance = 3.0; static double max_jitter = 1.0; +static int max_stratum = NTP_MAX_STRATUM - 1; +static int min_stratum = 0; static double reselect_distance = 1e-4; static double stratum_weight = 1e-3; static double combine_limit = 3.0; @@ -702,12 +704,16 @@ CNF_ParseLine(const char *filename, int number, char *line) parse_int(p, &max_samples, 0, INT_MAX); } else if (!strcasecmp(command, "maxslewrate")) { parse_double(p, &max_slew_rate); + } else if (!strcasecmp(command, "maxstratum")) { + parse_int(p, &max_stratum, 0, INT_MAX); } else if (!strcasecmp(command, "maxupdateskew")) { parse_double(p, &max_update_skew); } else if (!strcasecmp(command, "minsamples")) { parse_int(p, &min_samples, 0, INT_MAX); } else if (!strcasecmp(command, "minsources")) { parse_int(p, &min_sources, 1, INT_MAX); + } else if (!strcasecmp(command, "minstratum")) { + parse_int(p, &min_stratum, 0, INT_MAX); } else if (!strcasecmp(command, "nocerttimecheck")) { parse_int(p, &no_cert_time_check, 0, INT_MAX); } else if (!strcasecmp(command, "noclientlog")) { @@ -2311,6 +2317,22 @@ CNF_GetMaxJitter(void) /* ================================================== */ +int +CNF_GetMaxStratum(void) +{ + return max_stratum; +} + +/* ================================================== */ + +int +CNF_GetMinStratum(void) +{ + return min_stratum; +} + +/* ================================================== */ + double CNF_GetReselectDistance(void) { diff --git a/conf.h b/conf.h index 00a1170..a5ff164 100644 --- a/conf.h +++ b/conf.h @@ -106,6 +106,8 @@ extern double CNF_GetClockPrecision(void); extern SRC_AuthSelectMode CNF_GetAuthSelectMode(void); extern double CNF_GetMaxDistance(void); extern double CNF_GetMaxJitter(void); +extern int CNF_GetMaxStratum(void); +extern int CNF_GetMinStratum(void); extern double CNF_GetReselectDistance(void); extern double CNF_GetStratumWeight(void); extern double CNF_GetCombineLimit(void); diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc index 39e47a6..3e57da8 100644 --- a/doc/chrony.conf.adoc +++ b/doc/chrony.conf.adoc @@ -71,8 +71,10 @@ also when the <> command is issued in *chronyc*. The DNS record can change over time. The used address will be replaced with a newly resolved address when the server becomes unreachable (i.e. no valid response to the last 8 requests), unsynchronised, a falseticker (i.e. does not -agree with a majority of other sources), or the root distance is too large (the -limit can be configured by the <> directive). The +agree with a majority of other sources), the root distance is too large (the +limit can be configured by the <> directive), or +the stratum is too small or too large (the limits can be configured by the +<> and <> directives). The automatic replacement happens at most once per 30 minutes and only one server can be replaced at a time. The address is also refreshed periodically when the server is working normally (the interval can be configured by the @@ -318,7 +320,8 @@ with lower stratum are normally slightly preferred. This option can be used to increase stratum of the source to the specified minimum, so *chronyd* will avoid selecting that source. This is useful with low-stratum sources that are known to be unreliable or inaccurate and that should be used only when other -sources are unreachable. +sources are unreachable. Note that the <> directive +is distinct from this option. *version* _version_::: This option sets the NTP version of packets sent to the server. This can be useful when the server runs an old NTP implementation that does not respond to @@ -1102,6 +1105,16 @@ with sources that have a small root distance, but their time is too variable. + By default, the maximum jitter is 1 second. +[[maxstratum]]*maxstratum* _stratum_:: +The *maxstratum* directive sets the maximum allowed stratum of the sources to +be selected for synchronisation. The default value is 15. The useful range is 0 +to 15. + +[[minstratum]]*minstratum* _stratum_:: +The *minstratum* directive sets the minimum allowed stratum of the sources to +be selected for synchronisation. The default value is 0. The useful range is 0 +to 15. + [[minsources]]*minsources* _sources_:: The *minsources* directive sets the minimum number of sources that need to be considered as selectable in the source selection algorithm before the local @@ -2450,6 +2463,9 @@ Not considered selectable for synchronisation: * _N_ - has the *noselect* option. * _s_ - is not synchronised. * _M_ - does not have enough measurements. +* _r_ - has a stratum outside of the allowed range (configured by the + <> and <> + directives). * _d_ - has a root distance larger than the maximum distance (configured by the <> directive). * _~_ - has a jitter larger than the maximum jitter (configured by the diff --git a/doc/chronyc.adoc b/doc/chronyc.adoc index b9c3444..b7f30c6 100644 --- a/doc/chronyc.adoc +++ b/doc/chronyc.adoc @@ -474,6 +474,9 @@ synchronisation: * _N_ - has the *noselect* option. * _M_ - does not have enough measurements. * _s_ - is not synchronised. +* _r_ - has a stratum outside of the allowed range (configured by the + <> and + <> directives). * _d_ - has a root distance larger than the maximum distance (configured by the <> directive). * _~_ - has a jitter larger than the maximum jitter (configured by the diff --git a/sources.c b/sources.c index c6835fc..3c6de32 100644 --- a/sources.c +++ b/sources.c @@ -69,6 +69,7 @@ typedef enum { SRC_UNSELECTABLE, /* Has noselect option set */ SRC_BAD_STATS, /* Doesn't have valid stats data */ SRC_UNSYNCHRONISED, /* Provides samples, but not synchronised */ + SRC_BAD_STRATUM, /* Has stratum outside of allowed range */ SRC_BAD_DISTANCE, /* Has root distance longer than allowed maximum */ SRC_JITTERY, /* Had std dev larger than allowed maximum */ SRC_WAITS_STATS, /* Others have bad stats, selection postponed */ @@ -197,6 +198,8 @@ static int forced_first_report; /* Flag to allow one failed selection to be static double max_distance; static double max_jitter; +static int max_stratum; +static int min_stratum; static double reselect_distance; static double stratum_weight; static double combine_limit; @@ -230,6 +233,8 @@ void SRC_Initialise(void) { selected_source_index = INVALID_SOURCE; max_distance = CNF_GetMaxDistance(); max_jitter = CNF_GetMaxJitter(); + max_stratum = CNF_GetMaxStratum(); + min_stratum = CNF_GetMinStratum(); reselect_distance = CNF_GetReselectDistance(); stratum_weight = CNF_GetStratumWeight(); combine_limit = CNF_GetCombineLimit(); @@ -738,7 +743,8 @@ set_source_status(SRC_Instance inst, SRC_Status status) distance or jitter larger than the allowed maximums */ if (inst == last_updated_inst) { if (inst->bad < INT_MAX && - (status == SRC_FALSETICKER || status == SRC_BAD_DISTANCE || status == SRC_JITTERY)) + (status == SRC_FALSETICKER || status == SRC_BAD_DISTANCE || + status == SRC_BAD_STRATUM || status == SRC_JITTERY)) inst->bad++; else inst->bad = 0; @@ -782,6 +788,14 @@ mark_source(SRC_Instance inst, SRC_Status status) if (!inst->reported_status[status]) { switch (status) { + case SRC_BAD_STRATUM: + if (inst->bad < BAD_HANDLE_THRESHOLD) + break; + log_selection_source(LOGS_WARN, inst, + "Stratum of ## %sstratum of %d", + inst->stratum < min_stratum ? "below min" : "above max", + inst->stratum < min_stratum ? min_stratum : max_stratum); + break; case SRC_BAD_DISTANCE: if (inst->bad < BAD_HANDLE_THRESHOLD) break; @@ -1028,6 +1042,12 @@ SRC_SelectSource(SRC_Instance updated_inst) continue; } + /* Require the stratum to be in the allowed range */ + if (sources[i]->stratum < min_stratum || sources[i]->stratum > max_stratum) { + mark_source(sources[i], SRC_BAD_STRATUM); + continue; + } + /* Include extra dispersion in the root distance of sources that don't have new samples (the last sample is older than span of all samples) */ if (first_sample_ago < 2.0 * si->last_sample_ago) { @@ -1914,6 +1934,8 @@ get_status_char(SRC_Status status) return 'M'; case SRC_UNSYNCHRONISED: return 's'; + case SRC_BAD_STRATUM: + return 'r'; case SRC_BAD_DISTANCE: return 'd'; case SRC_JITTERY: diff --git a/test/simulation/151-stratumlimit b/test/simulation/151-stratumlimit new file mode 100755 index 0000000..47951bf --- /dev/null +++ b/test/simulation/151-stratumlimit @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "minstratum and maxstratum options" + +client_conf=" +minstratum 3 +maxstratum 5" + +for s in 3 5; do + server_conf="local stratum $s" + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail + + check_log_messages "Stratum of .* stratum" 0 0 || test_fail +done + +for s in 2 6; do + server_conf="local stratum $s" + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection && test_fail + check_packet_interval || test_fail + check_sync && test_fail + + check_log_messages "Stratum of .* stratum" 0 0 || test_fail + if [ $s -lt 3 ]; then + check_log_messages "Stratum of .* below minstratum of 3" 1 1 || test_fail + else + check_log_messages "Stratum of .* above maxstratum of 5" 1 1 || test_fail + fi +done + +test_pass