Compare commits

...

41 Commits
4.7 ... master

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
Miroslav Lichvar
e9848c0176 doc: update NEWS 2025-08-14 15:55:15 +02:00
Miroslav Lichvar
2cfe969940 doc: update credits in README 2025-08-14 15:30:27 +02:00
Miroslav Lichvar
487cf3840f doc: update FAQ 2025-08-14 15:30:27 +02:00
Miroslav Lichvar
4886c776d5 update copyright years 2025-08-14 15:30:27 +02:00
Miroslav Lichvar
d3f3638b3d util: avoid compiler warning in UTI_IPSockAddrToString()
Don't print directly a buffer of the pool to another buffer of
the pool to avoid a -Wrestrict warning produced by a recent gcc version.
2025-08-14 15:30:27 +02:00
Miroslav Lichvar
6c5973741b configure: fix compiler warnings in system function checks
Avoid using (void *)1 as an output buffer to fix detection of supported
system functions due to -Werror and -Wstringop-overflow etc.
2025-08-14 14:25:38 +02:00
Miroslav Lichvar
51d161a028 refclock: rework update of reachability again
The recent rework of refclock reachability to better work with
driver-specific filtering (PHC driver dropping samples with unexpected
delay) introduced an issue that a PPS refclock is indicated as reachable
even when its "lock" refclock is permanently unreachable, or its samples
constistently fail in other sample checks, and no actual samples can be
accumulated. This breaks the new maxunreach option.

Rework the refclock code to provide samples from drivers together with
their quality level (all drivers except PHC provide samples with
constant quality of 1) and drop samples with quality 0 after passing
all checks, right before the actual accumulation in the median sample
filter. Increment the reachability counter only for samples that would
be accumulated.

This fixes the problem with refclocks indicated as reachable when their
samples would be dropped for other reasons than the PHC-specific delay
filter, and the maxunreach option can work as expected.

Fixes: b9b338a8df ("refclock: rework update of reachability")
2025-08-14 14:25:38 +02:00
Miroslav Lichvar
5535384878 hwclock: don't drop valid samples in HCL_ProcessReadings()
Modify the HCL_ProcessReadings() function to try to always provide
a valid sample. Instead of dropping a sample outside of the expected
delay, provide its assumed quality level as a small integer (relative to
already accumulated samples), and let the caller decide what quality is
acceptable.
2025-08-14 14:24:54 +02:00
Miroslav Lichvar
f78e4681ef refclock_phc: open device for writing with extpps option
In version 6.15 the Linux kernel started checking write access on the
PHC file descriptor in the PTP_PIN_SETFUNC and PTP_EXTTS_REQUEST ioctls.
chronyd opened the PHC device as readonly, which caused the PHC refclock
driver configured with the extpps option to fail with the
"Could not enable external PHC timestamping" error message.

To ensure compatibility with new kernel versions, add flags to the
SYS_Linux_OpenPHC() function and open the device with the O_RDWR flag
when the extpps option is enabled.
2025-08-07 14:43:37 +02:00
Miroslav Lichvar
b365edb48e tls: don't accept NULL ALPN name in TLS_CreateInstance()
The TLS_CreateInstance() function handles a NULL alpn_name, but the
other session functions would crash if it was NULL. Change the function
to not handle the NULL for consistency and avoid potential confusion.

Fixes: 3e32e7e694 ("tls: move gnutls code into tls_gnutls.c")
2025-08-07 10:18:31 +02:00
Miroslav Lichvar
93a78c73ad tls: fix server log messages to have client IP address
Add an additional parameter to TLS_CreateInstance() to save the label of
the connection (server name on the client side and client IP
address:port on the server side) instead of the server name (which is
NULL on the server side) to fix the log messages.

Fixes: 3e32e7e694 ("tls: move gnutls code into tls_gnutls.c")
2025-08-07 10:18:31 +02:00
Miroslav Lichvar
abc267a556 tls: don't call gnutls_deinit() after failed gnutls_init()
Don't assume gnutls_init() leaves the session pointer at NULL when it
returns with an error status. It might be a session that was already
allocated and then freed without resetting it to NULL after an error.

Fixes: 3e32e7e694 ("tls: move gnutls code into tls_gnutls.c")
2025-08-07 10:18:31 +02:00
Miroslav Lichvar
9b183fe98f sources: add option to limit selection of unreachable sources
Add maxunreach option to NTP sources and refclocks to specify the
maximum number of polls that the source can stay selected for
synchronization when it is unreachable (i.e. no valid sample was
received in the last 8 polls).

It is an additional requirement to having at least one sample more
recent than the oldest sample of reachable sources.

The default value is 100000. Setting the option to 0 disables selection
of unreachable sources, which matches RFC 5905.
2025-08-07 10:18:31 +02:00
Miroslav Lichvar
be7f5e8916 client: add support for dropping root privileges
To minimize the impact of potential attacks targeting chronyc started
under root (e.g. performed by a local chronyd process running without
root privileges, a remote chronyd process, or a MITM attacker on the
network), add support for changing the effective UID/GID in chronyc
after start.

The user can be specified by the -u option, similarly to chronyd. The
default chronyc user can be changed by the --with-chronyc-user
configure option. The default value of the default chronyc user is
"root", i.e. chronyc doesn't try to change the identity by default.

The default chronyc user does not follow the default chronyd user
set by the configure --with-user option to avoid errors on systems where
chronyc is not allowed to change its UID/GID (e.g. by a SELinux policy).
2025-08-07 10:18:31 +02:00
Miroslav Lichvar
5e2cd47ad1 test: fix system tests to change also tempcomp owner 2025-07-30 14:57:01 +02:00
Miroslav Lichvar
9eaf8bc521 socket: rename sun variable to fix compilation on illumos
"sun" is reserved on Solaris/illumos.

Fixes: 3dea7dd723 ("socket: rework setting of struct sockaddr_un")
2025-07-30 14:46:59 +02:00
Miroslav Lichvar
54010586aa socket: remove unused chmod() call
Drop the SCK_FLAG_ALL_PERMISSIONS support from the socket code.
chronyc is now calling chmod() on its socket itself in a hidden
directory to mitigate the unsafe operation.
2025-07-30 14:46:59 +02:00
Miroslav Lichvar
90d808ed28 client: mitigate unsafe permissions change on chronyc socket
When chronyc running under root binds its Unix domain socket, it needs
to change the socket permissions in order for chronyd running without
root privileges to be able to send a response to the socket.

There is a race condition between the bind() and chmod() calls. If an
attacker was able to execute arbitrary code in the chronyd process, it
might be able to wait for chronyc to be executed under root, replace the
socket with a symlink between the two calls, and cause the privileged
chronyc process to change permissions of something else, possibly
leading to a privilege escalation.

There doesn't seem to be a safe and portable way to change the socket
permissions directly. Changing the process umask could be problematic in
future with threads.

Hide the socket in two levels of subdirectories (the lower one having
a randomly generated name and not visible to the chronyd process) to
make the socket path unpredictable, and force the bind() or chmod() call
to fail if the visible upper directory is replaced.

Reported-by: Matthias Gerstner <mgerstner@suse.de>
2025-07-30 14:46:59 +02:00
Miroslav Lichvar
1d9e080749 util: warn if UTI_OpenFile() is stuck in a loop
When UTI_OpenFile() is removing an existing file to be replaced by a new
file, it could potentially get stuck in an infinite loop if something
was able to consistently win the race and create a new file before
chronyd.

Log a warning message after 100 failed attempts and repeat on each 10x
increase to make it more obvious to the admin, if it ever happens.

Reported-by: Eric Sesterhenn <eric.sesterhenn@x41-dsec.de>
2025-07-02 16:02:41 +02:00
Miroslav Lichvar
d30913e78c keys: remove misleading memset()
After (re)loading symmetric NTP keys from the key file, there is an
attempt to erase the strings from the stack by calling memset() on the
buffer. However, compilers are free (and have been shown to do) optimize
this call out.

Remove the memset() call to not pretend the stack cannot not contain any
sensitive information. There is no such attempt made for the server and
client NTS keys.

Reported-by: Eric Sesterhenn <eric.sesterhenn@x41-dsec.de>
2025-07-02 16:02:31 +02:00
Ahmad Fatoum
c5d3be8cc4 leapdb: fix ordered comparison against NULL pointer
fgets returns either a valid pointer with the same value as its first
argument or NULL on error or EOF.

GCC 12.2.0 -Wextra warns against relational comparison of the return
value:

  leapdb.c:127:38: warning: ordered comparison of pointer with integer zero [-Wextra]

For clarity, and because the C standard doesn't mandate that valid pointers
have to compare greater than the null pointer constant, replace the
relational expression with an equality expression
2025-07-02 14:49:21 +02:00
Anthony Brandon
3e32e7e694 tls: move gnutls code into tls_gnutls.c
Currently nts_ke_session.c directly calls into gnutls.
This patch moves the calls to gnutls into tls_gnutls.c with an API
defined in tls.h. This way it becomes possible to use different TLS
implementations in future patches.

Signed-off-by: Anthony Brandon <anthony@amarulasolutions.com>
2025-06-26 15:53:41 +02:00
Miroslav Lichvar
52cce3dea8 sys_linux: drop support for kernels before 2.6.39
Linux 2.6.39 was released in 2011.

Refuse to start if a kernel version before 2.6.39 is detected. Assume
the ADJ_SETOFFSET adjtimex mode is always supported. Its verification
briefly reset the timex maxerror value to 0, which possibly confused
applications checking the value at that moment.

Drop the unneeded workaround for slow frequency updates in versions
2.6.27-2.6.32.
2025-06-24 15:49:33 +02:00
53 changed files with 1407 additions and 529 deletions

15
NEWS
View File

@@ -1,3 +1,18 @@
New in version 4.8
==================
Enhancements
------------
* Add maxunreach option to limit selection of unreachable sources
* Add -u option to chronyc to drop root privileges (default chronyc
user is set by configure script)
Bug fixes
---------
* Hide chronyc socket to mitigate unsafe permissions change
* Fix refclock extpps option to work on Linux >= 6.15
* Validate refclock samples for reachability updates
New in version 4.7
==================

1
README
View File

@@ -78,6 +78,7 @@ Vincent Blut <vincent.debian@free.fr>
Luca Boccassi <bluca@debian.org>
Stephan I. Boettcher <stephan@nevis1.columbia.edu>
David Bohman <debohman@gmail.com>
Anthony Brandon <anthony@amarulasolutions.com>
Goswin Brederlow <brederlo@informatik.uni-tuebingen.de>
Leigh Brown <leigh@solinno.co.uk>
Erik Bryer <ebryer@spots.ab.ca>

View File

@@ -306,7 +306,7 @@ typedef struct {
int32_t filter_length;
uint32_t cert_set;
Float max_delay_quant;
uint32_t reserved[1];
int32_t max_unreach;
int32_t EOR;
} REQ_NTP_Source;

181
client.c
View File

