Compare commits

...

18 Commits

Author SHA1 Message Date
Miroslav Lichvar
6fe4a60a1d client: fix typo in selectdata help text 2025-11-04 14:47:35 +01:00
Miroslav Lichvar
2da8fbc4c3 sources: add configurable limits for stratum
Add minstratum and maxstratum directives to specify the minimum and
maximum allowed stratum of sources to be selected. The default values
are 0 and 15 respectively, allowing all NTP sources and refclocks.

Sources that are rejected due to having too large or too small stratum
are marked with 'r' in the selection log and selectdata report.

This is similar to the "tos floor" and "tos ceiling" settings of ntpd,
except that maxstratum is interpreted as one below the ceiling.
2025-11-04 14:47:35 +01:00
Miroslav Lichvar
c252e52ee2 sources: rename min_stratum in SRC_SelectSource()
Rename the variable to min_sel_stratum to make it more specific and
avoid shadowing a variable that will be added in the next commit.
2025-11-04 14:47:35 +01:00
Miroslav Lichvar
c8a9ca4cf0 sources: don't save SST_GetSelectionData() status
There is no need to save the SST_GetSelectionData() "select_ok" status
as the source is immediately marked as SRC_BAD_STATS if it is not ok.
Nothing else is using this information.
2025-11-04 14:47:35 +01:00
Miroslav Lichvar
4659b574bf sourcestats: change ST_GetSelectionData() to return status directly
For better consistency, indicate success (usable data) by returning 1
instead of setting a pointer parameter.
2025-11-04 14:47:35 +01:00
Miroslav Lichvar
5436618c05 sourcestats: drop disabled code in SST_GetSelectionData() 2025-11-04 14:47:35 +01:00
Miroslav Lichvar
a0f460569e test: make 007-cmdmon test even more reliable
The last hit value in the clients report may be "-", because there is
a backward step of the clock caused by the doffset and reset sources
commands.
2025-11-04 14:44:09 +01:00
Miroslav Lichvar
3c39afa13c sys_linux: fix building with older compilers and some archs
The recent replacement of <termios.h> with <linux/termios.h> to get
TCGETS2 seems to work only with compilers (or C standards) that allow
the same structure to be defined multiple times. There is a conflict
between <sys/ioctl.h> and <linux/termios.h>.

Another problem is that TCGETS2 is not used on some archs like ppc64.

Switch back to <termios.h> and move TCGETS2 to a list in a separate
file where it can be compiled without <sys/ioctl.h>.

Fixes: 03875f1ea5 ("sys_linux: allow ioctl(TCGETS2) in seccomp filter")
2025-10-22 14:03:44 +02:00
Miroslav Lichvar
03875f1ea5 sys_linux: allow ioctl(TCGETS2) in seccomp filter
Add TCGETS2 to the list of allowed ioctls. It seems to be called by the
latest glibc version from isatty(), which is called from libpcsclite
used by gnutls in an NTS-KE session.

Include the linux termios header instead of glibc header to get a usable
definition of TCGETS2.
2025-10-21 14:22:08 +02:00
Miroslav Lichvar
2e29935c54 local: improve measurement of clock precision
By default, the clock precision is set to the minimum measured time
needed to read the clock. This value is typically larger than the actual
resolution, which causes the NTP server to add more noise to NTP
timestamps than necessary. With HW timestamping and PTP corrections
enabled by the NTP-over-PTP transport that can be the limiting factor in
the stability of NTP measurements.

Try to determine the actual resolution of the clock. On non-Linux
systems use the clock_getres() function. On FreeBSD and NetBSD it seems
to provide expected values. On illumos it returns a large value (kernel
tick length?). On Linux it seems to be the internal timer resolution,
which is 1 ns with hrtimers, even when using a lower-resolution
clocksource like hpet or acpi_pm.

On Linux, try to measure the resolution as the minimum observed change
in differences between consecutive readings of the CLOCK_MONOTONIC_RAW
clock with a varying amount of busy work. Ignore 1ns changes due to
the kernel converting readings to timespec. This seems to work reliably.
In a test with the acpi_pm clocksource, differences of 3073, 3352, and
3631 ns were measured, which gives a resolution of 279 ns, matching the
clocksource frequency of ~3.58 MHz. With a tsc clocksource it gives
the minimum accepted resolution of 2 ns and with kvm-clock 10 ns.

