Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f13dcb2
nts: Handle duplicate records in processing loop.
davidv1992 Dec 12, 2025
bfe5b35
nts: Use separate functions to provide normal and error responses.
davidv1992 Dec 12, 2025
f065843
nts: Add support for fixed key requests from pools.
davidv1992 Jun 27, 2025
2a282a5
nts: Add request protocol/algorithm information support.
davidv1992 Jun 27, 2025
0592493
Updated readme to document this is intended for the pool.
davidv1992 Nov 7, 2025
7b45f24
nts: Add support for long-lived sessions.
davidv1992 Nov 19, 2025
5f728fc
nts: Implement limits on the number of long lived connections.
davidv1992 Nov 21, 2025
46bc7e0
nts: Only allow pool extensions when authenticated.
davidv1992 Dec 4, 2025
92097cb
nts: Fix pool authentication tokens always reporting an error.
davidv1992 Jan 12, 2026
72aafef
dns: Introduce separate type for a DNS name lookup result.
davidv1992 Jan 7, 2026
19fd837
nts: Update nts server name from DNS lookup results.
davidv1992 Jan 7, 2026
9b34d4c
dns: Add parameter to DNS lookups to indicate whether its for NTS.
davidv1992 Jan 7, 2026
aa5c127
dns: Implement SRV record indirection for NTSKE lookups.
davidv1992 Jan 8, 2026
f0759f7
dns: Trim trailing dot from resolved SRV domain name.
davidv1992 Jan 12, 2026
9ef30e5
dns: Use SRV record port.
davidv1992 Jan 12, 2026
027cb7c
rename ip->ip_addr in DNS_SockAddrLookupResult
squell Feb 2, 2026
23854a0
formatting style
squell Feb 2, 2026
d365482
rename service_nts
squell Feb 2, 2026
0eea60d
Fixup unit tests for ip->ipaddr rename.
davidv1992 Mar 12, 2026
38d806f
Split off srv lookups into their own function.
davidv1992 Mar 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
This is a modified version of chrony 4.8, with added support for the protocols
needed for NTS pools. It is mainly intended for use as a server for the
experimental pool at https://experimental.ntspooltest.org/.

For inclusion in a pool, this version of chrony requires that the pool token is
added to the file specified by the ntsauthtokenfile configuration command. See
the crhony.conf documentation for details on how to do this.

The original README for chrony is included below for reference.

===============================================================================

This is the README for chrony.

What is chrony?
Expand Down
34 changes: 18 additions & 16 deletions client.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ get_addresses(const char *hostnames, int port)
struct Address *addr;
ARR_Instance addrs;
char *hostname, *s1, *s2;
IPAddr ip_addrs[DNS_MAX_ADDRESSES];
DNS_AddressLookupResult looked_up_addrs[DNS_MAX_ADDRESSES];
int i;