@@ -4,7 +4,7 @@
**********************************************************************
* Copyright (C) Richard P. Curnow 1997-2003
* Copyright (C) Lonnie Abelbeck 2016, 2018
* Copyright (C) Miroslav Lichvar 2009-2024
* Copyright (C) Miroslav Lichvar 2009-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
@@ -47,6 +47,8 @@
#include <editline/readline.h>
#endif
#define MAX_UNIX_SOCKET_LENGTH (sizeof ((struct sockaddr_un *)NULL)->sun_path)
/* ================================================== */
struct Address {
@@ -61,6 +63,9 @@ static ARR_Instance server_addresses;
static int sock_fd = -1;
static char sock_dir1[MAX_UNIX_SOCKET_LENGTH];
static char sock_dir2[MAX_UNIX_SOCKET_LENGTH];
static volatile int quit = 0;
static int on_terminal = 0;
@@ -207,35 +212,139 @@ free_addresses(ARR_Instance addresses)
ARR_DestroyInstance(addresses);
}
/* ================================================== */
/* Bind a Unix domain socket and connect it to the server chronyd socket.
Access to the sockets is restricted by the permissions and ownership
of the directory in which they are bound (by default /var/run/chrony).
The location of the chronyc socket follows the location of the chronyd
socket. Due to the possibility of chronyc running under a different
user, the permissions of the chronyc socket need to be loosened on
most systems (illumos is an exception) to allow chronyd to send a
response to the chronyc socket.
Unfortunately, there does not seem to be a safe and portable way to
do that directly (e.g. a compromised chronyd process could replace it
with a symlink and cause a privileged chronyc process to change the
permissions of something else).
The workaround is to bind the socket in a randomly named subdirectory
hidden in another subdirectory to avoid matching an existing path and
force the bind()/chmod() call to fail if the visible upper directory
is replaced.
Note that the socket cannot be moved once bound, because the source
address that chronyd will see would not match the actual path and the
responses would not reach chronyc. */
static int
open_unix_socket(char *server_path)
{
char *sock_dir0, sock_path[MAX_UNIX_SOCKET_LENGTH], rand_dir[16 + 1];
int i, s, dir_fd1 = -1;
struct stat st;
/* Check if the server socket is accessible */
s = SCK_OpenUnixDatagramSocket(server_path, NULL, 0);
if (s < 0)
return -1;
SCK_CloseSocket(s);
/* Generate the random hidden component of the socket path */
for (i = 0; i + 1 < sizeof (rand_dir); i++) {
do
UTI_GetRandomBytesUrandom(&rand_dir[i], sizeof (rand_dir[i]));
while (!isalnum((unsigned char)rand_dir[i]));
}
rand_dir[i] = '\0';
/* Format the paths of the subdirectories and socket:
sock_dir0 = dirname(server_path)
sock_dir1 = sock_dir0/chronyc.$PID
sock_dir2 = sock_dir0/chronyc.$PID/$RANDOM
sock_path = sock_dir0/chronyc.$PID/$RANDOM/sock */
sock_dir0 = UTI_PathToDir(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_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);
Free(sock_dir0);
goto error1;
}
Free(sock_dir0);
if (mkdir(sock_dir1, 0711) < 0 && errno != EEXIST) {
LOG(LOGS_ERR, "Could not create %s : %s", sock_dir1, strerror(errno));
goto error1;
}
dir_fd1 = open(sock_dir1, O_RDONLY | O_NOFOLLOW);
if (dir_fd1 < 0 || fstat(dir_fd1, &st) < 0) {
LOG(LOGS_ERR, "Could not open/stat %s : %s", sock_dir1, strerror(errno));
goto error2;
}
if (!S_ISDIR(st.st_mode) || (st.st_mode & 0777 & ~0711) != 0 || st.st_uid != geteuid()) {
LOG(LOGS_ERR, "Unexpected mode/owner of %s", sock_dir1);
goto error2;
}
if (mkdirat(dir_fd1, rand_dir, 0711) < 0) {
LOG(LOGS_ERR, "Could not create %s : %s", sock_dir2, strerror(errno));
goto error2;
}
s = SCK_OpenUnixDatagramSocket(server_path, sock_path, 0);
if (s < 0) {
LOG(LOGS_ERR, "Could not bind/connect client Unix socket");
goto error3;
}
if (chmod(sock_path, 0666) < 0 ||
chmod(sock_dir2, 0711) < 0 ||
fchmod(dir_fd1, 0711) < 0) {
LOG(LOGS_ERR, "Could not change socket or directory permissions : %s", strerror(errno));
SCK_RemoveSocket(s);
SCK_CloseSocket(s);
goto error3;
}
close(dir_fd1);
return s;
error3:
rmdir(sock_dir2);
error2:
rmdir(sock_dir1);
error1:
if (dir_fd1 >= 0)
close(dir_fd1);
sock_dir1[0] = '\0';
sock_dir2[0] = '\0';
return -1;
}
/* ================================================== */
/* Initialise the socket used to talk to the daemon */
static int
open_socket(struct Address *addr)
{
char *dir, *local_addr;
size_t local_addr_len;
switch (addr->type) {
case SCK_ADDR_IP:
sock_fd = SCK_OpenUdpSocket(&addr->addr.ip, NULL, NULL, 0);
break;
case SCK_ADDR_UNIX:
/* Construct path of our socket. Use the same directory as the server
socket and include our process ID to allow multiple chronyc instances
running at the same time. */
dir = UTI_PathToDir(addr->addr.path);
local_addr_len = strlen(dir) + 50;
local_addr = Malloc(local_addr_len);
snprintf(local_addr, local_addr_len, "%s/chronyc.%d.sock", dir, (int)getpid());
sock_fd = SCK_OpenUnixDatagramSocket(addr->addr.path, local_addr,
SCK_FLAG_ALL_PERMISSIONS);
Free(dir);
Free(local_addr);
sock_fd = open_unix_socket(addr->addr.path);
break;
default:
assert(0);
@@ -257,6 +366,14 @@ close_io(void)
SCK_RemoveSocket(sock_fd);
SCK_CloseSocket(sock_fd);
if (sock_dir2[0] != '\0')
rmdir(sock_dir2);
if (sock_dir1[0] != '\0')
rmdir(sock_dir1);
sock_dir2[0] = '\0';
sock_dir1[0] = '\0';
sock_fd = -1;
}
@@ -987,7 +1104,7 @@ process_cmd_add_source(CMD_Request *msg, char *line)
msg->data.ntp_source.cert_set = htonl(data.params.cert_set);
msg->data.ntp_source.max_delay_quant =
UTI_FloatHostToNetwork(data.params.max_delay_quant);
memset(msg->data.ntp_source.reserved, 0, sizeof (msg->data.ntp_source.reserved));
msg->data.ntp_source.max_unreach = htonl(data.params.max_unreach);
result = 1;
@@ -2459,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");
@@ -3473,9 +3590,11 @@ print_help(const char *progname)
" -m\t\tAccept multiple commands\n"
" -h HOST\tSpecify server (%s)\n"
" -p PORT\tSpecify UDP port (%d)\n"
" -u USER\tSpecify user (%s)\n"
" -v, --version\tPrint version and exit\n"
" --help\tPrint usage and exit\n",
progname, DEFAULT_COMMAND_SOCKET",127.0.0.1,::1", DEFAULT_CANDM_PORT);
progname, DEFAULT_COMMAND_SOCKET",127.0.0.1,::1",
DEFAULT_CANDM_PORT, DEFAULT_CHRONYC_USER);
}
/* ================================================== */
@@ -3492,10 +3611,11 @@ int
main(int argc, char **argv)
{
char *line;
const char *hostnames = NULL, *user = DEFAULT_CHRONYC_USER;
const char *progname = argv[0];
const char *hostnames = NULL;
int opt, ret = 1, multi = 0, family = IPADDR_UNSPEC;
int port = DEFAULT_CANDM_PORT;
struct passwd *pw;
/* Parse long command-line options */
for (optind = 1; optind < argc; optind++) {
@@ -3511,7 +3631,7 @@ main(int argc, char **argv)
optind = 1;
/* Parse short command-line options */
while ((opt = getopt(argc, argv, "+46acdef:h:mnNp:v")) != -1) {
while ((opt = getopt(argc, argv, "+46acdef:h:mnNp:u:v")) != -1) {
switch (opt) {
case '4':
case '6':
@@ -3547,6 +3667,9 @@ main(int argc, char **argv)
case 'p':
port = atoi(optarg);
break;
case 'u':
user = optarg;
break;
case 'v':
print_version();
return 0;
@@ -3556,6 +3679,14 @@ main(int argc, char **argv)
}
}
/* Drop root privileges if configured to do so */
if (strcmp(user, "root") != 0 && geteuid() == 0) {
pw = getpwnam(user);
if (!pw)
LOG_FATAL("Could not get user/group ID of %s", user);
UTI_DropRoot(pw->pw_uid, pw->pw_gid);
}
if (isatty(0) && isatty(1) && isatty(2)) {
on_terminal = 1;
}

View File

@@ -686,6 +686,7 @@ handle_add_source(CMD_Request *rx_message, CMD_Reply *tx_message)
params.max_sources = ntohl(rx_message->data.ntp_source.max_sources);
params.min_samples = ntohl(rx_message->data.ntp_source.min_samples);
params.max_samples = ntohl(rx_message->data.ntp_source.max_samples);
params.max_unreach = ntohl(rx_message->data.ntp_source.max_unreach);
params.filter_length = ntohl(rx_message->data.ntp_source.filter_length);
params.authkey = ntohl(rx_message->data.ntp_source.authkey);
params.nts_port = ntohl(rx_message->data.ntp_source.nts_port);

View File

@@ -3,7 +3,7 @@
**********************************************************************
* Copyright (C) Richard P. Curnow 1997-2003
* Copyright (C) Miroslav Lichvar 2013-2014, 2016, 2021
* Copyright (C) Miroslav Lichvar 2013-2014, 2016, 2021, 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
@@ -66,6 +66,7 @@ CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src)
src->params.max_sources = SRC_DEFAULT_MAXSOURCES;
src->params.min_samples = SRC_DEFAULT_MINSAMPLES;
src->params.max_samples = SRC_DEFAULT_MAXSAMPLES;
src->params.max_unreach = SRC_DEFAULT_MAXUNREACH;
src->params.filter_length = 0;
src->params.interleaved = 0;
src->params.sel_options = 0;
@@ -158,6 +159,9 @@ CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src)
} else if (!strcasecmp(cmd, "maxsources")) {
if (!SSCANF_IN_RANGE(line, "%d%n", &src->params.max_sources, &n, 1, INT_MAX))
return CPS_InvalidValue;
} else if (!strcasecmp(cmd, "maxunreach")) {
if (!SSCANF_IN_RANGE(line, "%d%n", &src->params.max_unreach, &n, 0, INT_MAX))
return CPS_InvalidValue;
} else if (!strcasecmp(cmd, "mindelay")) {
if (sscanf(line, "%lf%n", &src->params.min_delay, &n) != 1)
return CPS_InvalidValue;

29
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")) {
@@ -958,7 +964,7 @@ static void
parse_refclock(char *line)
{
int n, poll, dpoll, filter_length, pps_rate, min_samples, max_samples, sel_options;
int local, max_lock_age, pps_forced, sel_option, stratum, tai;
int local, max_lock_age, max_unreach, pps_forced, sel_option, stratum, tai;
uint32_t ref_id, lock_ref_id;
double offset, delay, precision, max_dispersion, pulse_width;
char *p, *cmd, *name, *param;
@@ -972,6 +978,7 @@ parse_refclock(char *line)
pps_rate = 0;
min_samples = SRC_DEFAULT_MINSAMPLES;
max_samples = SRC_DEFAULT_MAXSAMPLES;
max_unreach = SRC_DEFAULT_MAXUNREACH;
sel_options = 0;
offset = 0.0;
delay = 1e-9;
@@ -1036,6 +1043,9 @@ parse_refclock(char *line)
} else if (!strcasecmp(cmd, "maxsamples")) {
if (!SSCANF_IN_RANGE(line, "%d%n", &max_samples, &n, 0, INT_MAX))
break;
} else if (!strcasecmp(cmd, "maxunreach")) {
if (!SSCANF_IN_RANGE(line, "%d%n", &max_unreach, &n, 0, INT_MAX))
break;
} else if (!strcasecmp(cmd, "offset")) {
if (sscanf(line, "%lf%n", &offset, &n) != 1)
break;
@@ -1085,6 +1095,7 @@ parse_refclock(char *line)
refclock->pps_rate = pps_rate;
refclock->min_samples = min_samples;
refclock->max_samples = max_samples;
refclock->max_unreach = max_unreach;
refclock->sel_options = sel_options;
refclock->stratum = stratum;
refclock->tai = tai;
@@ -2306,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);

31
configure vendored
View File

@@ -133,6 +133,7 @@ For better control, use the options below.
--with-ntp-era=SECONDS Specify earliest assumed NTP time in seconds
since 1970-01-01 [50*365 days ago]
--with-user=USER Specify default chronyd user [root]
--with-chronyc-user=USER Specify default chronyc user [root]
--with-hwclockfile=PATH Specify default path to hwclock(8) adjtime file
--with-pidfile=PATH Specify default pidfile [/var/run/chrony/chronyd.pid]
--with-rtcdevice=PATH Specify default path to RTC device [/dev/rtc]
@@ -250,6 +251,7 @@ try_timestamping=0
feat_ntp_signd=0
ntp_era_split=""
default_user="root"
default_chronyc_user="root"
default_hwclockfile=""
default_pidfile="/var/run/chrony/chronyd.pid"
default_rtcdevice="/dev/rtc"
@@ -354,6 +356,9 @@ do
--with-user=* )
default_user=`echo $option | sed -e 's/^.*=//;'`
;;
--with-chronyc-user=* )
default_chronyc_user=`echo $option | sed -e 's/^.*=//;'`
;;
--with-hwclockfile=* )
default_hwclockfile=`echo $option | sed -e 's/^.*=//;'`
;;
@@ -661,13 +666,15 @@ then
fi
if [ $try_clock_gettime = "1" ]; then
if test_code 'clock_gettime()' 'time.h' '' '' \
'clock_gettime(CLOCK_REALTIME, (void *)1);'
if test_code 'clock_gettime()' 'time.h' '' '' '
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);'
then
add_def HAVE_CLOCK_GETTIME
else
if test_code 'clock_gettime() in -lrt' 'time.h' '' '-lrt' \
'clock_gettime(CLOCK_REALTIME, (void *)1);'
if test_code 'clock_gettime() in -lrt' 'time.h' '' '-lrt' '
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);'
then
add_def HAVE_CLOCK_GETTIME
EXTRA_LIBS="$EXTRA_LIBS -lrt"
@@ -694,13 +701,16 @@ else
fi
if [ $try_arc4random = "1" ] && \
test_code 'arc4random_buf()' 'stdlib.h' '' '' \
'arc4random_buf((void *)1, 1);'
test_code 'arc4random_buf()' 'stdlib.h' '' '' '
char c;
arc4random_buf(&c, 1);'
then
add_def HAVE_ARC4RANDOM
else
if test_code 'getrandom()' 'stdlib.h sys/random.h' '' '' \
'return getrandom((void *)1, 1, 0);'; then
if test_code 'getrandom()' 'stdlib.h sys/random.h' '' '' '
char c;
return getrandom(&c, 1, 0);'
then
add_def HAVE_GETRANDOM
fi
fi
@@ -798,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
@@ -1011,7 +1022,7 @@ if [ $feat_nts = "1" ] && [ $try_gnutls = "1" ]; then
fi
if grep '#define HAVE_SIV' config.h > /dev/null; then
EXTRA_OBJECTS="$EXTRA_OBJECTS nts_ke_client.o nts_ke_server.o nts_ke_session.o"
EXTRA_OBJECTS="$EXTRA_OBJECTS nts_ke_client.o nts_ke_server.o nts_ke_session.o tls_gnutls.o"
EXTRA_OBJECTS="$EXTRA_OBJECTS nts_ntp_auth.o nts_ntp_client.o nts_ntp_server.o"
LIBS="$LIBS $test_link"
MYCPPFLAGS="$MYCPPFLAGS $test_cflags"
@@ -1080,6 +1091,7 @@ add_def DEFAULT_HWCLOCK_FILE "\"$default_hwclockfile\""
add_def DEFAULT_PID_FILE "\"$default_pidfile\""
add_def DEFAULT_RTC_DEVICE "\"$default_rtcdevice\""
add_def DEFAULT_USER "\"$default_user\""
add_def DEFAULT_CHRONYC_USER "\"$default_chronyc_user\""
add_def DEFAULT_COMMAND_SOCKET "\"$CHRONYRUNDIR/chronyd.sock\""
add_def MAIL_PROGRAM "\"$mail_program\""
@@ -1123,6 +1135,7 @@ do
s%@DEFAULT_PID_FILE@%${default_pidfile}%;\
s%@DEFAULT_RTC_DEVICE@%${default_rtcdevice}%;\
s%@DEFAULT_USER@%${default_user}%;\
s%@DEFAULT_CHRONYC_USER@%${default_chronyc_user}%;\
s%@CHRONY_VERSION@%${CHRONY_VERSION}%;" \
< ${f}.in > $f
done