As the final value of the precision, use the minimum value from the
measured or clock_getres() resolution and the original minimum time
needed to read the clock.
2025-10-09 11:10:51 +02:00
Miroslav Lichvar
8084961011 leapdb: fix compiler warning on NetBSD about isspace() 2025-10-09 10:44:31 +02:00
Miroslav Lichvar
120bf44989 test: fix socket unit test to use non-blocking accepted sockets
SCK_AcceptConnection() always returns a non-blocking socket. Clear the
O_NONBLOCK flag in the socket unit test, which relies on blocking, to
avoid failures.

Reported-by: Matthias Andree <matthias.andree@gmx.de>
2025-08-28 09:39:26 +02:00
Miroslav Lichvar
9e8541e3c4 sys_linux: improve error message for failed PHC open
If the specified PHC device cannot be opened directly, an attempt is
made to open it as a network interface. When that fails, the error
"Could not open PHC of iface" is misleading the user that it was handled
only as an interface. Change the message to "Could not open PHC (of)" to
better cover both possibilities. Also remove the errno as it's not set
in all code paths.
2025-08-27 14:05:31 +02:00
Miroslav Lichvar
e95d5a161d test: avoid using cmdport equal to ntpport in system tests
Make sure the two randomly generated port numbers used in system tests
are different to avoid failures.
2025-08-27 14:05:27 +02:00
Miroslav Lichvar
2c63dfee34 doc: update URL in test documentation 2025-08-26 12:34:22 +02:00
Miroslav Lichvar
42e6b5577a test: check credentials in nts_ke_session test
Make it more clear when the test fails because the credentials could not
be created.
2025-08-26 12:33:14 +02:00
Miroslav Lichvar
830c8bb18a util: switch create_dir() from chown() to lchown()
Use lchown(), the safer variant of chown() that does not follow
symlinks, when changing the ownership of a created directory (logdir,
dumpdir, ntsdumpdir, and the directory of bindcmdaddress) to the chrony
user.
2025-08-26 12:32:33 +02:00
Miroslav Lichvar
0289442998 client: fix sizeof in open_unix_socket()
Fix one of the sizeofs in open_unix_socket() to correctly specify
sock_dir2 instead of sock_dir1. They have the same size, but don't rely
on that.

Fixes: 90d808ed28 ("client: mitigate unsafe permissions change on chronyc socket")
2025-08-26 09:59:37 +02:00
21 changed files with 406 additions and 83 deletions

View File