addrs = ARR_CreateInstance(sizeof (*addr));
Expand All @@ -175,17 +175,17 @@ get_addresses(const char *hostnames, int port)
addr->type = SCK_ADDR_UNIX;
addr->addr.path = Strdup(hostname);
} else {
if (DNS_Name2IPAddress(hostname, ip_addrs, DNS_MAX_ADDRESSES) != DNS_Success) {
if (DNS_Name2IPAddress(hostname, looked_up_addrs, DNS_MAX_ADDRESSES, 0) != DNS_Success) {
DEBUG_LOG("Could not get IP address for %s", hostname);
continue;
}

for (i = 0; i < DNS_MAX_ADDRESSES && ip_addrs[i].family != IPADDR_UNSPEC; i++) {
for (i = 0; i < DNS_MAX_ADDRESSES && looked_up_addrs[i].ip.family != IPADDR_UNSPEC; i++) {
addr = ARR_GetNewElement(addrs);
addr->type = SCK_ADDR_IP;
addr->addr.ip.ip_addr = ip_addrs[i];
addr->addr.ip.ip_addr = looked_up_addrs[i].ip;
addr->addr.ip.port = port;
DEBUG_LOG("Resolved %s to %s", hostname, UTI_IPToString(&ip_addrs[i]));
DEBUG_LOG("Resolved %s to %s", hostname, UTI_IPToString(&looked_up_addrs[i].ip));
}
}
}
Expand Down Expand Up @@ -449,11 +449,15 @@ bits_to_mask(int bits, int family, IPAddr *mask)
static int
parse_source_address(char *word, IPAddr *address)
{
DNS_AddressLookupResult lookup;

if (UTI_StringToIdIP(word, address))
return 1;

if (DNS_Name2IPAddress(word, address, 1) == DNS_Success)
if (DNS_Name2IPAddress(word, &lookup, 1, 0) == DNS_Success) {
*address = lookup.ip;
return 1;
}

return 0;
}
Expand Down Expand Up @@ -944,10 +948,10 @@ process_cmd_allowdeny(CMD_Request *msg, char *line, int cmd, int allcmd)
static int
process_cmd_accheck(CMD_Request *msg, char *line)
{
IPAddr ip;
DNS_AddressLookupResult lookup;
msg->command = htons(REQ_ACCHECK);
if (DNS_Name2IPAddress(line, &ip, 1) == DNS_Success) {
UTI_IPHostToNetwork(&ip, &msg->data.ac_check.ip);
if (DNS_Name2IPAddress(line, &lookup, 1, 0) == DNS_Success) {
UTI_IPHostToNetwork(&lookup.ip, &msg->data.ac_check.ip);
return 1;
} else {
LOG(LOGS_ERR, "Could not read address");
Expand All @@ -960,10 +964,10 @@ process_cmd_accheck(CMD_Request *msg, char *line)
static int
process_cmd_cmdaccheck(CMD_Request *msg, char *line)
{
IPAddr ip;
DNS_AddressLookupResult lookup;
msg->command = htons(REQ_CMDACCHECK);
if (DNS_Name2IPAddress(line, &ip, 1) == DNS_Success) {
UTI_IPHostToNetwork(&ip, &msg->data.ac_check.ip);
if (DNS_Name2IPAddress(line, &lookup, 1, 0) == DNS_Success) {
UTI_IPHostToNetwork(&lookup.ip, &msg->data.ac_check.ip);
return 1;
} else {
LOG(LOGS_ERR, "Could not read address");
Expand Down Expand Up @@ -1025,7 +1029,7 @@ process_cmd_add_source(CMD_Request *msg, char *line)
{
CPS_NTP_Source data;
CPS_Status status;
IPAddr ip_addr;
DNS_AddressLookupResult lookup;
int result = 0, type;
const char *opt_name, *word;

Expand All @@ -1051,7 +1055,7 @@ process_cmd_add_source(CMD_Request *msg, char *line)
/* Verify that the address is resolvable (chronyc and chronyd are
assumed to be running on the same host) */
if (strlen(data.name) >= sizeof (msg->data.ntp_source.name) ||
DNS_Name2IPAddress(data.name, &ip_addr, 1) != DNS_Success) {
DNS_Name2IPAddress(data.name, &lookup, 1, data.params.nts) != DNS_Success) {
LOG(LOGS_ERR, "Invalid host/IP address");
break;
}
Expand Down Expand Up @@ -3730,5 +3734,3 @@ main(int argc, char **argv)

return !ret;
}


4 changes: 3 additions & 1 deletion cmdparse.c
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ CPS_ParseAllowDeny(char *line, int *all, IPAddr *ip, int *subnet_bits)
char *p, *net, *slash;
uint32_t a, b, c;
int bits, len, n;
DNS_AddressLookupResult lookup_result;

p = CPS_SplitWord(line);

Expand Down Expand Up @@ -294,7 +295,8 @@ CPS_ParseAllowDeny(char *line, int *all, IPAddr *ip, int *subnet_bits)
}

/* The last possibility is a hostname */
if (bits < 0 && DNS_Name2IPAddress(net, ip, 1) == DNS_Success) {
if (bits < 0 && DNS_Name2IPAddress(net, &lookup_result, 1, 0) == DNS_Success) {
*ip = lookup_result.ip;
*subnet_bits = ip->family == IPADDR_INET6 ? 128 : 32;
return 1;
}
Expand Down
31 changes: 28 additions & 3 deletions conf.c
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ static char *rtc_device;
static int acquisition_port = -1;
static int ntp_port = NTP_PORT;
static char *keys_file = NULL;
static char *nts_auth_token_file = NULL;
static char *drift_file = NULL;
static int drift_file_interval = 3600;
static char *rtc_file = NULL;
Expand Down Expand Up @@ -281,6 +282,7 @@ static ARR_Instance nts_server_key_files; /* array of (char *) */
static int nts_server_port = NKE_PORT;
static int nts_server_processes = 1;
static int nts_server_connections = 100;
static int nts_longterm_connections = 5;
static int nts_refresh = 2419200; /* 4 weeks */
static int nts_rotate = 604800; /* 1 week */
static ARR_Instance nts_trusted_certs_paths; /* array of (char *) */
Expand Down Expand Up @@ -515,6 +517,7 @@ CNF_Finalise(void)
Free(dumpdir);
Free(hwclock_file);
Free(keys_file);
Free(nts_auth_token_file);
Free(leapsec_tz);
Free(leapsec_list);
Free(logdir);
Expand Down Expand Up @@ -662,6 +665,8 @@ CNF_ParseLine(const char *filename, int number, char *line)
parse_initstepslew(p);
} else if (!strcasecmp(command, "keyfile")) {
parse_string(p, &keys_file);
} else if (!strcasecmp(command, "ntsauthtokenfile")) {
parse_string(p, &nts_auth_token_file);
} else if (!strcasecmp(command, "leapsecmode")) {
parse_leapsecmode(p);
} else if (!strcasecmp(command, "leapsectz")) {
Expand Down Expand Up @@ -698,6 +703,8 @@ CNF_ParseLine(const char *filename, int number, char *line)
parse_double(p, &max_jitter);
} else if (!strcasecmp(command, "maxntsconnections")) {
parse_int(p, &nts_server_connections, 1, INT_MAX);
} else if (!strcasecmp(command, "maxntslongtermconnections")) {
parse_int(p, &nts_longterm_connections, 0, INT_MAX);
} else if (!strcasecmp(command, "maxsamples")) {
parse_int(p, &max_samples, 0, INT_MAX);
} else if (!strcasecmp(command, "maxslewrate")) {
Expand Down Expand Up @@ -1164,7 +1171,7 @@ static void
parse_initstepslew(char *line)
{
char *p, *hostname;
IPAddr ip_addr;
DNS_AddressLookupResult addr;

/* Ignore the line if chronyd was started with -R. */
if (restarted) {
Expand All @@ -1183,8 +1190,8 @@ parse_initstepslew(char *line)
hostname = p;
p = CPS_SplitWord(p);
if (*hostname) {
if (DNS_Name2IPAddress(hostname, &ip_addr, 1) == DNS_Success) {
ARR_AppendElement(init_sources, &ip_addr);
if (DNS_Name2IPAddress(hostname, &addr, 1, 0) == DNS_Success) {
ARR_AppendElement(init_sources, &addr.ip);
} else {
LOG(LOGS_WARN, "Could not resolve address of initstepslew server %s", hostname);
}
Expand Down Expand Up @@ -2000,6 +2007,8 @@ CNF_CheckReadOnlyAccess(void)

if (keys_file)
UTI_CheckReadOnlyAccess(keys_file);
if (nts_auth_token_file)
UTI_CheckReadOnlyAccess(nts_auth_token_file);
for (i = 0; i < ARR_GetSize(nts_server_key_files); i++)
UTI_CheckReadOnlyAccess(*(char **)ARR_GetElement(nts_server_key_files, i));
}
Expand Down Expand Up @@ -2215,6 +2224,14 @@ CNF_GetKeysFile(void)

/* ================================================== */

char *
CNF_GetNtsAuthTokenFile(void)
{
return nts_auth_token_file;
}

/* ================================================== */

double
CNF_GetRtcAutotrim(void)
{
Expand Down Expand Up @@ -2842,6 +2859,14 @@ CNF_GetNtsServerConnections(void)

/* ================================================== */

int
CNF_GetNtsLongtermConnections(void)
{
return nts_longterm_connections;
}

/* ================================================== */

int
CNF_GetNtsRefresh(void)
{
Expand Down
2 changes: 2 additions & 0 deletions conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ extern int CNF_GetLogRtc(void);
extern int CNF_GetLogRefclocks(void);
extern int CNF_GetLogTempComp(void);
extern char *CNF_GetKeysFile(void);
extern char *CNF_GetNtsAuthTokenFile(void);
extern char *CNF_GetRtcFile(void);
extern int CNF_GetManualEnabled(void);
extern ARR_Instance CNF_GetOpenCommands(void);
Expand Down Expand Up @@ -173,6 +174,7 @@ extern int CNF_GetNtsServerCertAndKeyFiles(const char ***certs, const char ***ke
extern int CNF_GetNtsServerPort(void);
extern int CNF_GetNtsServerProcesses(void);
extern int CNF_GetNtsServerConnections(void);
extern int CNF_GetNtsLongtermConnections(void);
extern int CNF_GetNtsRefresh(void);
extern int CNF_GetNtsRotate(void);
extern int CNF_GetNtsTrustedCertsPaths(const char ***paths, uint32_t **ids);
Expand Down
24 changes: 22 additions & 2 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ For better control, use the options below.
--disable-readline Disable line editing support
--without-editline Don't use editline even if it is available
--disable-sechash Disable support for hashes other than MD5
--without-getdns Don't use getdns
--without-nettle Don't use nettle even if it is available
--without-gnutls Don't use gnutls even if it is available
--without-nss Don't use NSS even if it is available
Expand All @@ -122,6 +123,7 @@ For better control, use the options below.
--disable-ipv6 Disable IPv6 support
--disable-rtc Don't include RTC even on Linux
--disable-privdrop Disable support for dropping root privileges
--disable-srv Disable support for resolving through SRV records
--without-libcap Don't use libcap even if it is available
--enable-scfilter Enable support for system call filtering
--without-seccomp Don't use seccomp even if it is available
Expand Down Expand Up @@ -221,6 +223,8 @@ feat_cmdmon=1
feat_refclock=1
feat_readline=1
try_editline=1
feat_srv=1
try_getdns=1
feat_sechash=1
try_nettle=1
try_nss=1
Expand Down Expand Up @@ -383,9 +387,15 @@ do
--without-tomcrypt )
try_tomcrypt=0
;;
--disable-srv )
feat_srv=0
;;
--disable-nts )
feat_nts=0
;;
--without-getdns )
try_getdns=0
;;
--without-gnutls )
try_gnutls=0
;;
Expand Down Expand Up @@ -970,6 +980,17 @@ EXTRA_OBJECTS="$EXTRA_OBJECTS $HASH_OBJ"
EXTRA_CLI_OBJECTS="$EXTRA_CLI_OBJECTS $HASH_OBJ"
LIBS="$LIBS $HASH_LINK"

if [ $feat_srv = "1" ] && [ $try_getdns = "1" ]; then
if test_code 'getdns' 'getdns/getdns.h' '' '-lgetdns' '
getdns_context *context;
getdns_context_create(&context, 1);'
then
EXTRA_LIBS="$EXTRA_LIBS -lgetdns"
EXTRA_CLI_LIBS="$EXTRA_CLI_LIBS -lgetdns"
add_def FEAT_SRV
fi
fi

if [ $feat_nts = "1" ] && [ $try_gnutls = "1" ]; then
if [ "$HASH_OBJ" = "hash_gnutls.o" ]; then
test_cflags=""
Expand Down Expand Up @@ -1096,7 +1117,7 @@ add_def MAIL_PROGRAM "\"$mail_program\""

common_features="`get_features SECHASH IPV6 DEBUG`"
chronyc_features="`get_features READLINE`"
chronyd_features="`get_features CMDMON REFCLOCK RTC PRIVDROP SCFILTER SIGND NTS`"
chronyd_features="`get_features CMDMON REFCLOCK RTC PRIVDROP SCFILTER SIGND NTS SRV`"
add_def CHRONYC_FEATURES "\"$chronyc_features $common_features\""
add_def CHRONYD_FEATURES "\"$chronyd_features $common_features\""
echo "Features : $chronyd_features $chronyc_features $common_features"
Expand Down Expand Up @@ -1141,4 +1162,3 @@ done

# =======================================================================
# vim:et:sw=2:ht=2:sts=2:fdm=marker:cms=#%s

16 changes: 16 additions & 0 deletions doc/chrony.conf.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1860,6 +1860,15 @@ This directive can be used multiple times to specify multiple keys. The number
of keys must be the same as the number of certificates, and the corresponding
files must be specified in the same order.

[[ntsauthtokenfile]]*ntsauthtokenfile* _file_::
This directive specifies a file containing the authentication tokens used
to check a pool connecting to chrony is allowed to do so. The file needs to be
readable by the user under which *chronyd* is running after dropping root
privileges. For security reasons, it should not be readable by other users.
+
The file should contain one authentication token per line, and all lines
including the last one should be ended with a newline. Emtpy lines are ignored.

[[ntsprocesses]]*ntsprocesses* _processes_::
This directive specifies how many helper processes will *chronyd* operating
as an NTS server start for handling client NTS-KE requests in order to improve
Expand All @@ -1873,6 +1882,13 @@ per process that the NTS server will accept. The default value is 100. The
maximum practical value is half of the system *FD_SETSIZE* constant (usually
1024).

[[maxntslongtermconnections]]*maxntslongtermconnections* _connections_::
This directive specifies the maximum number of concurrent longterm NTS-KE
connections per process that the NTS server will accept from pools. The
default value is 5. This value should be kept smaller than *maxntsconnections*,
otherwise clients trying to use the server may get starved from new
connections.

[[ntsaeads2]]*ntsaeads* _ID_...::
This directive specifies a list of IDs of Authenticated Encryption with
Associated Data (AEAD) algorithms enabled for NTS authentication of NTP
Expand Down
Loading