View File

@@ -17,6 +17,7 @@ CHRONYRUNDIR = @CHRONYRUNDIR@
CHRONYVARDIR = @CHRONYVARDIR@
CHRONY_VERSION = @CHRONY_VERSION@
DEFAULT_USER = @DEFAULT_USER@
DEFAULT_CHRONYC_USER = @DEFAULT_CHRONYC_USER@
DEFAULT_HWCLOCK_FILE = @DEFAULT_HWCLOCK_FILE@
DEFAULT_PID_FILE = @DEFAULT_PID_FILE@
DEFAULT_RTC_DEVICE = @DEFAULT_RTC_DEVICE@
@@ -29,6 +30,7 @@ SED_COMMANDS = "s%\@SYSCONFDIR\@%$(SYSCONFDIR)%g;\
s%\@DEFAULT_PID_FILE\@%$(DEFAULT_PID_FILE)%g;\
s%\@DEFAULT_RTC_DEVICE\@%$(DEFAULT_RTC_DEVICE)%g;\
s%\@DEFAULT_USER\@%$(DEFAULT_USER)%g;\
s%\@DEFAULT_CHRONYC_USER\@%$(DEFAULT_CHRONYC_USER)%g;\
s%\@CHRONYRUNDIR\@%$(CHRONYRUNDIR)%g;\
s%\@CHRONYVARDIR\@%$(CHRONYVARDIR)%g;"

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
@@ -218,6 +220,12 @@ Set the minimum number of samples kept for this source. This overrides the
*maxsamples* _samples_:::
Set the maximum number of samples kept for this source. This overrides the
<<maxsamples,*maxsamples*>> directive.
*maxunreach* _polls_:::
This option specifies the maximum number of polls that this source can stay
selected for synchronisation when it is unreachable (i.e. no valid response was
received to the last 8 requests). Only sources with at least one sample more
recent than the oldest sample of all reachable sources can be selected. The
default is 100000.
*filter* _polls_:::
This option enables a median filter to reduce noise in NTP measurements. The
filter will process samples collected in the specified number of polls
@@ -312,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
@@ -731,6 +740,12 @@ Set the minimum number of samples kept for this source. This overrides the
*maxsamples* _samples_:::
Set the maximum number of samples kept for this source. This overrides the
<<maxsamples,*maxsamples*>> directive.
*maxunreach* _polls_:::
This option specifies the maximum number of polls that this source can stay
selected for synchronisation when it is unreachable (i.e. no valid sample was
received in the last 8 polls). Only sources with at least one sample more
recent than the oldest sample of all reachable sources can be selected.
The default is 100000.
[[manual]]*manual*::
The *manual* directive enables support at run-time for the
@@ -1090,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
@@ -1121,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_::
@@ -2432,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

@@ -2,7 +2,7 @@
//
// Copyright (C) Richard P. Curnow 1997-2003
// Copyright (C) Stephen Wadeley 2016
// Copyright (C) Miroslav Lichvar 2009-2017, 2019-2024
// Copyright (C) Miroslav Lichvar 2009-2017, 2019-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
@@ -121,6 +121,13 @@ This option allows the user to specify the UDP port number which the target
*chronyd* is using for its monitoring connections. This defaults to 323; there
would rarely be a need to change this.
*-u* _user_::
This option sets the name of the user to which *chronyc* will switch when it is
started under the root user in order to drop its privileges. *chronyc* will not
try to change its identity if the option is set to _root_, or the effective
user ID of the started process is not 0. The compiled-in default value is
_@DEFAULT_CHRONYC_USER@_.
*-f* _file_::
This option is ignored and is provided only for compatibility.
@@ -467,12 +474,16 @@ 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
<<chrony.conf.adoc#maxjitter,*maxjitter*>> directive).
* _w_ - waits for other sources to get out of the _M_ state.
* _S_ - has older measurements than other sources.
* _S_ - has only measurements older than reachable sources, or is unreachable
for too many polls (configured by the *maxunreach* option).
* _O_ - has a stratum equal or larger than the orphan stratum (configured by
the <<chrony.conf.adoc#local,*local*>> directive).
* _T_ - does not fully agree with sources that have the *trust* option.

View File

@@ -165,6 +165,13 @@ versions or implementations of the libraries might make different system calls.
If the filter is missing some system call, `chronyd` could be killed even in
normal operation.
The impact of potential security issues in `chronyc` can be reduced by running
`chronyc` under the _chrony_ user instead of root, or another unprivileged user
if access to the Unix domain socket is not needed. Since version 4.8, `chronyc`
drops root privileges automatically if it is started with the `-u` option
specifying the _chrony_ user, or the name was specified to be the compiled-in
default by the `--with-chronyc-user` option of the configure script.
=== How can I make the system clock more secure?
An NTP client synchronising the system clock to an NTP server is susceptible to
@@ -897,7 +904,9 @@ measurements from both sources.
If the first source was significantly better than the second source, it can
take many hours before the second source is selected, depending on its polling
interval. You can force a faster reselection by increasing the clock error rate
interval. You can force a faster reselection by reducing the maximum number of
polls the source can still be selected when unreachable (`maxunreach` option
supported since `chrony` version 4.8), increasing the clock error rate
(`maxclockerror` directive), shortening the polling interval (`maxpoll`
option), or reducing the number of samples (`maxsamples` option).

View File

@@ -163,7 +163,8 @@ HCL_NeedsNewSample(HCL_Instance clock, struct timespec *now)
int
HCL_ProcessReadings(HCL_Instance clock, int n_readings, struct timespec tss[][3],
struct timespec *hw_ts, struct timespec *local_ts, double *err)
struct timespec *hw_ts, struct timespec *local_ts, double *err,
int *quality)
{
double delay, raw_delay, min_delay, low_delay, high_delay, e, pred_err;
double delay_sum, hw_sum, local_sum, local_prec, freq;
@@ -228,11 +229,13 @@ HCL_ProcessReadings(HCL_Instance clock, int n_readings, struct timespec tss[][3]
UTI_AddDoubleToTimespec(&tss[0][1], hw_sum / combined, hw_ts);
UTI_AddDoubleToTimespec(&tss[0][0], local_sum / combined, local_ts);
*err = MAX(delay_sum / combined / 2.0, clock->precision);
*quality = 2;
return 1;
}
/* Accept the reading with minimum delay if its interval does not contain
the current offset predicted from previous samples */
/* Indicate acceptable quality of the reading with minimum delay if its
interval does not contain the current offset predicted from previous
samples, or a new sample is needed to get the tracking working */
*hw_ts = tss[min_reading][1];
UTI_AddDoubleToTimespec(&tss[min_reading][0], min_delay / freq / 2.0, local_ts);
@@ -242,11 +245,14 @@ HCL_ProcessReadings(HCL_Instance clock, int n_readings, struct timespec tss[][3]
LCL_CookTime(local_ts, &ts1, NULL);
if (!HCL_CookTime(clock, hw_ts, &ts2, &e) ||
((pred_err = UTI_DiffTimespecsToDouble(&ts1, &ts2)) > *err)) {
DEBUG_LOG("Accepted reading err=%e prerr=%e", *err, pred_err);
return 1;
*quality = 1;
} else {
*quality = 0;
}
return 0;
DEBUG_LOG("Min-delay reading err=%e prerr=%e ql=%d", *err, pred_err, *quality);
return 1;
}
/* ================================================== */

View File

@@ -38,10 +38,14 @@ extern void HCL_DestroyInstance(HCL_Instance clock);
/* Check if a new sample should be accumulated at this time */
extern int HCL_NeedsNewSample(HCL_Instance clock, struct timespec *now);
/* Process new readings of the HW clock in form of (sys, hw, sys) triplets and
produce a sample which can be accumulated */
/* Process new readings of the HW clock in the form of (sys, hw, sys) triplets
and produce a sample which can be accumulated by HCL_AccumulateSample().
Indicate the quality of the sample relative to already processed samples as
a value of 0, 1, or 2, where a sample of quality 0 should normally be
dropped. */
extern int HCL_ProcessReadings(HCL_Instance clock, int n_readings, struct timespec tss[][3],
struct timespec *hw_ts, struct timespec *local_ts, double *err);
struct timespec *hw_ts, struct timespec *local_ts, double *err,
int *quality);
/* Accumulate a new sample */
extern void HCL_AccumulateSample(HCL_Instance clock, struct timespec *hw_ts,

3
keys.c
View File

@@ -265,9 +265,6 @@ KEY_Reload(void)
if (get_key(i - 1)->id == get_key(i)->id)
LOG(LOGS_WARN, "Detected duplicate key %"PRIu32, get_key(i - 1)->id);
}
/* Erase any passwords from stack */
memset(line, 0, sizeof (line));
}
/* ================================================== */