@@ -270,7 +270,7 @@ open_unix_socket(char *server_path)
if (snprintf(sock_dir1, sizeof (sock_dir1),
"%s/chronyc.%d", sock_dir0, (int)getpid()) >= sizeof (sock_dir1) ||
snprintf(sock_dir2, sizeof (sock_dir2),
"%s/%s", sock_dir1, rand_dir) >= sizeof (sock_dir1) ||
"%s/%s", sock_dir1, rand_dir) >= sizeof (sock_dir2) ||
snprintf(sock_path, sizeof (sock_path),
"%s/sock", sock_dir2) >= sizeof (sock_path)) {
LOG(LOGS_ERR, "Server socket path %s is too long", server_path);
@@ -2576,10 +2576,10 @@ 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");
printf( "| U - waits for update, x - falseticker, + - combined, * - best.\n");
printf( "| Effective options ---------. (N - noselect, P - prefer\n");
printf( "| Configured options ----. \\ T - trust, R - require)\n");
printf( "| Auth. enabled (Y/N) -. \\ \\ Offset interval --.\n");

22
conf.c
View File

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

2
conf.h
View File

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

1
configure vendored
View File

@@ -808,6 +808,7 @@ then
# a time and the async resolver would block the main thread
priv_ops="NAME2IPADDRESS RELOADDNS"
EXTRA_LIBS="$EXTRA_LIBS -lseccomp"
EXTRA_OBJECTS="$EXTRA_OBJECTS sys_linux_scmp.o"
fi
if [ "x$priv_ops" != "x" ]; then

View File

@@ -71,8 +71,10 @@ also when the <<chronyc.adoc#online,*online*>> 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 <<maxdistance,*maxdistance*>> directive). The
agree with a majority of other sources), the root distance is too large (the
limit can be configured by the <<maxdistance,*maxdistance*>> directive), or
the stratum is too small or too large (the limits can be configured by the
<<minstratum,*minstratum*>> and <<maxstratum,*maxstratum*>> 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 <<minstratum,*minstratum*>> 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
@@ -1133,23 +1146,29 @@ distances are in milliseconds.
[[clockprecision]]*clockprecision* _precision_::
The *clockprecision* directive specifies the precision of the system clock (in
seconds). It is used by *chronyd* to estimate the minimum noise in NTP
measurements and randomise low-order bits of timestamps in NTP responses. By
default, the precision is measured on start-up as the minimum time to read the
clock.
seconds). This value is used by *chronyd* as the minimum expected error and
amount of noise in NTP and refclock measurements, and to randomise low-order
bits of timestamps in NTP responses to make them less predictable. The minimum
value is 1 nanosecond and the maximum value is 1 second.
+
The measured value works well in most cases. It generally overestimates the
precision and it can be sensitive to the CPU speed, however, which can
change over time to save power. In some cases with a high-precision clocksource
(e.g. the Time Stamp Counter of the CPU) and hardware timestamping, setting the
precision on the server to a smaller value can improve stability of clients'
NTP measurements. The server's precision is reported on clients by the
By default, *chronyd* tries to determine the precision on start-up as the
resolution of the clock. On Linux, it tries to measure the resolution by
observing the minimum change in differences between consecutive readings of the
clock. On other systems it relies on the *clock_getres(2)* system function.
+
If the measurement fails, or the value provided by the system is too large, the
minimum measured time needed to read the clock will be used instead. This value
is typically larger than the resolution, and it is sensitive to the CPU speed,
however, which can change over time to save power.
+
The server's precision is reported on clients by the
<<chronyc.adoc#ntpdata,*ntpdata*>> command.
+
An example setting the precision to 8 nanoseconds is:
An example setting the precision to 1 nanosecond (e.g. when the system clock is
using a Time Stamp Counter (TSC) updated at a rate of at least 1 GHz) is:
+
----
clockprecision 8e-9
clockprecision 1e-9
----
[[corrtimeratio]]*corrtimeratio* _ratio_::
@@ -2444,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
<<minstratum,*minstratum*>> and <<maxstratum,*maxstratum*>>
directives).
* _d_ - has a root distance larger than the maximum distance (configured by the
<<maxdistance,*maxdistance*>> directive).
* _~_ - has a jitter larger than the maximum jitter (configured by the

View File

@@ -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
<<chrony.conf.adoc#minstratum,*minstratum*>> and
<<chrony.conf.adoc#maxstratum,*maxstratum*>> directives).
* _d_ - has a root distance larger than the maximum distance (configured by the
<<chrony.conf.adoc#maxdistance,*maxdistance*>> directive).
* _~_ - has a jitter larger than the maximum jitter (configured by the

View File

@@ -130,7 +130,7 @@ get_list_leap(time_t when, int *tai_offset)
char *p;
/* Ignore blank lines */
for (p = line; *p && isspace(*p); ++p)
for (p = line; *p && isspace((unsigned char)*p); ++p)
;
if (!*p)
continue;

163
local.c
View File

@@ -97,8 +97,142 @@ static double precision_quantum;
static double max_clock_error;
#define NSEC_PER_SEC 1000000000
/* ================================================== */
/* Ask the system for the resolution of the system clock. The Linux
clock_getres() is not usable, because it reports the internal timer
resolution, which is 1 ns when high-resolution timers are enabled,
even when using a lower-resolution clocksource. */
static int
get_clock_resolution(void)
{
#if defined(HAVE_CLOCK_GETTIME) && !defined(LINUX)
struct timespec res;
if (clock_getres(CLOCK_REALTIME, &res) < 0)
return 0;
return NSEC_PER_SEC * res.tv_sec + res.tv_nsec;
#else
return 0;
#endif
}
/* ================================================== */
#if defined(LINUX) && defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC_RAW)
static int
compare_ints(const void *a, const void *b)
{
return *(const int *)a - *(const int *)b;
}
#define READINGS 64
/* On Linux, try to measure the actual resolution of the system
clock by performing a varying amount of busy work between clock
readings and finding the minimum change in the measured interval.
Require a change of at least two nanoseconds to ignore errors
caused by conversion to timespec. Use the raw monotonic clock
to avoid the impact of potential frequency changes due to NTP
adjustments made by other processes, and the kernel dithering of
the 32-bit multiplier. */
static int
measure_clock_resolution(void)
{
int i, j, b, busy, diffs[READINGS - 1], diff2, min;
struct timespec start_ts, ts[READINGS];
uint32_t acc;
if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_ts) < 0)
return 0;
for (acc = 0, busy = 1; busy < 100000; busy = busy * 3 / 2 + 1) {
for (i = 0, b = busy * READINGS; i < READINGS; i++, b -= busy) {
if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts[i]) < 0)
return 0;
for (j = b; j > 0; j--)
acc += (acc & 1) + (uint32_t)ts[i].tv_nsec;
}
/* Give up after 0.1 seconds */
if (UTI_DiffTimespecsToDouble(&ts[READINGS - 1], &start_ts) > 0.1) {
DEBUG_LOG("Measurement too slow");
return 0;
}
for (i = 0; i < READINGS - 1; i++) {
diffs[i] = NSEC_PER_SEC * (ts[i + 1].tv_sec - ts[i].tv_sec) +
(ts[i + 1].tv_nsec - ts[i].tv_nsec);
/* Make sure the differences are sane. A resolution larger than the
reading time will be measured in measure_clock_read_delay(). */
if (diffs[i] <= 0 || diffs[i] > NSEC_PER_SEC)
return 0;
}
/* Sort the differences and keep values unique within 1 ns from the
first half of the array, which are less likely to be impacted by CPU
interruptions */
qsort(diffs, READINGS - 1, sizeof (diffs[0]), compare_ints);
for (i = 1, j = 0; i < READINGS / 2; i++) {
if (diffs[j] + 1 < diffs[i])
diffs[++j] = diffs[i];
}
j++;
#if 0
for (i = 0; i < j; i++)
DEBUG_LOG("busy %d diff %d %d", busy, i, diffs[i]);
#endif
/* Require at least three unique differences to be more confident
with the result */
if (j < 3)
continue;
/* Find the smallest difference between the unique differences */
for (i = 1, min = 0; i < j; i++) {
diff2 = diffs[i] - diffs[i - 1];
if (min == 0 || min > diff2)
min = diff2;
}
if (min == 0)
continue;
/* Prevent the compiler from optimising the busy work out */
if (acc == 0)
min += 1;
return min;
}
return 0;
}
#else
static int
measure_clock_resolution(void)
{
return 0;
}
#endif
/* ================================================== */
/* As a fallback, measure how long it takes to read the clock. It
typically takes longer than the resolution of the clock (and it
depends on the CPU speed), i.e. every reading gives a different
value, but handle also low-resolution clocks that might give
the same reading multiple times. */
/* Define the number of increments of the system clock that we want
to see to be fairly sure that we've got something approaching
the minimum increment. Even on a crummy implementation that can't
@@ -106,10 +240,8 @@ static double max_clock_error;
under 1s of busy waiting. */
#define NITERS 100
#define NSEC_PER_SEC 1000000000
static double
measure_clock_precision(void)
static int
measure_clock_read_delay(void)
{
struct timespec ts, old_ts;
int iters, diff, best;
@@ -135,7 +267,28 @@ measure_clock_precision(void)
assert(best > 0);
return 1.0e-9 * best;
return best;
}
/* ================================================== */
static double
measure_clock_precision(void)
{
int res, delay, prec;
res = get_clock_resolution();
if (res <= 0)
res = measure_clock_resolution();
delay = measure_clock_read_delay();
if (res > 0)
prec = MIN(res, delay);
else
prec = delay;
return prec / 1.0e9;
}
/* ================================================== */

View File

@@ -54,7 +54,6 @@ static int initialised = 0;
/* ================================================== */
/* Structure used to hold info for selecting between sources */
struct SelectInfo {
int select_ok;
double std_dev;
double root_distance;
double lo_limit;
@@ -70,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 */
@@ -198,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;
@@ -231,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();
@@ -739,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;
@@ -783,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;
@@ -955,7 +968,7 @@ SRC_SelectSource(SRC_Instance updated_inst)
int max_badstat_reach, max_badstat_reach_size, n_badstats_sources;
int max_sel_reach, max_sel_reach_size, n_unreach_sources;
int depth, best_depth, trust_depth, best_trust_depth, n_sel_trust_sources;
int combined, stratum, min_stratum, max_score_index;
int combined, stratum, min_sel_stratum, max_score_index;
int orphan_stratum, orphan_source;
double src_offset, src_offset_sd, src_frequency, src_frequency_sd, src_skew;
double src_root_delay, src_root_dispersion;
@@ -1010,12 +1023,10 @@ SRC_SelectSource(SRC_Instance updated_inst)
n_unreach_sources++;
si = &sources[i]->sel_info;
SST_GetSelectionData(sources[i]->stats, &now,
&si->lo_limit, &si->hi_limit, &si->root_distance,
&si->std_dev, &first_sample_ago,
&si->last_sample_ago, &si->select_ok);
if (!si->select_ok) {
if (!SST_GetSelectionData(sources[i]->stats, &now, &si->lo_limit, &si->hi_limit,
&si->root_distance, &si->std_dev, &first_sample_ago,
&si->last_sample_ago)) {
++n_badstats_sources;
mark_source(sources[i], SRC_BAD_STATS);
if (max_badstat_reach < sources[i]->reachability)
@@ -1031,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) {
@@ -1364,12 +1381,12 @@ SRC_SelectSource(SRC_Instance updated_inst)
/* Find minimum stratum */
index = sel_sources[0];
min_stratum = sources[index]->stratum;
min_sel_stratum = sources[index]->stratum;
for (i = 1; i < n_sel_sources; i++) {
index = sel_sources[i];
stratum = sources[index]->stratum;
if (stratum < min_stratum)
min_stratum = stratum;
if (min_sel_stratum > stratum)
min_sel_stratum = stratum;
}
/* Update scores and find the source with maximum score */
@@ -1380,7 +1397,7 @@ SRC_SelectSource(SRC_Instance updated_inst)
if (selected_source_index != INVALID_SOURCE)
sel_src_distance = sources[selected_source_index]->sel_info.root_distance +
(sources[selected_source_index]->stratum - min_stratum) * stratum_weight;
(sources[selected_source_index]->stratum - min_sel_stratum) * stratum_weight;
for (i = 0; i < n_sources; i++) {
/* Reset score for non-selectable sources */
@@ -1392,7 +1409,7 @@ SRC_SelectSource(SRC_Instance updated_inst)
}
distance = sources[i]->sel_info.root_distance +
(sources[i]->stratum - min_stratum) * stratum_weight;
(sources[i]->stratum - min_sel_stratum) * stratum_weight;
if (sources[i]->type == SRC_NTP)
distance += reselect_distance;
@@ -1917,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:

View File

@@ -644,22 +644,16 @@ SST_GetFrequencyRange(SST_Stats inst,
/* ================================================== */
void
SST_GetSelectionData(SST_Stats inst, struct timespec *now,
double *offset_lo_limit,
double *offset_hi_limit,
double *root_distance,
double *std_dev,
double *first_sample_ago,
double *last_sample_ago,
int *select_ok)
int
SST_GetSelectionData(SST_Stats inst, struct timespec *now, double *offset_lo_limit,
double *offset_hi_limit, double *root_distance, double *std_dev,
double *first_sample_ago, double *last_sample_ago)
{
double offset, sample_elapsed;
int i, j;
if (!inst->n_samples) {
*select_ok = 0;
return;
return 0;
}
i = get_runsbuf_index(inst, inst->best_single_sample);
@@ -675,38 +669,25 @@ SST_GetSelectionData(SST_Stats inst, struct timespec *now,
*offset_lo_limit = offset - *root_distance;
*offset_hi_limit = offset + *root_distance;
#if 0
double average_offset, elapsed;
int average_ok;
/* average_ok ignored for now */
elapsed = UTI_DiffTimespecsToDouble(now, &inst->offset_time);
average_offset = inst->estimated_offset + inst->estimated_frequency * elapsed;
if (fabs(average_offset - offset) <=
inst->peer_dispersions[j] + 0.5 * inst->peer_delays[i]) {
average_ok = 1;
} else {
average_ok = 0;
}
#endif
i = get_runsbuf_index(inst, 0);
*first_sample_ago = UTI_DiffTimespecsToDouble(now, &inst->sample_times[i]);
i = get_runsbuf_index(inst, inst->n_samples - 1);
*last_sample_ago = UTI_DiffTimespecsToDouble(now, &inst->sample_times[i]);
*select_ok = inst->regression_ok;
/* If maxsamples is too small to have a successful regression, enable the
selection as a special case for a fast update/print-once reference mode */
if (!*select_ok && inst->n_samples < MIN_SAMPLES_FOR_REGRESS &&
if (!inst->regression_ok && inst->n_samples < MIN_SAMPLES_FOR_REGRESS &&
inst->n_samples == inst->max_samples) {
*std_dev = CNF_GetMaxJitter();
*select_ok = 1;
} else if (!inst->regression_ok) {
return 0;
}
DEBUG_LOG("n=%d off=%f dist=%f sd=%f first_ago=%f last_ago=%f selok=%d",
DEBUG_LOG("n=%d off=%f dist=%f sd=%f first_ago=%f last_ago=%f",
inst->n_samples, offset, *root_distance, *std_dev,
*first_sample_ago, *last_sample_ago, *select_ok);
*first_sample_ago, *last_sample_ago);
return 1;
}
/* ================================================== */

View File

@@ -67,15 +67,10 @@ extern void SST_DoNewRegression(SST_Stats inst);
extern void SST_GetFrequencyRange(SST_Stats inst, double *lo, double *hi);
/* Get data needed for selection */
extern void
SST_GetSelectionData(SST_Stats inst, struct timespec *now,
double *offset_lo_limit,
double *offset_hi_limit,
double *root_distance,
double *variance,
double *first_sample_ago,
double *last_sample_ago,
int *select_ok);
extern int SST_GetSelectionData(SST_Stats inst, struct timespec *now,
double *offset_lo_limit, double *offset_hi_limit,
double *root_distance, double *variance,
double *first_sample_ago, double *last_sample_ago);
/* Get data needed when setting up tracking on this source */
extern void

View File

@@ -63,6 +63,7 @@
#endif
#include "sys_linux.h"
#include "sys_linux_scmp.h"
#include "sys_timex.h"
#include "conf.h"
#include "local.h"
@@ -728,6 +729,14 @@ SYS_Linux_EnableSystemCallFilter(int level, SYS_ProcessContext context)
SCMP_A1(SCMP_CMP_EQ, ioctls[i])) < 0)
goto add_failed;
}
/* Allow selected ioctls that need to be specified in a separate
file to avoid conflicting headers (e.g. TCGETS2) */
for (i = 0; SYS_Linux_GetExtraScmpIoctl(i) != 0; i++) {
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
SCMP_A1(SCMP_CMP_EQ, SYS_Linux_GetExtraScmpIoctl(i))) < 0)
goto add_failed;
}
}
if (seccomp_load(ctx) < 0)
@@ -944,8 +953,7 @@ SYS_Linux_OpenPHC(const char *device, int flags)
if (phc_fd < 0) {
phc_fd = open_phc_by_iface_name(device, flags);
if (phc_fd < 0) {
LOG(LOGS_ERR, "Could not open PHC of iface %s : %s",
device, strerror(errno));
LOG(LOGS_ERR, "Could not open PHC (of) %s", device);
return -1;
}
phc_fd = verify_fd_is_phc(phc_fd);

44
sys_linux_scmp.c Normal file
View File

@@ -0,0 +1,44 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2025
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
**********************************************************************
=======================================================================
Lists of values that are needed in seccomp filters but need to
be compiled separately from sys_linux.c due to conflicting headers.
*/
#include <linux/termios.h>
#include "sys_linux_scmp.h"
unsigned long
SYS_Linux_GetExtraScmpIoctl(int index)
{
const unsigned long ioctls[] = {
#ifdef TCGETS2
/* Conflict between <linux/termios.h> and <sys/ioctl.h> */
TCGETS2,
#endif
0
};
return ioctls[index];
}

28
sys_linux_scmp.h Normal file
View File

@@ -0,0 +1,28 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2025
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
**********************************************************************
=======================================================================
Header file for lists that are needed in seccomp filters but need to
be compiled separately from sys_linux.c due to conflicting headers.
*/
extern unsigned long SYS_Linux_GetExtraScmpIoctl(int index);

View File

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

View File

@@ -1,7 +1,7 @@
This is a collection of simulation tests using the clknetsim simulator
(supported on Linux only).
https://github.com/mlichvar/clknetsim
https://gitlab.com/chrony/clknetsim
The CLKNETSIM_PATH environment variable should point to the directory where
clknetsim was downloaded and compiled. If the variable is not set, the tests

View File

@@ -68,7 +68,7 @@ check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atm
run_chronyc "clients" || test_fail
check_chronyc_output "^Hostname NTP Drop Int IntL Last Cmd Drop Int Last
===============================================================================
.*127\.0\.0\.1 [0-9 ]+ 0 [-0-9 ]+ - [ 0-9]+ 0 0 - -.*$" \
.*127\.0\.0\.1 [0-9 ]+ 0 [-0-9 ]+ - [-0-9 ]+ 0 0 - -.*$" \
|| test_fail
run_chronyc "ntpdata $server" || test_fail

View File

@@ -213,7 +213,10 @@ generate_chrony_conf() {
user=$(get_user)
ntpport=$(get_free_port)
cmdport=$(get_free_port)
while true; do
cmdport=$(get_free_port)
[ "$ntpport" -ne "$cmdport" ] && break
done
echo "0.0 10000" > "$TEST_LIBDIR/driftfile"
echo "1 MD5 abcdefghijklmnopq" > "$TEST_DIR/keys"

View File

@@ -193,6 +193,8 @@ test_unit(void)
server_cred = NKSN_CreateServerCertCredentials(&cert, &key, 1);
client_cred = NKSN_CreateClientCertCredentials(&cert, &cert_id, 1, 0);
TEST_CHECK(server_cred);
TEST_CHECK(client_cred);
TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds) == 0);
TEST_CHECK(fcntl(sock_fds[0], F_SETFL, O_NONBLOCK) == 0);