View File

@@ -124,13 +124,13 @@ get_list_leap(time_t when, int *tai_offset)
/* leap-seconds.list timestamps are relative to 1 Jan 1900, 00:00:00 */
when1900 = (int64_t)when + LEAP_SEC_LIST_OFFSET;
while (fgets(line, sizeof line, f) > 0) {
while (fgets(line, sizeof line, f)) {
int64_t lsl_when;
int lsl_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

@@ -686,7 +686,8 @@ NCR_CreateInstance(NTP_Remote_Address *remote_addr, NTP_Source_Type type,
SRC_NTP, NAU_IsAuthEnabled(result->auth),
params->sel_options, &result->remote_addr.ip_addr,
params->min_samples, params->max_samples,
params->min_delay, params->asymmetry);
params->min_delay, params->asymmetry,
params->max_unreach);
if (params->max_delay_quant > 0.0) {
int k = round(CLAMP(0.05, params->max_delay_quant, 0.95) * DELAY_QUANT_Q);

View File

@@ -220,7 +220,7 @@ add_interface(CNF_HwTsInterface *conf_iface)
SCK_CloseSocket(sock_fd);
phc_fd = SYS_Linux_OpenPHC(req.ifr_name);
phc_fd = SYS_Linux_OpenPHC(req.ifr_name, O_RDONLY);
if (phc_fd < 0)
return 0;
@@ -519,7 +519,7 @@ poll_phc(struct Interface *iface, struct timespec *now)
struct timespec sample_phc_ts, sample_sys_ts, sample_local_ts;
struct timespec phc_readings[PHC_READINGS][3];
double phc_err, local_err, interval;
int n_readings;
int n_readings, quality;
if (!HCL_NeedsNewSample(iface->clock, now))
return;
@@ -543,7 +543,8 @@ poll_phc(struct Interface *iface, struct timespec *now)
return;
if (!HCL_ProcessReadings(iface->clock, n_readings, phc_readings,
&sample_phc_ts, &sample_sys_ts, &phc_err))
&sample_phc_ts, &sample_sys_ts, &phc_err, &quality) ||
quality <= 0)
return;
LCL_CookTime(&sample_sys_ts, &sample_local_ts, &local_err);

View File