View File

@@ -191,6 +191,7 @@ test_unit(void)
s3 = SCK_AcceptConnection(s1, &sa2);
TEST_CHECK(UTI_CompareIPs(&sa1.ip_addr, &sa2.ip_addr, NULL) == 0);
fcntl(s3, F_SETFL, fcntl(s3, F_GETFL) & ~O_NONBLOCK);
send_and_recv(SCK_ADDR_IP, 1, 1, s3, s2);
SCK_ShutdownConnection(s2);
@@ -227,6 +228,7 @@ test_unit(void)
s3 = SCK_AcceptConnection(s1, &sa2);
TEST_CHECK(sa2.ip_addr.family == IPADDR_UNSPEC);
fcntl(s3, F_SETFL, fcntl(s3, F_GETFL) & ~O_NONBLOCK);
send_and_recv(SCK_ADDR_UNIX, 1, i % 2, s3, s2);
if (i % 4)

2
util.c
View File

@@ -1203,7 +1203,7 @@ create_dir(char *p, mode_t mode, uid_t uid, gid_t gid)
}
/* Set its owner */
if (chown(p, uid, gid) < 0) {
if (lchown(p, uid, gid) < 0) {
LOG(LOGS_ERR, "Could not change ownership of %s : %s", p, strerror(errno));
/* Don't leave it there with incorrect ownership */
rmdir(p);