@@ -3,6 +3,7 @@
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020-2021
* Copyright (C) Anthony Brandon 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
@@ -37,11 +38,9 @@
#include "siv.h"
#include "socket.h"
#include "sched.h"
#include "tls.h"
#include "util.h"
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#define INVALID_SOCK_FD (-8)
struct RecordHeader {
@@ -75,7 +74,7 @@ struct NKSN_Instance_Record {
KeState state;
int sock_fd;
char *label;
gnutls_session_t tls_session;
TLS_Instance tls_session;
SCH_TimeoutID timeout_id;
int retry_factor;
@@ -85,8 +84,6 @@ struct NKSN_Instance_Record {
/* ================================================== */
static gnutls_priority_t priority_cache;
static int credentials_counter = 0;
static int clock_updates = 0;
@@ -206,71 +203,6 @@ check_message_format(struct Message *message, int eof)
/* ================================================== */
static gnutls_session_t
create_tls_session(int server_mode, int sock_fd, const char *server_name,
gnutls_certificate_credentials_t credentials,
gnutls_priority_t priority)
{
unsigned char alpn_name[sizeof (NKE_ALPN_NAME)];
gnutls_session_t session;
gnutls_datum_t alpn;
unsigned int flags;
int r;
r = gnutls_init(&session, GNUTLS_NONBLOCK | GNUTLS_NO_TICKETS |
(server_mode ? GNUTLS_SERVER : GNUTLS_CLIENT));
if (r < 0) {
LOG(LOGS_ERR, "Could not %s TLS session : %s", "create", gnutls_strerror(r));
return NULL;
}
if (!server_mode) {
assert(server_name);
if (!UTI_IsStringIP(server_name)) {
r = gnutls_server_name_set(session, GNUTLS_NAME_DNS, server_name, strlen(server_name));
if (r < 0)
goto error;
}
flags = 0;
if (clock_updates < CNF_GetNoCertTimeCheck()) {
flags |= GNUTLS_VERIFY_DISABLE_TIME_CHECKS | GNUTLS_VERIFY_DISABLE_TRUSTED_TIME_CHECKS;
DEBUG_LOG("Disabled time checks");
}
gnutls_session_set_verify_cert(session, server_name, flags);
}
r = gnutls_priority_set(session, priority);
if (r < 0)
goto error;
r = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, credentials);
if (r < 0)
goto error;
memcpy(alpn_name, NKE_ALPN_NAME, sizeof (alpn_name));
alpn.data = alpn_name;
alpn.size = sizeof (alpn_name) - 1;
r = gnutls_alpn_set_protocols(session, &alpn, 1, 0);
if (r < 0)
goto error;
gnutls_transport_set_int(session, sock_fd);
return session;
error:
LOG(LOGS_ERR, "Could not %s TLS session : %s", "set", gnutls_strerror(r));
gnutls_deinit(session);
return NULL;
}
/* ================================================== */
static void
stop_session(NKSN_Instance inst)
{
@@ -286,7 +218,7 @@ stop_session(NKSN_Instance inst)
Free(inst->label);
inst->label = NULL;
gnutls_deinit(inst->tls_session);
TLS_DestroyInstance(inst->tls_session);
inst->tls_session = NULL;
SCH_RemoveTimeout(inst->timeout_id);
@@ -308,21 +240,6 @@ session_timeout(void *arg)
/* ================================================== */
static int
check_alpn(NKSN_Instance inst)
{
gnutls_datum_t alpn;
if (gnutls_alpn_get_selected_protocol(inst->tls_session, &alpn) < 0 ||
alpn.size != sizeof (NKE_ALPN_NAME) - 1 ||
memcmp(alpn.data, NKE_ALPN_NAME, sizeof (NKE_ALPN_NAME) - 1) != 0)
return 0;
return 1;
}
/* ================================================== */
static void
set_input_output(NKSN_Instance inst, int output)
{
@@ -364,6 +281,7 @@ static int
handle_event(NKSN_Instance inst, int event)
{
struct Message *message = &inst->message;
TLS_Status s;
int r;
DEBUG_LOG("Session event %d fd=%d state=%d", event, inst->sock_fd, (int)inst->state);
@@ -390,56 +308,28 @@ handle_event(NKSN_Instance inst, int event)
return 0;
case KE_HANDSHAKE:
r = gnutls_handshake(inst->tls_session);
if (r < 0) {
if (gnutls_error_is_fatal(r)) {
gnutls_datum_t cert_error;
/* Get a description of verification errors */
if (r != GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR ||
gnutls_certificate_verification_status_print(
gnutls_session_get_verify_cert_status(inst->tls_session),
gnutls_certificate_type_get(inst->tls_session), &cert_error, 0) < 0)
cert_error.data = NULL;
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
"TLS handshake with %s failed : %s%s%s", inst->label, gnutls_strerror(r),
cert_error.data ? " " : "", cert_error.data ? (const char *)cert_error.data : "");
if (cert_error.data)
gnutls_free(cert_error.data);
s = TLS_DoHandshake(inst->tls_session);
switch (s) {
case TLS_SUCCESS:
break;
case TLS_AGAIN_OUTPUT:
case TLS_AGAIN_INPUT:
set_input_output(inst, s == TLS_AGAIN_OUTPUT);
return 0;
default:
stop_session(inst);
/* Increase the retry interval if the handshake did not fail due
to the other end closing the connection */
if (r != GNUTLS_E_PULL_ERROR && r != GNUTLS_E_PREMATURE_TERMINATION)
if (s != TLS_CLOSED)
inst->retry_factor = NKE_RETRY_FACTOR2_TLS;
return 0;
}
/* Disable output when the handshake is trying to receive data */
set_input_output(inst, gnutls_record_get_direction(inst->tls_session));
return 0;
}
inst->retry_factor = NKE_RETRY_FACTOR2_TLS;
if (DEBUG) {
char *description = gnutls_session_get_desc(inst->tls_session);
DEBUG_LOG("Handshake with %s completed %s",
inst->label, description ? description : "");
gnutls_free(description);
}
if (!check_alpn(inst)) {
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR, "NTS-KE not supported by %s", inst->label);
stop_session(inst);
return 0;
}
/* Client will send a request to the server */
change_state(inst, inst->server ? KE_RECEIVE : KE_SEND);
return 0;
@@ -448,16 +338,17 @@ handle_event(NKSN_Instance inst, int event)
assert(inst->new_message && message->complete);
assert(message->length <= sizeof (message->data) && message->length > message->sent);
r = gnutls_record_send(inst->tls_session, &message->data[message->sent],
message->length - message->sent);
s = TLS_Send(inst->tls_session, &message->data[message->sent],
message->length - message->sent, &r);
if (r < 0) {
if (gnutls_error_is_fatal(r)) {
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
"Could not send NTS-KE message to %s : %s", inst->label, gnutls_strerror(r));
switch (s) {
case TLS_SUCCESS:
break;
case TLS_AGAIN_OUTPUT:
return 0;
default:
stop_session(inst);
}
return 0;
return 0;
}
DEBUG_LOG("Sent %d bytes to %s", r, inst->label);
@@ -480,26 +371,24 @@ handle_event(NKSN_Instance inst, int event)
return 0;
}
r = gnutls_record_recv(inst->tls_session, &message->data[message->length],
sizeof (message->data) - message->length);
s = TLS_Receive(inst->tls_session, &message->data[message->length],
sizeof (message->data) - message->length, &r);
if (r < 0) {
/* Handle a renegotiation request on both client and server as
a protocol error */
if (gnutls_error_is_fatal(r) || r == GNUTLS_E_REHANDSHAKE) {
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
"Could not receive NTS-KE message from %s : %s",
inst->label, gnutls_strerror(r));
switch (s) {
case TLS_SUCCESS:
break;
case TLS_AGAIN_INPUT:
return 0;
default:
stop_session(inst);
}
return 0;
return 0;
}
DEBUG_LOG("Received %d bytes from %s", r, inst->label);
message->length += r;
} while (gnutls_record_check_pending(inst->tls_session) > 0);
} while (TLS_CheckPending(inst->tls_session));
if (!check_message_format(message, r == 0)) {
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
@@ -519,18 +408,18 @@ handle_event(NKSN_Instance inst, int event)
return 1;
case KE_SHUTDOWN:
r = gnutls_bye(inst->tls_session, GNUTLS_SHUT_RDWR);
s = TLS_Shutdown(inst->tls_session);
if (r < 0) {
if (gnutls_error_is_fatal(r)) {
DEBUG_LOG("Shutdown with %s failed : %s", inst->label, gnutls_strerror(r));
switch (s) {
case TLS_SUCCESS:
break;
case TLS_AGAIN_OUTPUT:
case TLS_AGAIN_INPUT:
set_input_output(inst, s == TLS_AGAIN_OUTPUT);
return 0;
default:
stop_session(inst);
return 0;
}
/* Disable output when the TLS shutdown is trying to receive data */
set_input_output(inst, gnutls_record_get_direction(inst->tls_session));
return 0;
}
SCK_ShutdownConnection(inst->sock_fd);
@@ -592,36 +481,18 @@ handle_step(struct timespec *raw, struct timespec *cooked, double dfreq,
/* ================================================== */
static int gnutls_initialised = 0;
static int tls_initialised = 0;
static int
init_gnutls(void)
init_tls(void)
{
int r;
if (gnutls_initialised)
if (tls_initialised)
return 1;
r = gnutls_global_init();
if (r < 0)
LOG_FATAL("Could not initialise %s : %s", "gnutls", gnutls_strerror(r));
/* Prepare a priority cache for server and client NTS-KE sessions
(the NTS specification requires TLS1.3 or later) */
r = gnutls_priority_init2(&priority_cache,
"-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.2:-VERS-DTLS-ALL",
NULL, GNUTLS_PRIORITY_INIT_DEF_APPEND);
if (r < 0) {
LOG(LOGS_ERR, "Could not initialise %s : %s",
"priority cache for TLS", gnutls_strerror(r));
gnutls_global_deinit();
if (!TLS_Initialise(&get_time))
return 0;
}
/* Use our clock instead of the system clock in certificate verification */
gnutls_global_set_time_function(get_time);
gnutls_initialised = 1;
tls_initialised = 1;
DEBUG_LOG("Initialised");
LCL_AddParameterChangeHandler(handle_step, NULL);
@@ -632,16 +503,15 @@ init_gnutls(void)
/* ================================================== */
static void
deinit_gnutls(void)
deinit_tls(void)
{
if (!gnutls_initialised || credentials_counter > 0)
if (!tls_initialised || credentials_counter > 0)
return;
LCL_RemoveParameterChangeHandler(handle_step, NULL);
gnutls_priority_deinit(priority_cache);
gnutls_global_deinit();
gnutls_initialised = 0;
TLS_Finalise();
tls_initialised = 0;
DEBUG_LOG("Deinitialised");
}
@@ -652,67 +522,21 @@ create_credentials(const char **certs, const char **keys, int n_certs_keys,
const char **trusted_certs, uint32_t *trusted_certs_ids,
int n_trusted_certs, uint32_t trusted_cert_set)
{
gnutls_certificate_credentials_t credentials = NULL;
int i, r;
TLS_Credentials credentials;
if (!init_gnutls())
if (!init_tls())
return NULL;
r = gnutls_certificate_allocate_credentials(&credentials);
if (r < 0)
goto error;
if (certs && keys) {
BRIEF_ASSERT(!trusted_certs && !trusted_certs_ids);
for (i = 0; i < n_certs_keys; i++) {
if (!UTI_CheckFilePermissions(keys[i], 0771))
;
r = gnutls_certificate_set_x509_key_file(credentials, certs[i], keys[i],
GNUTLS_X509_FMT_PEM);
if (r < 0)
goto error;
}
} else {
BRIEF_ASSERT(!certs && !keys && n_certs_keys <= 0);
if (trusted_cert_set == 0 && !CNF_GetNoSystemCert()) {
r = gnutls_certificate_set_x509_system_trust(credentials);
if (r < 0)
goto error;
}
if (trusted_certs && trusted_certs_ids) {
for (i = 0; i < n_trusted_certs; i++) {
struct stat buf;
if (trusted_certs_ids[i] != trusted_cert_set)
continue;
if (stat(trusted_certs[i], &buf) == 0 && S_ISDIR(buf.st_mode))
r = gnutls_certificate_set_x509_trust_dir(credentials, trusted_certs[i],
GNUTLS_X509_FMT_PEM);
else
r = gnutls_certificate_set_x509_trust_file(credentials, trusted_certs[i],
GNUTLS_X509_FMT_PEM);
if (r < 0)
goto error;
DEBUG_LOG("Added %d trusted certs from %s", r, trusted_certs[i]);
}
}
credentials = TLS_CreateCredentials(certs, keys, n_certs_keys, trusted_certs,
trusted_certs_ids, n_trusted_certs, trusted_cert_set);
if (!credentials) {
deinit_tls();
return NULL;
}
credentials_counter++;
return (NKSN_Credentials)credentials;
error:
LOG(LOGS_ERR, "Could not set credentials : %s", gnutls_strerror(r));
if (credentials)
gnutls_certificate_free_credentials(credentials);
deinit_gnutls();
return NULL;
return credentials;
}
/* ================================================== */
@@ -737,9 +561,9 @@ NKSN_CreateClientCertCredentials(const char **certs, uint32_t *ids,
void
NKSN_DestroyCertCredentials(NKSN_Credentials credentials)
{
gnutls_certificate_free_credentials((gnutls_certificate_credentials_t)credentials);
TLS_DestroyCredentials(credentials);
credentials_counter--;
deinit_gnutls();
deinit_tls();
}
/* ================================================== */
@@ -789,9 +613,9 @@ NKSN_StartSession(NKSN_Instance inst, int sock_fd, const char *label,
{
assert(inst->state == KE_STOPPED);
inst->tls_session = create_tls_session(inst->server, sock_fd, inst->server_name,
(gnutls_certificate_credentials_t)credentials,
priority_cache);
inst->tls_session = TLS_CreateInstance(inst->server, sock_fd, inst->server_name,
label, NKE_ALPN_NAME, credentials,
clock_updates < CNF_GetNoCertTimeCheck());
if (!inst->tls_session)
return 0;
@@ -899,19 +723,15 @@ NKSN_GetKeys(NKSN_Instance inst, SIV_Algorithm algorithm, SIV_Algorithm exporter
context.algorithm = htons(exporter_algorithm);
context.is_s2c = 0;
if (gnutls_prf_rfc5705(inst->tls_session,
sizeof (NKE_EXPORTER_LABEL) - 1, NKE_EXPORTER_LABEL,
sizeof (context) - 1, (char *)&context,
length, (char *)c2s->key) < 0) {
if (!TLS_ExportKey(inst->tls_session, sizeof (NKE_EXPORTER_LABEL) - 1, NKE_EXPORTER_LABEL,
sizeof (context) - 1, &context, length, c2s->key)) {
DEBUG_LOG("Could not export key");
return 0;
}
context.is_s2c = 1;
if (gnutls_prf_rfc5705(inst->tls_session,
sizeof (NKE_EXPORTER_LABEL) - 1, NKE_EXPORTER_LABEL,
sizeof (context) - 1, (char *)&context,
length, (char *)s2c->key) < 0) {
if (!TLS_ExportKey(inst->tls_session, sizeof (NKE_EXPORTER_LABEL) - 1, NKE_EXPORTER_LABEL,
sizeof (context) - 1, &context, length, s2c->key)) {
DEBUG_LOG("Could not export key");
return 0;
}

View File

@@ -244,7 +244,7 @@ RCL_AddRefclock(RefclockParameters *params)
inst->source = SRC_CreateNewInstance(inst->ref_id, SRC_REFCLOCK, 0, params->sel_options,
NULL, params->min_samples, params->max_samples,
0.0, 0.0);
0.0, 0.0, params->max_unreach);
DEBUG_LOG("refclock %s refid=%s poll=%d dpoll=%d filter=%d",
params->driver_name, UTI_RefidToString(inst->ref_id),
@@ -427,10 +427,21 @@ convert_tai_offset(struct timespec *sample_time, double *offset)
}
static int
accumulate_sample(RCL_Instance instance, struct timespec *sample_time, double offset, double dispersion)
accumulate_sample(RCL_Instance instance, struct timespec *sample_time, double offset, double dispersion,
int quality)
{
NTP_Sample sample;
instance->reached++;
/* Don't accumulate the sample if the driver is suggesting it should be
dropped due to low quality. The only reason it went so far was to update
the reachability. */
if (quality < 1) {
DEBUG_LOG("refclock sample ignored quality=%d", quality);
return 0;
}
sample.time = *sample_time;
sample.offset = offset;
sample.peer_delay = instance->delay;
@@ -443,14 +454,14 @@ accumulate_sample(RCL_Instance instance, struct timespec *sample_time, double of
int
RCL_AddSample(RCL_Instance instance, struct timespec *sample_time,
struct timespec *ref_time, int leap)
struct timespec *ref_time, int leap, int quality)
{
double correction, dispersion, raw_offset, offset;
struct timespec cooked_time;
if (instance->pps_forced)
return RCL_AddPulse(instance, sample_time,
1.0e-9 * (sample_time->tv_nsec - ref_time->tv_nsec));
1.0e-9 * (sample_time->tv_nsec - ref_time->tv_nsec), quality);
raw_offset = UTI_DiffTimespecsToDouble(ref_time, sample_time);
@@ -486,7 +497,7 @@ RCL_AddSample(RCL_Instance instance, struct timespec *sample_time,
return 0;
}
if (!accumulate_sample(instance, &cooked_time, offset, dispersion))
if (!accumulate_sample(instance, &cooked_time, offset, dispersion, quality))
return 0;
instance->pps_active = 0;
@@ -501,7 +512,7 @@ RCL_AddSample(RCL_Instance instance, struct timespec *sample_time,
}
int
RCL_AddPulse(RCL_Instance instance, struct timespec *pulse_time, double second)
RCL_AddPulse(RCL_Instance instance, struct timespec *pulse_time, double second, int quality)
{
double correction, dispersion;
struct timespec cooked_time;
@@ -513,7 +524,7 @@ RCL_AddPulse(RCL_Instance instance, struct timespec *pulse_time, double second)
if (!UTI_IsTimeOffsetSane(pulse_time, 0.0))
return 0;
return RCL_AddCookedPulse(instance, &cooked_time, second, dispersion, correction);
return RCL_AddCookedPulse(instance, &cooked_time, second, dispersion, correction, quality);
}
static int
@@ -539,7 +550,7 @@ check_pulse_edge(RCL_Instance instance, double offset, double distance)
int
RCL_AddCookedPulse(RCL_Instance instance, struct timespec *cooked_time,
double second, double dispersion, double raw_correction)
double second, double dispersion, double raw_correction, int quality)
{
double offset;
int rate;
@@ -641,7 +652,7 @@ RCL_AddCookedPulse(RCL_Instance instance, struct timespec *cooked_time,
return 0;
}
if (!accumulate_sample(instance, cooked_time, offset, dispersion))
if (!accumulate_sample(instance, cooked_time, offset, dispersion, quality))
return 0;
instance->leap_status = leap;
@@ -657,12 +668,6 @@ RCL_AddCookedPulse(RCL_Instance instance, struct timespec *cooked_time,
return 1;
}
void
RCL_UpdateReachability(RCL_Instance instance)
{
instance->reached++;
}
double
RCL_GetPrecision(RCL_Instance instance)
{

View File

@@ -42,6 +42,7 @@ typedef struct {
int pps_rate;
int min_samples;
int max_samples;
int max_unreach;
int sel_options;
int max_lock_age;
int stratum;
@@ -77,11 +78,12 @@ extern char *RCL_GetDriverParameter(RCL_Instance instance);
extern void RCL_CheckDriverOptions(RCL_Instance instance, const char **options);
extern char *RCL_GetDriverOption(RCL_Instance instance, char *name);
extern int RCL_AddSample(RCL_Instance instance, struct timespec *sample_time,
struct timespec *ref_time, int leap);
extern int RCL_AddPulse(RCL_Instance instance, struct timespec *pulse_time, double second);
struct timespec *ref_time, int leap, int quality);
extern int RCL_AddPulse(RCL_Instance instance, struct timespec *pulse_time, double second,
int quality);
extern int RCL_AddCookedPulse(RCL_Instance instance, struct timespec *cooked_time,
double second, double dispersion, double raw_correction);
extern void RCL_UpdateReachability(RCL_Instance instance);
double second, double dispersion, double raw_correction,
int quality);
extern double RCL_GetPrecision(RCL_Instance instance);
extern int RCL_GetDriverPoll(RCL_Instance instance);

View File

@@ -66,7 +66,7 @@ static int phc_initialise(RCL_Instance instance)
{
const char *options[] = {"nocrossts", "extpps", "pin", "channel", "clear", NULL};
struct phc_instance *phc;
int phc_fd, rising_edge;
int rising_edge;
struct stat st;
char *path, *s;
@@ -74,19 +74,20 @@ static int phc_initialise(RCL_Instance instance)
path = RCL_GetDriverParameter(instance);
phc_fd = SYS_Linux_OpenPHC(path);
if (phc_fd < 0)
LOG_FATAL("Could not open PHC");
phc = MallocNew(struct phc_instance);
phc->fd = phc_fd;
if (fstat(phc_fd, &st) < 0 || !S_ISCHR(st.st_mode))
LOG_FATAL("Could not get PHC index");
phc->dev_index = minor(st.st_rdev);
phc->mode = 0;
phc->nocrossts = RCL_GetDriverOption(instance, "nocrossts") ? 1 : 0;
phc->extpps = RCL_GetDriverOption(instance, "extpps") ? 1 : 0;
UTI_ZeroTimespec(&phc->last_extts);
phc->fd = SYS_Linux_OpenPHC(path, phc->extpps ? O_RDWR : O_RDONLY);
if (phc->fd < 0)
LOG_FATAL("Could not open PHC");
if (fstat(phc->fd, &st) < 0 || !S_ISCHR(st.st_mode))
LOG_FATAL("Could not get PHC index");
phc->dev_index = minor(st.st_rdev);
phc->clock = HCL_CreateInstance(0, 16, UTI_Log2ToDouble(RCL_GetDriverPoll(instance)),
RCL_GetPrecision(instance));
@@ -154,13 +155,11 @@ static void process_ext_pulse(RCL_Instance instance, struct timespec *phc_ts)
}
phc->last_extts = *phc_ts;
RCL_UpdateReachability(instance);
if (!HCL_CookTime(phc->clock, phc_ts, &local_ts, &local_err))
return;
RCL_AddCookedPulse(instance, &local_ts, 1.0e-9 * local_ts.tv_nsec, local_err,
UTI_DiffTimespecsToDouble(phc_ts, &local_ts));
UTI_DiffTimespecsToDouble(phc_ts, &local_ts), 1);
}
static void read_ext_pulse(int fd, int event, void *anything)
@@ -197,7 +196,7 @@ static int phc_poll(RCL_Instance instance)
struct timespec phc_ts, sys_ts, local_ts, readings[PHC_READINGS][3];
struct phc_instance *phc;
double phc_err, local_err;
int n_readings;
int n_readings, quality;
phc = (struct phc_instance *)RCL_GetDriverData(instance);
@@ -206,14 +205,13 @@ static int phc_poll(RCL_Instance instance)
if (n_readings < 1)
return 0;
if (!phc->extpps)
RCL_UpdateReachability(instance);
if (!HCL_ProcessReadings(phc->clock, n_readings, readings, &phc_ts, &sys_ts, &phc_err))
if (!HCL_ProcessReadings(phc->clock, n_readings, readings,
&phc_ts, &sys_ts, &phc_err, &quality))
return 0;
LCL_CookTime(&sys_ts, &local_ts, &local_err);
HCL_AccumulateSample(phc->clock, &phc_ts, &local_ts, phc_err + local_err);
if (quality > 0)
HCL_AccumulateSample(phc->clock, &phc_ts, &local_ts, phc_err + local_err);
if (phc->extpps)
return 0;
@@ -221,7 +219,7 @@ static int phc_poll(RCL_Instance instance)
DEBUG_LOG("PHC offset: %+.9f err: %.9f",
UTI_DiffTimespecsToDouble(&phc_ts, &sys_ts), phc_err);
return RCL_AddSample(instance, &sys_ts, &phc_ts, LEAP_Normal);
return RCL_AddSample(instance, &sys_ts, &phc_ts, LEAP_Normal, quality);
}
RefclockDriver RCL_PHC_driver = {

View File

@@ -143,9 +143,7 @@ static int pps_poll(RCL_Instance instance)
pps->last_seq = seq;
RCL_UpdateReachability(instance);
return RCL_AddPulse(instance, &ts, 1.0e-9 * ts.tv_nsec);
return RCL_AddPulse(instance, &ts, 1.0e-9 * ts.tv_nsec, 1);
}
RefclockDriver RCL_PPS_driver = {

View File

@@ -58,9 +58,7 @@ static int refrtc_add_sample(RCL_Instance instance, struct timespec *now,
rtc_ts.tv_sec = rtc_sec;
rtc_ts.tv_nsec = rtc_nsec;
RCL_UpdateReachability(instance);
status = RCL_AddSample(instance, now, &rtc_ts, LEAP_Normal);
status = RCL_AddSample(instance, now, &rtc_ts, LEAP_Normal, 1);
return status;
}

View File

@@ -109,8 +109,6 @@ static int shm_poll(RCL_Instance instance)
shm->valid = 0;
RCL_UpdateReachability(instance);
receive_ts.tv_sec = t.receiveTimeStampSec;
clock_ts.tv_sec = t.clockTimeStampSec;
@@ -126,7 +124,7 @@ static int shm_poll(RCL_Instance instance)
UTI_NormaliseTimespec(&clock_ts);
UTI_NormaliseTimespec(&receive_ts);
return RCL_AddSample(instance, &receive_ts, &clock_ts, t.leap);
return RCL_AddSample(instance, &receive_ts, &clock_ts, t.leap, 1);
}
RefclockDriver RCL_SHM_driver = {

View File

@@ -129,17 +129,15 @@ static void read_sample(int sockfd, int event, void *anything)
UTI_TimevalToTimespec(&sample.tv, &sys_ts);
UTI_NormaliseTimespec(&sys_ts);
RCL_UpdateReachability(instance);
if (!UTI_IsTimeOffsetSane(&sys_ts, sample.offset))
return;
UTI_AddDoubleToTimespec(&sys_ts, sample.offset, &ref_ts);
if (sample.pulse) {
RCL_AddPulse(instance, &sys_ts, sample.offset);
RCL_AddPulse(instance, &sys_ts, sample.offset, 1);
} else {
RCL_AddSample(instance, &sys_ts, &ref_ts, sample.leap);
RCL_AddSample(instance, &sys_ts, &ref_ts, sample.leap, 1);
}
}

View File

@@ -604,18 +604,18 @@ error:
/* ================================================== */
static socklen_t
set_unix_sockaddr(struct sockaddr_un *sun, const char *addr)
set_unix_sockaddr(struct sockaddr_un *sa, const char *addr)
{
size_t len = strlen(addr);
if (len + 1 > sizeof (sun->sun_path)) {
if (len + 1 > sizeof (sa->sun_path)) {
DEBUG_LOG("Unix socket path %s too long", addr);
return 0;
}
memset(sun, 0, sizeof (*sun));
sun->sun_family = AF_UNIX;
memcpy(sun->sun_path, addr, len);
memset(sa, 0, sizeof (*sa));
sa->sun_family = AF_UNIX;
memcpy(sa->sun_path, addr, len);
return offsetof(struct sockaddr_un, sun_path) + len + 1;
}
@@ -641,12 +641,6 @@ bind_unix_address(int sock_fd, const char *addr, int flags)
return 0;
}
/* Allow access to everyone with access to the directory if requested */
if (flags & SCK_FLAG_ALL_PERMISSIONS && chmod(addr, 0666) < 0) {
DEBUG_LOG("Could not change permissions of %s : %s", addr, strerror(errno));
return 0;
}
return 1;
}

View File

@@ -34,8 +34,7 @@
#define SCK_FLAG_BLOCK 1
#define SCK_FLAG_BROADCAST 2
#define SCK_FLAG_RX_DEST_ADDR 4
#define SCK_FLAG_ALL_PERMISSIONS 8
#define SCK_FLAG_PRIV_BIND 16
#define SCK_FLAG_PRIV_BIND 8
/* Flags for receiving and sending messages */
#define SCK_FLAG_MSG_ERRQUEUE 1

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 */
@@ -106,13 +106,20 @@ struct SRC_Instance_Record {
/* Number of set bits in the reachability register */
int reachability_size;
/* Updates since last reference update */
/* Number of reachability updates with cleared register */
int unreachable_run;
/* Maximum number of reachability updates with cleared register to still
allow selection */
int max_unreachable_run;
/* Number of selection updates since last reference update */
int updates;
/* Updates left before allowing combining */
/* Number of selection updates left before allowing combining again */
int distant;
/* Updates with a status requiring source replacement */
/* Number of selection updates with a status requiring source replacement */
int bad;
/* Flag indicating the status of the source */
@@ -191,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;
@@ -224,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();
@@ -259,7 +270,8 @@ void SRC_Finalise(void)
SRC_Instance SRC_CreateNewInstance(uint32_t ref_id, SRC_Type type, int authenticated,
int sel_options, IPAddr *addr, int min_samples,
int max_samples, double min_delay, double asymmetry)
int max_samples, double min_delay, double asymmetry,
int max_unreach)
{
SRC_Instance result;
@@ -295,6 +307,7 @@ SRC_Instance SRC_CreateNewInstance(uint32_t ref_id, SRC_Type type, int authentic
result->authenticated = authenticated;
result->conf_sel_options = sel_options;
result->sel_options = sel_options;
result->max_unreachable_run = max_unreach;
result->active = 0;
SRC_SetRefid(result, ref_id, addr);
@@ -351,6 +364,7 @@ SRC_ResetInstance(SRC_Instance instance)
instance->updates = 0;
instance->reachability = 0;
instance->reachability_size = 0;
instance->unreachable_run = 0;
instance->distant = 0;
instance->bad = 0;
instance->status = SRC_BAD_STATS;
@@ -524,6 +538,13 @@ SRC_UpdateReachability(SRC_Instance inst, int reachable)
if (inst->reachability_size < SOURCE_REACH_BITS)
inst->reachability_size++;
if (inst->reachability == 0) {
if (inst->unreachable_run < INT_MAX)
inst->unreachable_run++;
} else {
inst->unreachable_run = 0;
}
/* Check if special reference update mode failed */
if (REF_GetMode() != REF_ModeNormal && special_mode_end()) {
REF_SetUnsynchronised();
@@ -545,6 +566,7 @@ SRC_ResetReachability(SRC_Instance inst)
{
inst->reachability = 0;
inst->reachability_size = 0;
inst->unreachable_run = 0;
SRC_UpdateReachability(inst, 0);
}
@@ -721,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;
@@ -765,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;
@@ -937,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;
@@ -992,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)
@@ -1013,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) {
@@ -1059,8 +1094,11 @@ SRC_SelectSource(SRC_Instance updated_inst)
/* Reachability is not a requirement for selection. An unreachable source
can still be selected if its newest sample is not older than the oldest
sample from reachable sources. */
if (!sources[i]->reachability && max_reach_sample_ago < si->last_sample_ago) {
sample from reachable sources and the number of consecutive unreachable
updates does not exceed the configured maximum. */
if (sources[i]->reachability == 0 &&
(si->last_sample_ago > max_reach_sample_ago ||
sources[i]->unreachable_run > sources[i]->max_unreachable_run)) {
mark_source(sources[i], SRC_STALE);
continue;
}
@@ -1343,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 */
@@ -1359,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 */
@@ -1371,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;
@@ -1896,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

@@ -69,7 +69,8 @@ typedef enum {
extern SRC_Instance SRC_CreateNewInstance(uint32_t ref_id, SRC_Type type, int authenticated,
int sel_options, IPAddr *addr, int min_samples,
int max_samples, double min_delay, double asymmetry);
int max_samples, double min_delay, double asymmetry,
int max_unreach);
/* Function to get rid of a source when it is being unconfigured.
This may cause the current reference source to be reselected, if this

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

@@ -49,6 +49,7 @@ typedef struct {
int max_sources;
int min_samples;
int max_samples;
int max_unreach;
int filter_length;
int interleaved;
int sel_options;
@@ -79,6 +80,7 @@ typedef struct {
#define SRC_DEFAULT_MAXSOURCES 4
#define SRC_DEFAULT_MINSAMPLES (-1)
#define SRC_DEFAULT_MAXSAMPLES (-1)
#define SRC_DEFAULT_MAXUNREACH 100000
#define SRC_DEFAULT_ASYMMETRY 1.0
#define SRC_DEFAULT_NTSPORT 4460
#define SRC_DEFAULT_CERTSET 0

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"
@@ -96,9 +97,6 @@ static int max_tick_bias;
static int hz;
static double dhz; /* And dbl prec version of same for arithmetic */
/* Flag indicating whether adjtimex() can step the clock */
static int have_setoffset;
/* The assumed rate at which the effective frequency and tick values are
updated in the kernel */
static int tick_update_hz;
@@ -298,16 +296,11 @@ get_version_specific_details(void)
get_kernel_version(&major, &minor, &patch);
DEBUG_LOG("Linux kernel major=%d minor=%d patch=%d", major, minor, patch);
if (kernelvercmp(major, minor, patch, 2, 2, 0) < 0) {
if (kernelvercmp(major, minor, patch, 2, 6, 39) < 0) {
LOG_FATAL("Kernel version not supported, sorry.");
}
if (kernelvercmp(major, minor, patch, 2, 6, 27) >= 0 &&
kernelvercmp(major, minor, patch, 2, 6, 33) < 0) {
/* In tickless kernels before 2.6.33 the frequency is updated in
a half-second interval */
tick_update_hz = 2;
} else if (kernelvercmp(major, minor, patch, 4, 19, 0) < 0) {
if (kernelvercmp(major, minor, patch, 4, 19, 0) < 0) {
/* In kernels before 4.19 the frequency is updated only on internal ticks
(CONFIG_HZ). As their rate cannot be reliably detected from the user
space, and it may not even be constant (CONFIG_NO_HZ - aka tickless),
@@ -315,13 +308,6 @@ get_version_specific_details(void)
tick_update_hz = 100;
}
/* ADJ_SETOFFSET support */
if (kernelvercmp(major, minor, patch, 2, 6, 39) < 0) {
have_setoffset = 0;
} else {
have_setoffset = 1;
}
DEBUG_LOG("hz=%d nominal_tick=%d max_tick_bias=%d tick_update_hz=%d",
hz, nominal_tick, max_tick_bias, tick_update_hz);
}
@@ -342,34 +328,6 @@ reset_adjtime_offset(void)
/* ================================================== */
static int
test_step_offset(void)
{
struct timex txc;
/* Zero maxerror and check it's reset to a maximum after ADJ_SETOFFSET.
This seems to be the only way how to verify that the kernel really
supports the ADJ_SETOFFSET mode as it doesn't return an error on unknown
mode. */
txc.modes = MOD_MAXERROR;
txc.maxerror = 0;
if (SYS_Timex_Adjust(&txc, 1) < 0 || txc.maxerror != 0)
return 0;
txc.modes = ADJ_SETOFFSET | ADJ_NANO;
txc.time.tv_sec = 0;
txc.time.tv_usec = 0;
if (SYS_Timex_Adjust(&txc, 1) < 0 || txc.maxerror < 100000)
return 0;
return 1;
}
/* ================================================== */
static void
report_time_adjust_blockers(void)
{
@@ -392,15 +350,10 @@ SYS_Linux_Initialise(void)
reset_adjtime_offset();
if (have_setoffset && !test_step_offset()) {
LOG(LOGS_INFO, "adjtimex() doesn't support ADJ_SETOFFSET");
have_setoffset = 0;
}
SYS_Timex_InitialiseWithFunctions(1.0e6 * max_tick_bias / nominal_tick,
1.0 / tick_update_hz,
read_frequency, set_frequency,
have_setoffset ? apply_step_offset : NULL,
apply_step_offset,
0.0, 0.0, NULL, NULL);
}
@@ -776,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)
@@ -927,7 +888,7 @@ verify_fd_is_phc(int phc_fd)
/* ================================================== */
static int
open_phc_by_iface_name(const char *iface)
open_phc_by_iface_name(const char *iface, int flags)
{
#ifdef HAVE_LINUX_TIMESTAMPING
struct ethtool_ts_info ts_info;
@@ -970,7 +931,7 @@ open_phc_by_iface_name(const char *iface)
"/dev/ptp%d", ts_info.phc_index) >= sizeof (phc_device))
return -1;
return open(phc_device, O_RDONLY);
return open(phc_device, flags);
#else
return -1;
#endif
@@ -979,21 +940,20 @@ open_phc_by_iface_name(const char *iface)
/* ================================================== */
int
SYS_Linux_OpenPHC(const char *device)
SYS_Linux_OpenPHC(const char *device, int flags)
{
int phc_fd = -1;
if (device[0] == '/') {
phc_fd = open(device, O_RDONLY);
phc_fd = open(device, flags);
if (phc_fd >= 0)
phc_fd = verify_fd_is_phc(phc_fd);
}
if (phc_fd < 0) {
phc_fd = open_phc_by_iface_name(device);
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);

View File

@@ -39,7 +39,7 @@ extern void SYS_Linux_EnableSystemCallFilter(int level, SYS_ProcessContext conte
extern int SYS_Linux_CheckKernelVersion(int req_major, int req_minor);
extern int SYS_Linux_OpenPHC(const char *device);
extern int SYS_Linux_OpenPHC(const char *device, int flags);
extern int SYS_Linux_GetPHCReadings(int fd, int nocrossts, int *reading_mode, int max_readings,
struct timespec tss[][3]);

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

@@ -100,7 +100,7 @@ Root delay : 0\.000000001 seconds
check_file_messages "20.* PPS1.*- N " 60 63 refclocks.log || test_fail
rm -f tmp/refclocks.log
min_sync_time=180
min_sync_time=80
max_sync_time=260
chronyc_start=270
client_conf="
@@ -137,7 +137,10 @@ maxupdateskew 10000"
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
# This fails occasionally due to the 4th unreachable PPS update
# (made just before the SHM update) causing SHM unselection due to
# a large root distance
#check_source_selection || test_fail
check_sync || test_fail
check_chronyc_output "^Reference ID.*50505330 \(PPS0\)
Stratum.*: 1

View File

@@ -114,7 +114,7 @@ limit=1
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 extfield F324 ipv6 ipv4" \
"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 maxunreach 8 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 ipv6 ipv4" \
"add server node1.net1.clk" \
"allow 1.2.3.4" \
"allow 1.2" \

43
test/simulation/150-maxunreach Executable file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env bash
. ./test.common
test_start "maxunreach option"
limit=5000
servers=2
client_server_options="minpoll 6 maxpoll 6 minsamples 64"
base_delay=$(cat <<-EOF | tr -d '\n'
(+ 1e-4
(* -1
(equal 0.1 from 3)
(equal 0.1 to 1)
(equal 0.1 (min time 2000) 2000))
(* 0.5
(+ (equal 0.1 from 2)
(equal 0.1 to 2))))
EOF
)
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 "Selected source 192.168.123.1" 1 1 || test_fail
check_log_messages "Selected source 192.168.123.2" 0 0 || test_fail
client_server_options="minpoll 6 maxpoll 6 minsamples 64 maxunreach 10"
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 "Selected source 192.168.123.1" 1 1 || test_fail
check_log_messages "Selected source 192.168.123.2" 1 1 || test_fail
check_log_messages "00:52:..Z Selected source 192.168.123.2" 1 1 || test_fail
test_pass

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,12 +213,15 @@ 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"
chown "$user:$(id -g "$user")" "$TEST_LIBDIR/driftfile" "$TEST_DIR/keys"
echo "0.0" > "$TEST_DIR/tempcomp"
chown "$user:$(id -g "$user")" "$TEST_LIBDIR/driftfile" "$TEST_DIR"/{keys,tempcomp}
(
echo "pidfile $(get_pidfile)"
@@ -390,7 +393,7 @@ run_chronyc() {
fi
$CHRONYC_WRAPPER $wrapper_options \
"$chronyc" -h "$host" $options "$@" > "$TEST_DIR/chronyc.out" && \
"$chronyc" -h "$host" -u "$(get_user)" $options "$@" > "$TEST_DIR/chronyc.out" && \
test_ok || test_error
[ $? -ne 0 ] && ret=1

View File

@@ -32,9 +32,9 @@ test_unit(void)
{
struct timespec start_hw_ts, start_local_ts, hw_ts, local_ts, ts;
struct timespec readings[MAX_READINGS][3];
int i, j, k, l, new_sample, n_readings, count, quality;
HCL_Instance clock;
double freq, jitter, interval, dj, err, sum;
int i, j, k, l, new_sample, n_readings, count;
LCL_Initialise();
TST_RegisterDummyDrivers();
@@ -84,11 +84,13 @@ test_unit(void)
UTI_ZeroTimespec(&hw_ts);
UTI_ZeroTimespec(&local_ts);
if (HCL_ProcessReadings(clock, n_readings, readings, &hw_ts, &local_ts, &err)) {
HCL_AccumulateSample(clock, &hw_ts, &local_ts, 2.0 * jitter);
new_sample = 1;
} else {
new_sample = 0;
new_sample = 0;
if (HCL_ProcessReadings(clock, n_readings, readings, &hw_ts, &local_ts, &err, &quality)) {
TEST_CHECK(quality >= 0 && quality <= 2);
if (quality > 0) {
HCL_AccumulateSample(clock, &hw_ts, &local_ts, 2.0 * jitter);
new_sample = 1;
}
}
}

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)

View File

@@ -28,7 +28,8 @@ create_source(SRC_Type type, IPAddr *addr, int authenticated, int sel_options)
return SRC_CreateNewInstance(UTI_IPToRefid(addr), type, authenticated, sel_options,
type == SRC_NTP ? addr : NULL,
SRC_DEFAULT_MINSAMPLES, SRC_DEFAULT_MAXSAMPLES, 0.0, 1.0);
SRC_DEFAULT_MINSAMPLES, SRC_DEFAULT_MAXSAMPLES, 0.0, 1.0,
SRC_DEFAULT_MAXUNREACH);
}
void

93
tls.h Normal file
View File

@@ -0,0 +1,93 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Anthony Brandon 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 the TLS session
*/
#ifndef GOT_TLS_H
#define GOT_TLS_H
struct TLS_Instance_Record;
typedef struct TLS_Instance_Record *TLS_Instance;
typedef void *TLS_Credentials;
typedef enum {
/* TLS operation succeeded */
TLS_SUCCESS,
/* TLS operation failed.
No more operations should be called and the session should be destroyed. */
TLS_FAILED,
/* TLS session closed by other end */
TLS_CLOSED,
/* The last TLS operation should be called again when input is ready */
TLS_AGAIN_INPUT,
/* The last TLS operation should be called again when output is ready */
TLS_AGAIN_OUTPUT,
} TLS_Status;
/* Initialize TLS */
extern int TLS_Initialise(time_t (*get_time)(time_t *t));
/* Deinitialize TLS */
extern void TLS_Finalise(void);
/* Create new TLS credentials instance */
extern TLS_Credentials TLS_CreateCredentials(const char **certs, const char **keys,
int n_certs_keys, const char **trusted_certs,
uint32_t * trusted_certs_ids, int n_trusted_certs,
uint32_t trusted_cert_set);
/* Destroy TLS credentials instance */
extern void TLS_DestroyCredentials(TLS_Credentials credentials);
/* Create new TLS session instance */
extern TLS_Instance TLS_CreateInstance(int server_mode, int sock_fd, const char *server_name,
const char *label, const char *alpn_name,
TLS_Credentials credentials, int disable_time_checks);
/* Destroy TLS instance */
extern void TLS_DestroyInstance(TLS_Instance inst);
/* Perform TLS handshake */
extern TLS_Status TLS_DoHandshake(TLS_Instance inst);
/* Send data over TLS */
extern TLS_Status TLS_Send(TLS_Instance inst, const void *data, int length, int *sent);
/* Receive data over TLS */
extern TLS_Status TLS_Receive(TLS_Instance inst, void *data, int length, int *received);
/* Check if there is data pending to read */
extern int TLS_CheckPending(TLS_Instance inst);
/* Perform TLS shutdown */
extern TLS_Status TLS_Shutdown(TLS_Instance inst);
/* Export key from TLS instance */
extern int TLS_ExportKey(TLS_Instance inst, int label_length, const char *label,
int context_length, const void *context, int key_length,
unsigned char *key);
#endif

409
tls_gnutls.c Normal file
View File

@@ -0,0 +1,409 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 2020-2021
* Copyright (C) Anthony Brandon 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.
*
**********************************************************************
=======================================================================
Routines implementing TLS session handling using the gnutls library.
*/
#include "config.h"
#include "sysincl.h"
#include "tls.h"
#include "conf.h"
#include "logging.h"
#include "memory.h"
#include "util.h"
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
struct TLS_Instance_Record {
gnutls_session_t session;
int server;
char *label;
char *alpn_name;
};
/* ================================================== */
static gnutls_priority_t priority_cache;
/* ================================================== */
int
TLS_Initialise(time_t (*get_time)(time_t *t))
{
int r = gnutls_global_init();
if (r < 0)
LOG_FATAL("Could not initialise %s : %s", "gnutls", gnutls_strerror(r));
/* Prepare a priority cache for server and client NTS-KE sessions
(the NTS specification requires TLS1.3 or later) */
r = gnutls_priority_init2(&priority_cache,
"-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.2:-VERS-DTLS-ALL",
NULL, GNUTLS_PRIORITY_INIT_DEF_APPEND);
if (r < 0) {
LOG(LOGS_ERR, "Could not initialise %s : %s",
"priority cache for TLS", gnutls_strerror(r));
gnutls_global_deinit();
return 0;
}
/* Use our clock instead of the system clock in certificate verification */
gnutls_global_set_time_function(get_time);
return 1;
}
/* ================================================== */
void
TLS_Finalise(void)
{
gnutls_priority_deinit(priority_cache);
gnutls_global_deinit();
}
/* ================================================== */
TLS_Credentials
TLS_CreateCredentials(const char **certs, const char **keys, int n_certs_keys,
const char **trusted_certs, uint32_t *trusted_certs_ids,
int n_trusted_certs, uint32_t trusted_cert_set)
{
gnutls_certificate_credentials_t credentials = NULL;
int i, r;
r = gnutls_certificate_allocate_credentials(&credentials);
if (r < 0)
goto error;
if (certs && keys) {
BRIEF_ASSERT(!trusted_certs && !trusted_certs_ids);
for (i = 0; i < n_certs_keys; i++) {
if (!UTI_CheckFilePermissions(keys[i], 0771))
;
r = gnutls_certificate_set_x509_key_file(credentials, certs[i], keys[i],
GNUTLS_X509_FMT_PEM);
if (r < 0)
goto error;
}
} else {
BRIEF_ASSERT(!certs && !keys && n_certs_keys <= 0);
if (trusted_cert_set == 0 && !CNF_GetNoSystemCert()) {
r = gnutls_certificate_set_x509_system_trust(credentials);
if (r < 0)
goto error;
}
if (trusted_certs && trusted_certs_ids) {
for (i = 0; i < n_trusted_certs; i++) {
struct stat buf;
if (trusted_certs_ids[i] != trusted_cert_set)
continue;
if (stat(trusted_certs[i], &buf) == 0 && S_ISDIR(buf.st_mode))
r = gnutls_certificate_set_x509_trust_dir(credentials, trusted_certs[i],
GNUTLS_X509_FMT_PEM);
else
r = gnutls_certificate_set_x509_trust_file(credentials, trusted_certs[i],
GNUTLS_X509_FMT_PEM);
if (r < 0)
goto error;
DEBUG_LOG("Added %d trusted certs from %s", r, trusted_certs[i]);
}
}
}
return credentials;
error:
LOG(LOGS_ERR, "Could not set credentials : %s", gnutls_strerror(r));
if (credentials)
gnutls_certificate_free_credentials(credentials);
return NULL;
}
/* ================================================== */
void
TLS_DestroyCredentials(TLS_Credentials credentials)
{
gnutls_certificate_free_credentials((gnutls_certificate_credentials_t)credentials);
}
/* ================================================== */
TLS_Instance
TLS_CreateInstance(int server_mode, int sock_fd, const char *server_name, const char *label,
const char *alpn_name, TLS_Credentials credentials, int disable_time_checks)
{
gnutls_datum_t alpn;
unsigned int flags;
int r;
TLS_Instance inst = MallocNew(struct TLS_Instance_Record);
inst->session = NULL;
inst->server = server_mode;
inst->label = Strdup(label);
inst->alpn_name = Strdup(alpn_name);
r = gnutls_init(&inst->session, GNUTLS_NONBLOCK | GNUTLS_NO_TICKETS |
(server_mode ? GNUTLS_SERVER : GNUTLS_CLIENT));
if (r < 0) {
LOG(LOGS_ERR, "Could not %s TLS session : %s", "create", gnutls_strerror(r));
inst->session = NULL;
goto error;
}
if (!server_mode) {
assert(server_name);
if (!UTI_IsStringIP(server_name)) {
r = gnutls_server_name_set(inst->session, GNUTLS_NAME_DNS, server_name,
strlen(server_name));
if (r < 0)
goto error;
}
flags = 0;
if (disable_time_checks) {
flags |= GNUTLS_VERIFY_DISABLE_TIME_CHECKS | GNUTLS_VERIFY_DISABLE_TRUSTED_TIME_CHECKS;
DEBUG_LOG("Disabled time checks");
}
gnutls_session_set_verify_cert(inst->session, server_name, flags);
}
r = gnutls_priority_set(inst->session, priority_cache);
if (r < 0)
goto error;
r = gnutls_credentials_set(inst->session, GNUTLS_CRD_CERTIFICATE, credentials);
if (r < 0)
goto error;
alpn.data = (unsigned char *)inst->alpn_name;
alpn.size = strlen(inst->alpn_name);
r = gnutls_alpn_set_protocols(inst->session, &alpn, 1, 0);
if (r < 0)
goto error;
gnutls_transport_set_int(inst->session, sock_fd);
return inst;
error:
LOG(LOGS_ERR, "Could not %s TLS session : %s", "set", gnutls_strerror(r));
TLS_DestroyInstance(inst);
return NULL;
}
/* ================================================== */
void
TLS_DestroyInstance(TLS_Instance inst)
{
if (inst->session)
gnutls_deinit(inst->session);
Free(inst->label);
Free(inst->alpn_name);
Free(inst);
}
/* ================================================== */
static int
check_alpn(TLS_Instance inst)
{
gnutls_datum_t alpn;
int length = strlen(inst->alpn_name);
if (gnutls_alpn_get_selected_protocol(inst->session, &alpn) < 0 ||
alpn.size != length || memcmp(alpn.data, inst->alpn_name, length) != 0)
return 0;
return 1;
}
/* ================================================== */
TLS_Status
TLS_DoHandshake(TLS_Instance inst)
{
int r = gnutls_handshake(inst->session);
if (r < 0) {
if (gnutls_error_is_fatal(r)) {
gnutls_datum_t cert_error;
/* Get a description of verification errors */
if (r != GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR ||
gnutls_certificate_verification_status_print(
gnutls_session_get_verify_cert_status(inst->session),
gnutls_certificate_type_get(inst->session), &cert_error, 0) < 0)
cert_error.data = NULL;
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
"TLS handshake with %s failed : %s%s%s", inst->label, gnutls_strerror(r),
cert_error.data ? " " : "", cert_error.data ? (const char *)cert_error.data : "");
if (cert_error.data)
gnutls_free(cert_error.data);
/* Increase the retry interval if the handshake did not fail due
to the other end closing the connection */
if (r != GNUTLS_E_PULL_ERROR && r != GNUTLS_E_PREMATURE_TERMINATION)
return TLS_FAILED;
return TLS_CLOSED;
}
return gnutls_record_get_direction(inst->session) ? TLS_AGAIN_OUTPUT : TLS_AGAIN_INPUT;
}
if (DEBUG) {
char *description = gnutls_session_get_desc(inst->session);
DEBUG_LOG("Handshake with %s completed %s", inst->label, description ? description : "");
gnutls_free(description);
}
if (!check_alpn(inst)) {
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR, "NTS-KE not supported by %s", inst->label);
return TLS_FAILED;
}
return TLS_SUCCESS;
}
/* ================================================== */
TLS_Status
TLS_Send(TLS_Instance inst, const void *data, int length, int *sent)
{
int r;
if (length < 0)
return TLS_FAILED;
r = gnutls_record_send(inst->session, data, length);
if (r < 0) {
if (gnutls_error_is_fatal(r)) {
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
"Could not send NTS-KE message to %s : %s", inst->label, gnutls_strerror(r));
return TLS_FAILED;
}
return TLS_AGAIN_OUTPUT;
}
*sent = r;
return TLS_SUCCESS;
}
/* ================================================== */
TLS_Status
TLS_Receive(TLS_Instance inst, void *data, int length, int *received)
{
int r;
if (length < 0)
return TLS_FAILED;
r = gnutls_record_recv(inst->session, data, length);
if (r < 0) {
/* Handle a renegotiation request on both client and server as
a protocol error */
if (gnutls_error_is_fatal(r) || r == GNUTLS_E_REHANDSHAKE) {
LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
"Could not receive NTS-KE message from %s : %s", inst->label, gnutls_strerror(r));
return TLS_FAILED;
}
return TLS_AGAIN_INPUT;
}
*received = r;
return TLS_SUCCESS;
}
/* ================================================== */
int
TLS_CheckPending(TLS_Instance inst)
{
return gnutls_record_check_pending(inst->session) > 0;
}
/* ================================================== */
TLS_Status
TLS_Shutdown(TLS_Instance inst)
{
int r = gnutls_bye(inst->session, GNUTLS_SHUT_RDWR);
if (r < 0) {
if (gnutls_error_is_fatal(r)) {
DEBUG_LOG("Shutdown with %s failed : %s", inst->label, gnutls_strerror(r));
return TLS_FAILED;
}
return gnutls_record_get_direction(inst->session) ? TLS_AGAIN_OUTPUT : TLS_AGAIN_INPUT;
}
return TLS_SUCCESS;
}
/* ================================================== */
int
TLS_ExportKey(TLS_Instance inst, int label_length, const char *label, int context_length,
const void *context, int key_length, unsigned char *key)
{
int r;
if (label_length < 0 || context_length < 0 || key_length < 0)
return 0;
r = gnutls_prf_rfc5705(inst->session, label_length, label, context_length, (char *)context,
key_length, (char *)key);
return r >= 0;
}

17
util.c
View File

@@ -545,12 +545,14 @@ UTI_CompareIPs(const IPAddr *a, const IPAddr *b, const IPAddr *mask)
char *
UTI_IPSockAddrToString(const IPSockAddr *sa)
{
char *result;
char buf[BUFFER_LENGTH], *result;
/* Copy to a separate buffer to avoid a compiler warning */
snprintf(buf, sizeof (buf), "%s", UTI_IPToString(&sa->ip_addr));
result = NEXT_BUFFER;
snprintf(result, BUFFER_LENGTH,
sa->ip_addr.family != IPADDR_INET6 ? "%s:%hu" : "[%s]:%hu",
UTI_IPToString(&sa->ip_addr), sa->port);
sa->ip_addr.family != IPADDR_INET6 ? "%s:%hu" : "[%s]:%hu", buf, sa->port);
return result;
}
@@ -1201,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);
@@ -1364,6 +1366,7 @@ FILE *
UTI_OpenFile(const char *basedir, const char *name, const char *suffix,
char mode, mode_t perm)
{
uint64_t attempts = 0, warn_attempts = 100;
const char *file_mode;
char path[PATH_MAX];
LOG_Severity severity;
@@ -1407,6 +1410,12 @@ try_again:
return NULL;
}
DEBUG_LOG("Removed %s", path);
if (++attempts == warn_attempts) {
LOG(LOGS_WARN, "Failing to replace %s (%"PRIu64" attempts)", path, attempts);
warn_attempts *= 10;
}
goto try_again;
}
LOG(severity, "Could not open %s : %s", path, strerror(errno));