From f13dcb222cf753ac5d317bc4f355fde06899995d Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 12 Dec 2025 09:46:15 +0100 Subject: [PATCH 01/20] nts: Handle duplicate records in processing loop. This makes error checking at the end of the function simpler, which is particularly wanted once pool features are added later. --- nts_ke_server.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nts_ke_server.c b/nts_ke_server.c index 33aef082..f16eeb85 100644 --- a/nts_ke_server.c +++ b/nts_ke_server.c @@ -423,7 +423,7 @@ prepare_response(NKSN_Instance session, int error, int next_protocol, int aead_a static int process_request(NKSN_Instance session) { - int next_protocol_records = 0, aead_algorithm_records = 0; + int have_next_protocol_record = 0, have_aead_algorithm_record = 0; int next_protocol_values = 0, aead_algorithm_values = 0; int next_protocol = -1, aead_algorithm = -1, error = -1; int i, j, critical, type, length; @@ -439,12 +439,12 @@ process_request(NKSN_Instance session) switch (type) { case NKE_RECORD_NEXT_PROTOCOL: - if (!critical || length < 2 || length % 2 != 0) { + if (!critical || length < 2 || length % 2 != 0 || have_next_protocol_record) { error = NKE_ERROR_BAD_REQUEST; break; } - next_protocol_records++; + have_next_protocol_record = 1; for (i = 0; i < MIN(length, sizeof (data)) / 2; i++) { next_protocol_values++; @@ -453,12 +453,12 @@ process_request(NKSN_Instance session) } break; case NKE_RECORD_AEAD_ALGORITHM: - if (length < 2 || length % 2 != 0) { + if (length < 2 || length % 2 != 0 || have_aead_algorithm_record) { error = NKE_ERROR_BAD_REQUEST; break; } - aead_algorithm_records++; + have_aead_algorithm_record = 1; for (i = 0; i < MIN(length, sizeof (data)) / 2; i++) { aead_algorithm_values++; @@ -489,9 +489,9 @@ process_request(NKSN_Instance session) } if (error < 0) { - if (next_protocol_records != 1 || next_protocol_values < 1 || + if (!have_next_protocol_record || next_protocol_values < 1 || (next_protocol == NKE_NEXT_PROTOCOL_NTPV4 && - (aead_algorithm_records != 1 || aead_algorithm_values < 1))) + (!have_aead_algorithm_record || aead_algorithm_values < 1))) error = NKE_ERROR_BAD_REQUEST; } From bfe5b3542cd2335ea52ce1dbc4746ad5fc4db23e Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 12 Dec 2025 10:02:53 +0100 Subject: [PATCH 02/20] nts: Use separate functions to provide normal and error responses. This perpares for further separate functions for special responses when handling pool requests. --- nts_ke_server.c | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/nts_ke_server.c b/nts_ke_server.c index f16eeb85..64452c36 100644 --- a/nts_ke_server.c +++ b/nts_ke_server.c @@ -337,7 +337,28 @@ helper_signal(int x) /* ================================================== */ static int -prepare_response(NKSN_Instance session, int error, int next_protocol, int aead_algorithm, +prepare_error_response(NKSN_Instance session, int error) +{ + uint16_t datum; + + DEBUG_LOG("NTS KE error response: error=%d", error); + + NKSN_BeginMessage(session); + + datum = htons(error); + if (!NKSN_AddRecord(session, 1, NKE_RECORD_ERROR, &datum, sizeof (datum))) + return 0; + + if (!NKSN_EndMessage(session)) + return 0; + + return 1; +} + +/* ================================================== */ + +static int +prepare_response(NKSN_Instance session, int next_protocol, int aead_algorithm, int compliant_128gcm) { SIV_Algorithm exporter_algorithm; @@ -347,15 +368,11 @@ prepare_response(NKSN_Instance session, int error, int next_protocol, int aead_a uint16_t datum; int i; - DEBUG_LOG("NTS KE response: error=%d next=%d aead=%d", error, next_protocol, aead_algorithm); + DEBUG_LOG("NTS KE response: next=%d aead=%d", next_protocol, aead_algorithm); NKSN_BeginMessage(session); - if (error >= 0) { - datum = htons(error); - if (!NKSN_AddRecord(session, 1, NKE_RECORD_ERROR, &datum, sizeof (datum))) - return 0; - } else if (next_protocol < 0) { + if (next_protocol < 0) { if (!NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, NULL, 0)) return 0; } else if (aead_algorithm < 0) { @@ -495,8 +512,13 @@ process_request(NKSN_Instance session) error = NKE_ERROR_BAD_REQUEST; } - if (!prepare_response(session, error, next_protocol, aead_algorithm, compliant_128gcm)) - return 0; + if (error >= 0) { + if (!prepare_error_response(session, error)) + return 0; + } else { + if (!prepare_response(session, next_protocol, aead_algorithm, compliant_128gcm)) + return 0; + } return 1; } From f065843761b4c12825cb3d38627f220bd723712e Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 27 Jun 2025 15:39:26 +0200 Subject: [PATCH 03/20] nts: Add support for fixed key requests from pools. Add support for fixed key requests, which draft-venhoek-nts-pool requires to allow the pool to handle key exchange requests on behalf of the time source. In a fixed key request, the NTS keys are explicitly provided instead of being derived from the TLS connection. --- nts_ke.h | 1 + nts_ke_server.c | 38 +++++++++++--- test/unit/nts_ke_server.c | 105 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 8 deletions(-) diff --git a/nts_ke.h b/nts_ke.h index 2cfbb085..cb12b20c 100644 --- a/nts_ke.h +++ b/nts_ke.h @@ -41,6 +41,7 @@ #define NKE_RECORD_NTPV4_SERVER_NEGOTIATION 6 #define NKE_RECORD_NTPV4_PORT_NEGOTIATION 7 #define NKE_RECORD_COMPLIANT_128GCM_EXPORT 1024 +#define NKE_RECORD_FIXED_KEY 0x4002 #define NKE_NEXT_PROTOCOL_NTPV4 0 diff --git a/nts_ke_server.c b/nts_ke_server.c index 64452c36..87fd6d90 100644 --- a/nts_ke_server.c +++ b/nts_ke_server.c @@ -359,10 +359,9 @@ prepare_error_response(NKSN_Instance session, int error) static int prepare_response(NKSN_Instance session, int next_protocol, int aead_algorithm, - int compliant_128gcm) + int compliant_128gcm, int have_keys, NKE_Context *context) { SIV_Algorithm exporter_algorithm; - NKE_Context context; NKE_Cookie cookie; char *ntp_server; uint16_t datum; @@ -408,7 +407,7 @@ prepare_response(NKSN_Instance session, int next_protocol, int aead_algorithm, return 0; } - context.algorithm = aead_algorithm; + context->algorithm = aead_algorithm; exporter_algorithm = aead_algorithm; /* With AES-128-GCM-SIV, set the algorithm ID in the RFC5705 key exporter @@ -417,12 +416,12 @@ prepare_response(NKSN_Instance session, int next_protocol, int aead_algorithm, if (exporter_algorithm == AEAD_AES_128_GCM_SIV && !compliant_128gcm) exporter_algorithm = AEAD_AES_SIV_CMAC_256; - if (!NKSN_GetKeys(session, aead_algorithm, exporter_algorithm, - NKE_NEXT_PROTOCOL_NTPV4, &context.c2s, &context.s2c)) + if (!have_keys && !NKSN_GetKeys(session, aead_algorithm, exporter_algorithm, + NKE_NEXT_PROTOCOL_NTPV4, &context->c2s, &context->s2c)) return 0; for (i = 0; i < NKE_MAX_COOKIES; i++) { - if (!NKS_GenerateCookie(&context, &cookie)) + if (!NKS_GenerateCookie(context, &cookie)) return 0; if (!NKSN_AddRecord(session, 0, NKE_RECORD_COOKIE, cookie.cookie, cookie.length)) return 0; @@ -444,7 +443,8 @@ process_request(NKSN_Instance session) int next_protocol_values = 0, aead_algorithm_values = 0; int next_protocol = -1, aead_algorithm = -1, error = -1; int i, j, critical, type, length; - int compliant_128gcm = 0; + int compliant_128gcm = 0, have_fixed_key_record = 0; + NKE_Context context; uint16_t data[NKE_MAX_RECORD_BODY_LENGTH / sizeof (uint16_t)]; assert(NKE_MAX_RECORD_BODY_LENGTH % sizeof (uint16_t) == 0); @@ -455,6 +455,20 @@ process_request(NKSN_Instance session) break; switch (type) { + case NKE_RECORD_FIXED_KEY: + if (!critical || length % 2 != 0 || length < 2 || length > 2 * NKE_MAX_KEY_LENGTH || + have_fixed_key_record) { + error = NKE_ERROR_BAD_REQUEST; + break; + } + + have_fixed_key_record = 1; + + memcpy(context.c2s.key, data, length / 2); + context.c2s.length = length / 2; + memcpy(context.s2c.key, ((uint8_t*) data) + length / 2, length / 2); + context.s2c.length = length / 2; + break; case NKE_RECORD_NEXT_PROTOCOL: if (!critical || length < 2 || length % 2 != 0 || have_next_protocol_record) { error = NKE_ERROR_BAD_REQUEST; @@ -510,13 +524,21 @@ process_request(NKSN_Instance session) (next_protocol == NKE_NEXT_PROTOCOL_NTPV4 && (!have_aead_algorithm_record || aead_algorithm_values < 1))) error = NKE_ERROR_BAD_REQUEST; + + if (have_fixed_key_record) { + if (SIV_GetKeyLength(aead_algorithm) != context.c2s.length || + SIV_GetKeyLength(aead_algorithm) != context.s2c.length || + aead_algorithm_values != 1 || next_protocol_values != 1) + error = NKE_ERROR_BAD_REQUEST; + } } if (error >= 0) { if (!prepare_error_response(session, error)) return 0; } else { - if (!prepare_response(session, next_protocol, aead_algorithm, compliant_128gcm)) + if (!prepare_response(session, next_protocol, aead_algorithm, compliant_128gcm, + have_fixed_key_record, &context)) return 0; } diff --git a/test/unit/nts_ke_server.c b/test/unit/nts_ke_server.c index 5637f3e4..689f3909 100644 --- a/test/unit/nts_ke_server.c +++ b/test/unit/nts_ke_server.c @@ -117,6 +117,105 @@ prepare_request(NKSN_Instance session, int valid) TEST_CHECK(NKSN_EndMessage(session)); } +static void +prepare_fixedkey_request(NKSN_Instance session, int valid) +{ + uint16_t data[32]; + int index, length, keytype; + + if (valid) + index = -1; + else + index = random() % 13; + DEBUG_LOG("index=%d", index); + + NKSN_BeginMessage(session); + + memset(data, 0, sizeof (data)); + length = 2; + assert(sizeof (data[0]) == 2); + + keytype = random() % 2 && SIV_GetKeyLength(AEAD_AES_128_GCM_SIV) > 0 ? + AEAD_AES_128_GCM_SIV : AEAD_AES_SIV_CMAC_256; + + if ((keytype == AEAD_AES_128_GCM_SIV) != (index == 0)) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_FIXED_KEY, data, + 2 * SIV_GetKeyLength(AEAD_AES_128_GCM_SIV))); + else + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_FIXED_KEY, data, + 2 * SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256))); + + if (index == 1) { + if (keytype == AEAD_AES_128_GCM_SIV) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_FIXED_KEY, data, + 2 * SIV_GetKeyLength(AEAD_AES_128_GCM_SIV))); + else + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_FIXED_KEY, data, + 2 * SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256))); + } + + if (index != 2) { + memset(data, NKE_NEXT_PROTOCOL_NTPV4 + 1, sizeof (data)); + data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4); + if (index == 3) + length = 0; + else if (index == 4) + length = 3 + random() % 15 * 2; + else if (index == 5) + length = 4 + random() % 15 * 2; + else + length = 2; + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, length)); + } + + if (index == 6) { + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, length)); + } + + if (index != 7) { + memset(data, keytype + 1, sizeof(data)); + data[0] = htons(keytype); + if (index == 8) + length = 0; + else if (index == 9) + length = 3 + random() % 15 * 2; + else if (index == 10) + length = 4 + random() % 15 * 2; + else + length = 2; + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length)); + } + + if (index == 11) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length)); + + if (index == 12) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 1, 2000 + random() % 1000, data, length)); + } + + if (random() % 2) { + const char server[] = "127.0.0.1"; + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_NTPV4_SERVER_NEGOTIATION, + server, sizeof (server) - 1)); + } + + if (random() % 2) { + data[0] = htons(123); + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_NTPV4_PORT_NEGOTIATION, data, length)); + } + + if (random() % 2) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_COMPLIANT_128GCM_EXPORT, NULL, 0)); + + if (random() % 2) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, 2000 + random() % 1000, data, length)); + } + + TEST_CHECK(NKSN_EndMessage(session)); +} + static void process_response(NKSN_Instance session, int valid) { @@ -175,6 +274,12 @@ test_unit(void) process_response(session, valid); } + for (i = 0; i < 10000; i++) { + valid = random() % 2; + prepare_fixedkey_request(session, valid); + TEST_CHECK(process_request(session)); + process_response(session, valid); + } for (i = 0; i < 10000; i++) { context.algorithm = AEAD_AES_SIV_CMAC_256; From 2a282a54caafb0c65c41ba8f70c8a6e520aed88e Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 27 Jun 2025 16:05:19 +0200 Subject: [PATCH 04/20] nts: Add request protocol/algorithm information support. In order to handle NTS key exchanges on behalf of the server, pools in the draft-venhoek-nts-pool framework need to know which protocols and algorithms a time source supports. --- nts_ke.h | 2 + nts_ke_server.c | 95 +++++++++++++++++++++++++++++++---- test/unit/nts_ke_server.c | 102 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 186 insertions(+), 13 deletions(-) diff --git a/nts_ke.h b/nts_ke.h index cb12b20c..6e5e8f31 100644 --- a/nts_ke.h +++ b/nts_ke.h @@ -41,6 +41,8 @@ #define NKE_RECORD_NTPV4_SERVER_NEGOTIATION 6 #define NKE_RECORD_NTPV4_PORT_NEGOTIATION 7 #define NKE_RECORD_COMPLIANT_128GCM_EXPORT 1024 +#define NKE_RECORD_SUPPORTED_ALGORITHMS 0x4001 +#define NKE_RECORD_SUPPORTED_PROTOCOLS 0x4004 #define NKE_RECORD_FIXED_KEY 0x4002 #define NKE_NEXT_PROTOCOL_NTPV4 0 diff --git a/nts_ke_server.c b/nts_ke_server.c index 87fd6d90..4158c790 100644 --- a/nts_ke_server.c +++ b/nts_ke_server.c @@ -357,6 +357,54 @@ prepare_error_response(NKSN_Instance session, int error) /* ================================================== */ +static int +prepare_supports_response(NKSN_Instance session, int want_supported_protocols, + int want_supported_algorithms) +{ + DEBUG_LOG("NTS KE supports response: want_supported_protocols=%d, want_supported_algorithms=%d", + want_supported_protocols, want_supported_algorithms); + + NKSN_BeginMessage(session); + + if (want_supported_protocols) { + uint16_t supported_protocol = NKE_NEXT_PROTOCOL_NTPV4; + if (!NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, &supported_protocol, + sizeof(supported_protocol))) + return 0; + } + + if (want_supported_algorithms) { + /* Generate descriptions for the enabled Aeads that are actually supported */ + ARR_Instance supported_algorithms = ARR_CreateInstance(sizeof(uint16_t[2])); + for (int i=0; i= 0) { if (!prepare_error_response(session, error)) return 0; + } else if (is_support_request) { + if (!prepare_supports_response(session, have_supported_algorithm_record, + have_supported_protocol_record)) + return 0; } else { if (!prepare_response(session, next_protocol, aead_algorithm, compliant_128gcm, have_fixed_key_record, &context)) diff --git a/test/unit/nts_ke_server.c b/test/unit/nts_ke_server.c index 689f3909..7574e7ce 100644 --- a/test/unit/nts_ke_server.c +++ b/test/unit/nts_ke_server.c @@ -51,7 +51,7 @@ prepare_request(NKSN_Instance session, int valid) if (valid) index = -1; else - index = random() % 9; + index = random() % 11; DEBUG_LOG("index=%d", index); NKSN_BeginMessage(session); @@ -90,7 +90,13 @@ prepare_request(NKSN_Instance session, int valid) if (index == 7) TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length)); - if (index == 8) { + if (index == 8) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); + + if (index == 9) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_ALGORITHMS, NULL, 0)); + + if (index == 10) { length = random() % (sizeof (data) + 1); TEST_CHECK(NKSN_AddRecord(session, 1, 2000 + random() % 1000, data, length)); } @@ -126,7 +132,7 @@ prepare_fixedkey_request(NKSN_Instance session, int valid) if (valid) index = -1; else - index = random() % 13; + index = random() % 15; DEBUG_LOG("index=%d", index); NKSN_BeginMessage(session); @@ -189,7 +195,13 @@ prepare_fixedkey_request(NKSN_Instance session, int valid) if (index == 11) TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length)); - if (index == 12) { + if (index == 12) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); + + if (index == 13) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); + + if (index == 14) { length = random() % (sizeof (data) + 1); TEST_CHECK(NKSN_AddRecord(session, 1, 2000 + random() % 1000, data, length)); } @@ -216,6 +228,61 @@ prepare_fixedkey_request(NKSN_Instance session, int valid) TEST_CHECK(NKSN_EndMessage(session)); } +static void +prepare_support_request(NKSN_Instance session, int valid) +{ + uint16_t data[16]; + int index, length; + + if (valid) + index = -1; + else + index = random() % 5; + DEBUG_LOG("index=%d", index); + + NKSN_BeginMessage(session); + + memset(data, 0, sizeof (data)); + length = 2; + assert(sizeof (data[0]) == 2); + + if ((random() % 3) || (index == 0)) { + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); + if ((random() % 2) || (index == 1)) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_ALGORITHMS, NULL, 0)); + } else { + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_ALGORITHMS, NULL, 0)); + } + + if (index == 0) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); + + if (index == 1) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_ALGORITHMS, NULL, 0)); + + if (index == 2) { + data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4); + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, 2)); + } + + if (index == 3) { + data[0] = htons(AEAD_AES_SIV_CMAC_256); + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, 2)); + } + + if (index == 4) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 1, 2000 + random() % 1000, data, length)); + } + + if (random() % 2) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, 2000 + random() % 1000, data, length)); + } + + TEST_CHECK(NKSN_EndMessage(session)); +} + static void process_response(NKSN_Instance session, int valid) { @@ -236,6 +303,26 @@ process_response(NKSN_Instance session, int valid) } } +static void +process_support_response(NKSN_Instance session, int valid) +{ + int records, errors, critical, type, length; + + for (records = errors = 0; ; records++) { + if (!NKSN_GetRecord(session, &critical, &type, &length, NULL, 0)) + break; + if (type == NKE_RECORD_ERROR) + errors++; + } + + if (valid) { + TEST_CHECK(records >= 1); + } else { + TEST_CHECK(records == 1); + TEST_CHECK(errors == 1); + } +} + void test_unit(void) { @@ -281,6 +368,13 @@ test_unit(void) process_response(session, valid); } + for (i = 0; i < 10000; i++) { + valid = random() % 2; + prepare_support_request(session, valid); + TEST_CHECK(process_request(session)); + process_support_response(session, valid); + } + for (i = 0; i < 10000; i++) { context.algorithm = AEAD_AES_SIV_CMAC_256; get_keys(session, context.algorithm, random() % 100, NKE_NEXT_PROTOCOL_NTPV4, From 0592493ac347126e1113094302ab9f839ff2067b Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 7 Nov 2025 12:04:44 +0100 Subject: [PATCH 05/20] Updated readme to document this is intended for the pool. --- README | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README b/README index 4154cce9..fc77ac73 100644 --- a/README +++ b/README @@ -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 no configuration +changes once the server is setup to provide NTS. Note that this means that all +clients can use fixed key requests. + +The original README for chrony is included below for reference. + +=============================================================================== + This is the README for chrony. What is chrony? From 7b45f24b95ca52a9647bdefe834271fff5accf4b Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Wed, 19 Nov 2025 11:42:13 +0100 Subject: [PATCH 06/20] nts: Add support for long-lived sessions. Pools can do multiple requests in very quick succession. Allowing them to keep the connection alive therefore can offer quite significant performance gains. This commit adds support for a keep-alive record that a pool can use to indicate it wants to keep the connection alive long term. It honors that request if the pool is properly authenticated using an authentication token. --- README | 5 ++ conf.c | 14 +++++ conf.h | 1 + doc/chrony.conf.adoc | 10 ++++ nts_ke.h | 2 + nts_ke_server.c | 105 ++++++++++++++++++++++++++++++++++++-- nts_ke_session.c | 15 +++++- nts_ke_session.h | 4 ++ test/unit/authtokens.txt | 1 + test/unit/nts_ke_server.c | 34 ++++++++++++ 10 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 test/unit/authtokens.txt diff --git a/README b/README index fc77ac73..456ad3af 100644 --- a/README +++ b/README @@ -6,6 +6,11 @@ For inclusion in a pool, this version of chrony requires no configuration changes once the server is setup to provide NTS. Note that this means that all clients can use fixed key requests. +To enable keeping long lived connections to the pool, which can help with +throughput, the pools authentication token must be added to the authentication +token file. See the chrony.conf documentation for the ntsauthtokenfile for +details on how to do this. + The original README for chrony is included below for reference. =============================================================================== diff --git a/conf.c b/conf.c index 1f362d7f..878ccfba 100644 --- a/conf.c +++ b/conf.c @@ -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; @@ -515,6 +516,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); @@ -662,6 +664,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")) { @@ -2000,6 +2004,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)); } @@ -2215,6 +2221,14 @@ CNF_GetKeysFile(void) /* ================================================== */ +char * +CNF_GetNtsAuthTokenFile(void) +{ + return nts_auth_token_file; +} + +/* ================================================== */ + double CNF_GetRtcAutotrim(void) { diff --git a/conf.h b/conf.h index 00a11702..cb5fbd95 100644 --- a/conf.h +++ b/conf.h @@ -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); diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc index fee500e6..966ca3a2 100644 --- a/doc/chrony.conf.adoc +++ b/doc/chrony.conf.adoc @@ -1860,6 +1860,16 @@ 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 keep its connections alive. +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 diff --git a/nts_ke.h b/nts_ke.h index 6e5e8f31..99c99c71 100644 --- a/nts_ke.h +++ b/nts_ke.h @@ -41,9 +41,11 @@ #define NKE_RECORD_NTPV4_SERVER_NEGOTIATION 6 #define NKE_RECORD_NTPV4_PORT_NEGOTIATION 7 #define NKE_RECORD_COMPLIANT_128GCM_EXPORT 1024 +#define NKE_RECORD_KEEP_ALIVE 0x4000 #define NKE_RECORD_SUPPORTED_ALGORITHMS 0x4001 #define NKE_RECORD_SUPPORTED_PROTOCOLS 0x4004 #define NKE_RECORD_FIXED_KEY 0x4002 +#define NKE_RECORD_AUTH_TOKEN 0x4005 #define NKE_NEXT_PROTOCOL_NTPV4 0 diff --git a/nts_ke_server.c b/nts_ke_server.c index 4158c790..3af8c9e1 100644 --- a/nts_ke_server.c +++ b/nts_ke_server.c @@ -80,6 +80,11 @@ typedef struct { uint16_t _pad; } HelperRequest; +typedef struct { + uint32_t len; + char *token; +} AuthToken; + /* ================================================== */ static ServerKey server_keys[MAX_SERVER_KEYS]; @@ -93,6 +98,8 @@ static int server_sock_fd6; static int helper_sock_fd; static int is_helper; +static ARR_Instance auth_tokens; + static int initialised = 0; /* Array of NKSN instances */ @@ -357,9 +364,20 @@ prepare_error_response(NKSN_Instance session, int error) /* ================================================== */ +static int +prepare_response_keepalive(NKSN_Instance session) +{ + DEBUG_LOG("Keeping session alive"); + if (!NKSN_AddRecord(session, 0, NKE_RECORD_KEEP_ALIVE, NULL, 0)) + return 0; + NKSN_KeepAlive(session); + + return 1; +} + static int prepare_supports_response(NKSN_Instance session, int want_supported_protocols, - int want_supported_algorithms) + int want_supported_algorithms, int keep_alive) { DEBUG_LOG("NTS KE supports response: want_supported_protocols=%d, want_supported_algorithms=%d", want_supported_protocols, want_supported_algorithms); @@ -397,6 +415,11 @@ prepare_supports_response(NKSN_Instance session, int want_supported_protocols, ARR_DestroyInstance(supported_algorithms); } + if (keep_alive) { + if (!prepare_response_keepalive(session)) + return 0; + } + if (!NKSN_EndMessage(session)) return 0; @@ -407,7 +430,7 @@ prepare_supports_response(NKSN_Instance session, int want_supported_protocols, static int prepare_response(NKSN_Instance session, int next_protocol, int aead_algorithm, - int compliant_128gcm, int have_keys, NKE_Context *context) + int compliant_128gcm, int have_keys, NKE_Context *context, int keep_alive) { SIV_Algorithm exporter_algorithm; NKE_Cookie cookie; @@ -476,6 +499,11 @@ prepare_response(NKSN_Instance session, int next_protocol, int aead_algorithm, } } + if (have_keys && keep_alive) { + if (!prepare_response_keepalive(session)) + return 0; + } + if (!NKSN_EndMessage(session)) return 0; @@ -493,7 +521,9 @@ process_request(NKSN_Instance session) int next_protocol = -1, aead_algorithm = -1, error = -1; int i, j, critical, type, length; int compliant_128gcm = 0, have_fixed_key_record = 0; + int keep_alive = 0, is_authenticated = 0; int is_support_request; + AuthToken *token; NKE_Context context; uint16_t data[NKE_MAX_RECORD_BODY_LENGTH / sizeof (uint16_t)]; @@ -574,6 +604,19 @@ process_request(NKSN_Instance session) } compliant_128gcm = 1; break; + case NKE_RECORD_KEEP_ALIVE: + keep_alive = 1; + break; + case NKE_RECORD_AUTH_TOKEN: + for (i = 0; i < ARR_GetSize(auth_tokens); i++) { + token = (AuthToken *)ARR_GetElement(auth_tokens, i); + // Accept that this leaks the token length. Its length is not really secret anyway + if (length == token->len && UTI_IsMemoryEqual(data, token->token, length)) { + is_authenticated = 1; + break; + } + } + break; case NKE_RECORD_ERROR: case NKE_RECORD_WARNING: case NKE_RECORD_COOKIE: @@ -611,11 +654,11 @@ process_request(NKSN_Instance session) return 0; } else if (is_support_request) { if (!prepare_supports_response(session, have_supported_algorithm_record, - have_supported_protocol_record)) + have_supported_protocol_record, keep_alive && is_authenticated)) return 0; } else { if (!prepare_response(session, next_protocol, aead_algorithm, compliant_128gcm, - have_fixed_key_record, &context)) + have_fixed_key_record, &context, keep_alive && is_authenticated)) return 0; } @@ -807,6 +850,51 @@ load_keys(void) return 0; } +static void +load_auth_tokens(void) +{ + char *nts_auth_tokens_file, line[1024], *key; + AuthToken *token; + int n_words; + FILE *f; + + nts_auth_tokens_file = CNF_GetNtsAuthTokenFile(); + if (!nts_auth_tokens_file) + return; + + if (!UTI_CheckFilePermissions(nts_auth_tokens_file, 0771)) + ; + + f = UTI_OpenFile(NULL, nts_auth_tokens_file, NULL, 'r', 0); + if (!f) { + LOG(LOGS_WARN, "Could not open ntsauthtokenfile %s", nts_auth_tokens_file); + return; + } + + while (fgets(line, sizeof(line), f)) { + if (strlen(line) == sizeof(line) && line[sizeof(line) - 2] != '\n') { + LOG(LOGS_ERR, "Authentication token line too long"); + goto error; + } + + n_words = UTI_SplitString(line, &key, 1); + if (n_words > 1) { + LOG(LOGS_ERR, "Authentication token line contained more than one key"); + goto error; + } + + if (n_words == 1) { + token = ARR_GetNewElement(auth_tokens); + token->len = strlen(key); + token->token = strdup(key); + } + } + +error: + LOG(LOGS_ERR, "Could not load all authentication tokens"); + fclose(f); +} + /* ================================================== */ static void @@ -953,6 +1041,9 @@ NKS_Initialise(void) for (i = 0; i < CNF_GetNtsServerConnections(); i++) *(NKSN_Instance *)ARR_GetNewElement(sessions) = NULL; + auth_tokens = ARR_CreateInstance(sizeof (AuthToken)); + load_auth_tokens(); + /* Generate random keys, even if they will be replaced by reloaded keys, or unused (in the helper) */ for (i = 0; i < MAX_SERVER_KEYS; i++) { @@ -1015,6 +1106,12 @@ NKS_Finalise(void) for (i = 0; i < MAX_SERVER_KEYS; i++) SIV_DestroyInstance(server_keys[i].siv); + for (i = 0; i < ARR_GetSize(auth_tokens); i++) { + AuthToken token = *(AuthToken *)ARR_GetElement(auth_tokens, i); + Free(token.token); + } + ARR_DestroyInstance(auth_tokens); + for (i = 0; i < ARR_GetSize(sessions); i++) { NKSN_Instance session = *(NKSN_Instance *)ARR_GetElement(sessions, i); if (session) diff --git a/nts_ke_session.c b/nts_ke_session.c index 30f6de46..4721038e 100644 --- a/nts_ke_session.c +++ b/nts_ke_session.c @@ -72,6 +72,7 @@ struct NKSN_Instance_Record { void *handler_arg; KeState state; + int keep_alive; int sock_fd; char *label; TLS_Instance tls_session; @@ -358,7 +359,8 @@ handle_event(NKSN_Instance inst, int event) return 0; /* Client will receive a response */ - change_state(inst, inst->server ? KE_SHUTDOWN : KE_RECEIVE); + change_state(inst, (inst->server && !inst->keep_alive) ? KE_SHUTDOWN : KE_RECEIVE); + inst->keep_alive = 0; reset_message(&inst->message); inst->new_message = 0; return 0; @@ -585,6 +587,7 @@ NKSN_CreateInstance(int server_mode, const char *server_name, inst->handler_arg = inst; inst->state = KE_STOPPED; + inst->keep_alive = 0; inst->sock_fd = INVALID_SOCK_FD; inst->label = NULL; inst->tls_session = NULL; @@ -624,6 +627,7 @@ NKSN_StartSession(NKSN_Instance inst, int sock_fd, const char *label, inst->label = Strdup(label); inst->timeout_id = SCH_AddTimeoutByDelay(timeout, session_timeout, inst); + inst->keep_alive = 0; inst->retry_factor = NKE_RETRY_FACTOR2_CONNECT; reset_message(&inst->message); @@ -634,6 +638,15 @@ NKSN_StartSession(NKSN_Instance inst, int sock_fd, const char *label, return 1; } +/* ================================================== */ +void +NKSN_KeepAlive(NKSN_Instance inst) +{ + inst->keep_alive = 1; + SCH_RemoveTimeout(inst->timeout_id); + inst->timeout_id = 0; +} + /* ================================================== */ void diff --git a/nts_ke_session.h b/nts_ke_session.h index b046144f..15933ec8 100644 --- a/nts_ke_session.h +++ b/nts_ke_session.h @@ -61,6 +61,10 @@ extern void NKSN_DestroyInstance(NKSN_Instance inst); extern int NKSN_StartSession(NKSN_Instance inst, int sock_fd, const char *label, NKSN_Credentials credentials, double timeout); +/* Mark a server session to be kept alive after sending the response. The + mark is reset after sending the next response. */ +extern void NKSN_KeepAlive(NKSN_Instance inst); + /* Begin an NTS-KE message. A request should be made right after starting the session and response should be made in the message handler. */ extern void NKSN_BeginMessage(NKSN_Instance inst); diff --git a/test/unit/authtokens.txt b/test/unit/authtokens.txt new file mode 100644 index 00000000..acbe86c7 --- /dev/null +++ b/test/unit/authtokens.txt @@ -0,0 +1 @@ +abcd diff --git a/test/unit/nts_ke_server.c b/test/unit/nts_ke_server.c index 7574e7ce..e5d088d1 100644 --- a/test/unit/nts_ke_server.c +++ b/test/unit/nts_ke_server.c @@ -60,6 +60,14 @@ prepare_request(NKSN_Instance session, int valid) length = 2; assert(sizeof (data[0]) == 2); + if (random() % 3) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, data, length)); + } + + if (random() % 3) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, "abcd", 4)); + if (index != 0) { memset(data, NKE_NEXT_PROTOCOL_NTPV4 + 1, sizeof (data)); data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4); @@ -120,6 +128,9 @@ prepare_request(NKSN_Instance session, int valid) TEST_CHECK(NKSN_AddRecord(session, 0, 2000 + random() % 1000, data, length)); } + if (random() % 2) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_KEEP_ALIVE, NULL, 0)); + TEST_CHECK(NKSN_EndMessage(session)); } @@ -141,6 +152,14 @@ prepare_fixedkey_request(NKSN_Instance session, int valid) length = 2; assert(sizeof (data[0]) == 2); + if (random() % 3) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, data, length)); + } + + if (random() % 3) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, "abcd", 4)); + keytype = random() % 2 && SIV_GetKeyLength(AEAD_AES_128_GCM_SIV) > 0 ? AEAD_AES_128_GCM_SIV : AEAD_AES_SIV_CMAC_256; @@ -225,6 +244,9 @@ prepare_fixedkey_request(NKSN_Instance session, int valid) TEST_CHECK(NKSN_AddRecord(session, 0, 2000 + random() % 1000, data, length)); } + if (random() % 2) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_KEEP_ALIVE, NULL, 0)); + TEST_CHECK(NKSN_EndMessage(session)); } @@ -246,6 +268,14 @@ prepare_support_request(NKSN_Instance session, int valid) length = 2; assert(sizeof (data[0]) == 2); + if (random() % 3) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, data, length)); + } + + if (random() % 3) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, "abcd", 4)); + if ((random() % 3) || (index == 0)) { TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); if ((random() % 2) || (index == 1)) @@ -280,6 +310,9 @@ prepare_support_request(NKSN_Instance session, int valid) TEST_CHECK(NKSN_AddRecord(session, 0, 2000 + random() % 1000, data, length)); } + if (random() % 2) + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_KEEP_ALIVE, NULL, 0)); + TEST_CHECK(NKSN_EndMessage(session)); } @@ -338,6 +371,7 @@ test_unit(void) "ntsprocesses 0", "ntsserverkey nts_ke.key", "ntsservercert nts_ke.crt", + "ntsauthtokenfile authtokens.txt", }; CNF_Initialise(0, 0); From 5f728fcedd901f9be174091399922f70c6ac21ae Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 21 Nov 2025 12:28:59 +0100 Subject: [PATCH 07/20] nts: Implement limits on the number of long lived connections. This avoids an overenthousiastic pool consuming all resources of the key exchange server. --- conf.c | 11 +++++++++++ conf.h | 1 + doc/chrony.conf.adoc | 7 +++++++ nts_ke_server.c | 39 +++++++++++++++++++++++++++++++++++---- nts_ke_session.c | 36 ++++++++++++++++++++++++++++++++++++ nts_ke_session.h | 11 +++++++++++ 6 files changed, 101 insertions(+), 4 deletions(-) diff --git a/conf.c b/conf.c index 878ccfba..7478818e 100644 --- a/conf.c +++ b/conf.c @@ -282,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 *) */ @@ -702,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")) { @@ -2856,6 +2859,14 @@ CNF_GetNtsServerConnections(void) /* ================================================== */ +int +CNF_GetNtsLongtermConnections(void) +{ + return nts_longterm_connections; +} + +/* ================================================== */ + int CNF_GetNtsRefresh(void) { diff --git a/conf.h b/conf.h index cb5fbd95..f836472f 100644 --- a/conf.h +++ b/conf.h @@ -174,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); diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc index 966ca3a2..fc9dd63d 100644 --- a/doc/chrony.conf.adoc +++ b/doc/chrony.conf.adoc @@ -1883,6 +1883,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 diff --git a/nts_ke_server.c b/nts_ke_server.c index 3af8c9e1..708ddb3d 100644 --- a/nts_ke_server.c +++ b/nts_ke_server.c @@ -102,6 +102,8 @@ static ARR_Instance auth_tokens; static int initialised = 0; +static int active_longterm_connections = 0; + /* Array of NKSN instances */ static ARR_Instance sessions; static NKSN_Credentials server_credentials; @@ -112,6 +114,26 @@ static int handle_message(void *arg); /* ================================================== */ +static int +handle_longterm_stop(void *arg) +{ + NKSN_Instance inst = (NKSN_Instance)arg; + + if (!NKSN_IsLongterm(inst)) + return 0; + + if (active_longterm_connections > 0) { + active_longterm_connections--; + } else { + /* This should never happen, unless something goes haywire*/ + LOG(LOGS_ERR, "Internal error: count of active longterm connections is lower than actual number of longterm connections"); + } + + return 0; +} + +/* ================================================== */ + static int handle_client(int sock_fd, IPSockAddr *addr) { @@ -367,10 +389,19 @@ prepare_error_response(NKSN_Instance session, int error) static int prepare_response_keepalive(NKSN_Instance session) { - DEBUG_LOG("Keeping session alive"); - if (!NKSN_AddRecord(session, 0, NKE_RECORD_KEEP_ALIVE, NULL, 0)) - return 0; - NKSN_KeepAlive(session); + if ((NKSN_IsLongterm(session) || CNF_GetNtsLongtermConnections() > active_longterm_connections)) + { + if (!NKSN_IsLongterm(session)) { + active_longterm_connections++; + NKSN_MarkLongterm(session); + NKSN_SetStopHandler(session, handle_longterm_stop); + } + + DEBUG_LOG("Keeping session alive"); + if (!NKSN_AddRecord(session, 0, NKE_RECORD_KEEP_ALIVE, NULL, 0)) + return 0; + NKSN_KeepAlive(session); + } return 1; } diff --git a/nts_ke_session.c b/nts_ke_session.c index 4721038e..f3a95f8e 100644 --- a/nts_ke_session.c +++ b/nts_ke_session.c @@ -69,10 +69,12 @@ struct NKSN_Instance_Record { int server; char *server_name; NKSN_MessageHandler handler; + NKSN_MessageHandler stop_handler; void *handler_arg; KeState state; int keep_alive; + int is_longterm; int sock_fd; char *label; TLS_Instance tls_session; @@ -212,6 +214,12 @@ stop_session(NKSN_Instance inst) inst->state = KE_STOPPED; + if (inst->stop_handler) + inst->stop_handler(inst->handler_arg); + + inst->stop_handler = NULL; + inst->is_longterm = 0; + SCH_RemoveFileHandler(inst->sock_fd); SCK_CloseSocket(inst->sock_fd); inst->sock_fd = INVALID_SOCK_FD; @@ -581,6 +589,7 @@ NKSN_CreateInstance(int server_mode, const char *server_name, inst->server = server_mode; inst->server_name = server_name ? Strdup(server_name) : NULL; inst->handler = handler; + inst->stop_handler = NULL; inst->handler_arg = handler_arg; /* Replace a NULL argument with the session itself */ if (!inst->handler_arg) @@ -588,6 +597,7 @@ NKSN_CreateInstance(int server_mode, const char *server_name, inst->state = KE_STOPPED; inst->keep_alive = 0; + inst->is_longterm = 0; inst->sock_fd = INVALID_SOCK_FD; inst->label = NULL; inst->tls_session = NULL; @@ -616,6 +626,9 @@ NKSN_StartSession(NKSN_Instance inst, int sock_fd, const char *label, { assert(inst->state == KE_STOPPED); + inst->stop_handler = NULL; + inst->is_longterm = 0; + inst->tls_session = TLS_CreateInstance(inst->server, sock_fd, inst->server_name, label, NKE_ALPN_NAME, credentials, clock_updates < CNF_GetNoCertTimeCheck()); @@ -649,6 +662,21 @@ NKSN_KeepAlive(NKSN_Instance inst) /* ================================================== */ +void +NKSN_MarkLongterm(NKSN_Instance inst) +{ + inst->is_longterm = 1; +} + +/* ================================================== */ +void +NKSN_SetStopHandler(NKSN_Instance inst, NKSN_MessageHandler handler) +{ + inst->stop_handler = handler; +} + +/* ================================================== */ + void NKSN_BeginMessage(NKSN_Instance inst) { @@ -765,6 +793,14 @@ NKSN_IsStopped(NKSN_Instance inst) /* ================================================== */ +int +NKSN_IsLongterm(NKSN_Instance inst) +{ + return inst->is_longterm; +} + +/* ================================================== */ + void NKSN_StopSession(NKSN_Instance inst) { diff --git a/nts_ke_session.h b/nts_ke_session.h index 15933ec8..61406165 100644 --- a/nts_ke_session.h +++ b/nts_ke_session.h @@ -65,6 +65,14 @@ extern int NKSN_StartSession(NKSN_Instance inst, int sock_fd, const char *label, mark is reset after sending the next response. */ extern void NKSN_KeepAlive(NKSN_Instance inst); +/* Mark the session as longterm. This does not alter behaviour in this module + but can be used to check the status of a connection in the server. The mark + gets reset on starting of the session. */ +extern void NKSN_MarkLongterm(NKSN_Instance inst); + +/* Add a handler function to be run when the session stops */ +extern void NKSN_SetStopHandler(NKSN_Instance inst, NKSN_MessageHandler handler); + /* Begin an NTS-KE message. A request should be made right after starting the session and response should be made in the message handler. */ extern void NKSN_BeginMessage(NKSN_Instance inst); @@ -90,6 +98,9 @@ extern int NKSN_GetKeys(NKSN_Instance inst, SIV_Algorithm algorithm, /* Check if the session has stopped */ extern int NKSN_IsStopped(NKSN_Instance inst); +/* Check if the session has been marked longterm */ +extern int NKSN_IsLongterm(NKSN_Instance inst); + /* Stop the session */ extern void NKSN_StopSession(NKSN_Instance inst); From 46bc7e0797f4f8e5a3929ed58e9ad79dfc8c519c Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Thu, 4 Dec 2025 09:44:03 +0100 Subject: [PATCH 08/20] nts: Only allow pool extensions when authenticated. This makes the key exchange server only cooperate with pools the user has explicitly configured, making misuse of the server harder. --- README | 11 +++------ doc/chrony.conf.adoc | 7 +++--- nts_ke_server.c | 13 +++++----- test/unit/nts_ke_server.c | 52 +++++++++++++++++++-------------------- 4 files changed, 39 insertions(+), 44 deletions(-) diff --git a/README b/README index 456ad3af..6f559385 100644 --- a/README +++ b/README @@ -2,14 +2,9 @@ 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 no configuration -changes once the server is setup to provide NTS. Note that this means that all -clients can use fixed key requests. - -To enable keeping long lived connections to the pool, which can help with -throughput, the pools authentication token must be added to the authentication -token file. See the chrony.conf documentation for the ntsauthtokenfile for -details on how to do this. +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. diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc index fc9dd63d..cc8e59de 100644 --- a/doc/chrony.conf.adoc +++ b/doc/chrony.conf.adoc @@ -1862,10 +1862,9 @@ 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 keep its connections alive. -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. +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. diff --git a/nts_ke_server.c b/nts_ke_server.c index 708ddb3d..402298e1 100644 --- a/nts_ke_server.c +++ b/nts_ke_server.c @@ -567,8 +567,8 @@ process_request(NKSN_Instance session) switch (type) { case NKE_RECORD_FIXED_KEY: - if (!critical || length % 2 != 0 || length < 2 || length > 2 * NKE_MAX_KEY_LENGTH || - have_fixed_key_record) { + if (!critical || !is_authenticated || length % 2 != 0 || length < 2 || + length > 2 * NKE_MAX_KEY_LENGTH || have_fixed_key_record) { error = NKE_ERROR_BAD_REQUEST; break; } @@ -613,7 +613,7 @@ process_request(NKSN_Instance session) } break; case NKE_RECORD_SUPPORTED_PROTOCOLS: - if (length != 0 || have_supported_protocol_record) { + if (length != 0 || !is_authenticated || have_supported_protocol_record) { error = NKE_ERROR_BAD_REQUEST; break; } @@ -621,7 +621,7 @@ process_request(NKSN_Instance session) have_supported_protocol_record = 1; break; case NKE_RECORD_SUPPORTED_ALGORITHMS: - if (length != 0 || have_supported_algorithm_record) { + if (length != 0 || !is_authenticated || have_supported_algorithm_record) { error = NKE_ERROR_BAD_REQUEST; break; } @@ -902,8 +902,9 @@ load_auth_tokens(void) return; } - while (fgets(line, sizeof(line), f)) { - if (strlen(line) == sizeof(line) && line[sizeof(line) - 2] != '\n') { + line[sizeof (line) - 2] = '\0'; + while (fgets(line, sizeof (line), f)) { + if (line[sizeof (line) - 2] != '\n' && line[sizeof (line) - 2] != '\0') { LOG(LOGS_ERR, "Authentication token line too long"); goto error; } diff --git a/test/unit/nts_ke_server.c b/test/unit/nts_ke_server.c index e5d088d1..d7d82d9c 100644 --- a/test/unit/nts_ke_server.c +++ b/test/unit/nts_ke_server.c @@ -143,7 +143,7 @@ prepare_fixedkey_request(NKSN_Instance session, int valid) if (valid) index = -1; else - index = random() % 15; + index = random() % 16; DEBUG_LOG("index=%d", index); NKSN_BeginMessage(session); @@ -157,20 +157,20 @@ prepare_fixedkey_request(NKSN_Instance session, int valid) TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, data, length)); } - if (random() % 3) + if (index != 0) TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, "abcd", 4)); keytype = random() % 2 && SIV_GetKeyLength(AEAD_AES_128_GCM_SIV) > 0 ? AEAD_AES_128_GCM_SIV : AEAD_AES_SIV_CMAC_256; - if ((keytype == AEAD_AES_128_GCM_SIV) != (index == 0)) + if ((keytype == AEAD_AES_128_GCM_SIV) != (index == 1)) TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_FIXED_KEY, data, 2 * SIV_GetKeyLength(AEAD_AES_128_GCM_SIV))); else TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_FIXED_KEY, data, 2 * SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256))); - if (index == 1) { + if (index == 2) { if (keytype == AEAD_AES_128_GCM_SIV) TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_FIXED_KEY, data, 2 * SIV_GetKeyLength(AEAD_AES_128_GCM_SIV))); @@ -179,48 +179,48 @@ prepare_fixedkey_request(NKSN_Instance session, int valid) 2 * SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256))); } - if (index != 2) { + if (index != 3) { memset(data, NKE_NEXT_PROTOCOL_NTPV4 + 1, sizeof (data)); data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4); - if (index == 3) + if (index == 4) length = 0; - else if (index == 4) - length = 3 + random() % 15 * 2; else if (index == 5) + length = 3 + random() % 15 * 2; + else if (index == 6) length = 4 + random() % 15 * 2; else length = 2; TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, length)); } - if (index == 6) { + if (index == 7) { TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, length)); } - if (index != 7) { + if (index != 8) { memset(data, keytype + 1, sizeof(data)); data[0] = htons(keytype); - if (index == 8) + if (index == 9) length = 0; - else if (index == 9) - length = 3 + random() % 15 * 2; else if (index == 10) + length = 3 + random() % 15 * 2; + else if (index == 11) length = 4 + random() % 15 * 2; else length = 2; TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length)); } - if (index == 11) + if (index == 12) TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length)); - if (index == 12) + if (index == 13) TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); - if (index == 13) + if (index == 14) TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); - if (index == 14) { + if (index == 15) { length = random() % (sizeof (data) + 1); TEST_CHECK(NKSN_AddRecord(session, 1, 2000 + random() % 1000, data, length)); } @@ -259,7 +259,7 @@ prepare_support_request(NKSN_Instance session, int valid) if (valid) index = -1; else - index = random() % 5; + index = random() % 6; DEBUG_LOG("index=%d", index); NKSN_BeginMessage(session); @@ -273,34 +273,34 @@ prepare_support_request(NKSN_Instance session, int valid) TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, data, length)); } - if (random() % 3) + if (index != 0) TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_AUTH_TOKEN, "abcd", 4)); - if ((random() % 3) || (index == 0)) { + if ((random() % 3) || (index == 1)) { TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); - if ((random() % 2) || (index == 1)) + if ((random() % 2) || (index == 2)) TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_ALGORITHMS, NULL, 0)); } else { TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_ALGORITHMS, NULL, 0)); } - if (index == 0) + if (index == 1) TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_PROTOCOLS, NULL, 0)); - if (index == 1) + if (index == 2) TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_SUPPORTED_ALGORITHMS, NULL, 0)); - if (index == 2) { + if (index == 3) { data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4); TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, 2)); } - if (index == 3) { + if (index == 4) { data[0] = htons(AEAD_AES_SIV_CMAC_256); TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, 2)); } - if (index == 4) { + if (index == 5) { length = random() % (sizeof (data) + 1); TEST_CHECK(NKSN_AddRecord(session, 1, 2000 + random() % 1000, data, length)); } From 92097cb6ce9f7895f09967c4404e8e0304660d86 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Mon, 12 Jan 2026 11:36:29 +0100 Subject: [PATCH 09/20] nts: Fix pool authentication tokens always reporting an error. --- nts_ke_server.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nts_ke_server.c b/nts_ke_server.c index 402298e1..eec69a26 100644 --- a/nts_ke_server.c +++ b/nts_ke_server.c @@ -922,6 +922,9 @@ load_auth_tokens(void) } } + fclose(f); + return; + error: LOG(LOGS_ERR, "Could not load all authentication tokens"); fclose(f); From 72aafef2a9af903d94dce6ab69193ab073bfd09b Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Wed, 7 Jan 2026 10:08:10 +0100 Subject: [PATCH 10/20] dns: Introduce separate type for a DNS name lookup result. This type allows carrying of an additional name that is needed when using the results from an SRV lookup. --- client.c | 34 ++++++++++++++++++---------------- cmdparse.c | 4 +++- conf.c | 6 +++--- nameserv.c | 23 ++++++++++++----------- nameserv.h | 10 ++++++++-- nameserv_async.c | 4 ++-- nameserv_async.h | 2 +- ntp_sources.c | 10 +++++----- nts_ke_client.c | 10 +++++----- privops.c | 4 ++-- privops.h | 4 +++- test/unit/ntp_sources.c | 12 ++++++++---- 12 files changed, 70 insertions(+), 53 deletions(-) diff --git a/client.c b/client.c index 66f23b70..bb1312b4 100644 --- a/client.c +++ b/client.c @@ -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)); @@ -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) != 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)); } } } @@ -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) == DNS_Success) { + *address = lookup.ip; return 1; + } return 0; } @@ -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) == DNS_Success) { + UTI_IPHostToNetwork(&lookup.ip, &msg->data.ac_check.ip); return 1; } else { LOG(LOGS_ERR, "Could not read address"); @@ -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) == DNS_Success) { + UTI_IPHostToNetwork(&lookup.ip, &msg->data.ac_check.ip); return 1; } else { LOG(LOGS_ERR, "Could not read address"); @@ -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; @@ -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) != DNS_Success) { LOG(LOGS_ERR, "Invalid host/IP address"); break; } @@ -3730,5 +3734,3 @@ main(int argc, char **argv) return !ret; } - - diff --git a/cmdparse.c b/cmdparse.c index 29c65b31..0e24b7b9 100644 --- a/cmdparse.c +++ b/cmdparse.c @@ -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); @@ -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) == DNS_Success) { + *ip = lookup_result.ip; *subnet_bits = ip->family == IPADDR_INET6 ? 128 : 32; return 1; } diff --git a/conf.c b/conf.c index 7478818e..de0c5a64 100644 --- a/conf.c +++ b/conf.c @@ -1171,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) { @@ -1190,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) == DNS_Success) { + ARR_AppendElement(init_sources, &addr.ip); } else { LOG(LOGS_WARN, "Could not resolve address of initstepslew server %s", hostname); } diff --git a/nameserv.c b/nameserv.c index 9f7e648c..99609964 100644 --- a/nameserv.c +++ b/nameserv.c @@ -48,7 +48,7 @@ DNS_SetAddressFamily(int family) } DNS_Status -DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs) +DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_addrs) { struct addrinfo hints, *res, *ai; int i, result; @@ -56,15 +56,17 @@ DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs) max_addrs = MIN(max_addrs, DNS_MAX_ADDRESSES); - for (i = 0; i < max_addrs; i++) - ip_addrs[i].family = IPADDR_UNSPEC; + for (i = 0; i < max_addrs; i++) { + addrs[i].ip.family = IPADDR_UNSPEC; + addrs[i].service_name[0] = 0; + } /* Avoid calling getaddrinfo() if the name is an IP address */ if (UTI_StringToIP(name, &ip)) { if (address_family != IPADDR_UNSPEC && ip.family != address_family) return DNS_Failure; if (max_addrs >= 1) - ip_addrs[0] = ip; + addrs[0].ip = ip; return DNS_Success; } @@ -99,8 +101,8 @@ DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs) case AF_INET: if (address_family != IPADDR_UNSPEC && address_family != IPADDR_INET4) continue; - ip_addrs[i].family = IPADDR_INET4; - ip_addrs[i].addr.in4 = ntohl(((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr); + addrs[i].ip.family = IPADDR_INET4; + addrs[i].ip.addr.in4 = ntohl(((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr); i++; break; #ifdef FEAT_IPV6 @@ -110,9 +112,9 @@ DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs) /* Don't return an address that would lose a scope ID */ if (((struct sockaddr_in6 *)ai->ai_addr)->sin6_scope_id != 0) continue; - ip_addrs[i].family = IPADDR_INET6; - memcpy(&ip_addrs[i].addr.in6, &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr.s6_addr, - sizeof (ip_addrs->addr.in6)); + addrs[i].ip.family = IPADDR_INET6; + memcpy(&addrs[i].ip.addr.in6, &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr.s6_addr, + sizeof (addrs->ip.addr.in6)); i++; break; #endif @@ -121,7 +123,7 @@ DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs) freeaddrinfo(res); - return !max_addrs || ip_addrs[0].family != IPADDR_UNSPEC ? DNS_Success : DNS_Failure; + return !max_addrs || addrs[0].ip.family != IPADDR_UNSPEC ? DNS_Success : DNS_Failure; } /* ================================================== */ @@ -163,4 +165,3 @@ DNS_Reload(void) } /* ================================================== */ - diff --git a/nameserv.h b/nameserv.h index dbef61a3..acd698d7 100644 --- a/nameserv.h +++ b/nameserv.h @@ -30,23 +30,29 @@ #include "addressing.h" +#define DNS_SERVICE_NAME_LEN 256 + typedef enum { DNS_Success, DNS_TryAgain, DNS_Failure } DNS_Status; +typedef struct { + IPAddr ip; + char service_name[DNS_SERVICE_NAME_LEN]; +} DNS_AddressLookupResult; + /* Resolve names only to selected address family */ extern void DNS_SetAddressFamily(int family); /* Maximum number of addresses returned by DNS_Name2IPAddress */ #define DNS_MAX_ADDRESSES 16 -extern DNS_Status DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs); +extern DNS_Status DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_addrs); extern int DNS_IPAddress2Name(IPAddr *ip_addr, char *name, int len); extern void DNS_Reload(void); #endif /* GOT_NAMESERV_H */ - diff --git a/nameserv_async.c b/nameserv_async.c index 118443c6..f6aa7dd8 100644 --- a/nameserv_async.c +++ b/nameserv_async.c @@ -43,7 +43,7 @@ struct DNS_Async_Instance { const char *name; DNS_Status status; - IPAddr addresses[DNS_MAX_ADDRESSES]; + DNS_AddressLookupResult addresses[DNS_MAX_ADDRESSES]; DNS_NameResolveHandler handler; void *arg; @@ -88,7 +88,7 @@ end_resolving(int fd, int event, void *anything) close(inst->pipe[1]); for (i = 0; inst->status == DNS_Success && i < DNS_MAX_ADDRESSES && - inst->addresses[i].family != IPADDR_UNSPEC; i++) + inst->addresses[i].ip.family != IPADDR_UNSPEC; i++) ; (inst->handler)(inst->status, i, inst->addresses, inst->arg); diff --git a/nameserv_async.h b/nameserv_async.h index b8479e1d..00bd2451 100644 --- a/nameserv_async.h +++ b/nameserv_async.h @@ -31,7 +31,7 @@ #include "nameserv.h" /* Function type for callback to process the result */ -typedef void (*DNS_NameResolveHandler)(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *anything); +typedef void (*DNS_NameResolveHandler)(DNS_Status status, int n_addrs, DNS_AddressLookupResult *addrs, void *anything); /* Request resolving of a name to IP address. The handler will be called when the result is available. */ diff --git a/ntp_sources.c b/ntp_sources.c index 7bf08894..4f197589 100644 --- a/ntp_sources.c +++ b/ntp_sources.c @@ -533,7 +533,7 @@ replace_source_connectable(NTP_Remote_Address *old_addr, NTP_Remote_Address *new /* ================================================== */ static void -process_resolved_name(struct UnresolvedSource *us, IPAddr *ip_addrs, int n_addrs) +process_resolved_name(struct UnresolvedSource *us, DNS_AddressLookupResult *addrs, int n_addrs) { NTP_Remote_Address old_addr, new_addr; SourceRecord *record; @@ -547,7 +547,7 @@ process_resolved_name(struct UnresolvedSource *us, IPAddr *ip_addrs, int n_addrs for (i = 0; i < n_addrs; i++) { if (find_slot2(&us->address, &slot) == 2 && - UTI_CompareIPs(&get_record(slot)->resolved_addr, &ip_addrs[i], NULL) == 0) { + UTI_CompareIPs(&get_record(slot)->resolved_addr, &addrs[i].ip, NULL) == 0) { DEBUG_LOG("%s still fresh", UTI_IPToString(&us->address.ip_addr)); return; } @@ -558,7 +558,7 @@ process_resolved_name(struct UnresolvedSource *us, IPAddr *ip_addrs, int n_addrs UTI_GetRandomBytes(&first, sizeof (first)); for (i = 0; i < n_addrs; i++) { - new_addr.ip_addr = ip_addrs[((unsigned int)i + first) % n_addrs]; + new_addr.ip_addr = addrs[((unsigned int)i + first) % n_addrs].ip; DEBUG_LOG("(%d) %s", i + 1, UTI_IPToString(&new_addr.ip_addr)); @@ -616,7 +616,7 @@ resolve_sources_timeout(void *arg) /* ================================================== */ static void -name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *anything) +name_resolve_handler(DNS_Status status, int n_addrs, DNS_AddressLookupResult *addrs, void *anything) { struct UnresolvedSource *us, *next; @@ -631,7 +631,7 @@ name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *any case DNS_TryAgain: break; case DNS_Success: - process_resolved_name(us, ip_addrs, n_addrs); + process_resolved_name(us, addrs, n_addrs); break; case DNS_Failure: LOG(LOGS_WARN, "Invalid host %s", us->name); diff --git a/nts_ke_client.c b/nts_ke_client.c index e68d4dcf..563d2c56 100644 --- a/nts_ke_client.c +++ b/nts_ke_client.c @@ -67,7 +67,7 @@ static int default_credentials_refs = 0; /* ================================================== */ static void -name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *arg) +name_resolve_handler(DNS_Status status, int n_addrs, DNS_AddressLookupResult *addrs, void *arg) { NKC_Instance inst = arg; int i; @@ -86,13 +86,13 @@ name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *arg return; } - inst->ntp_address.ip_addr = ip_addrs[0]; + inst->ntp_address.ip_addr = addrs[0].ip; /* Prefer an address in the same family as the NTS-KE server */ for (i = 0; i < n_addrs; i++) { - DEBUG_LOG("%s resolved to %s", inst->server_name, UTI_IPToString(&ip_addrs[i])); - if (ip_addrs[i].family == inst->address.ip_addr.family) { - inst->ntp_address.ip_addr = ip_addrs[i]; + DEBUG_LOG("%s resolved to %s", inst->server_name, UTI_IPToString(&addrs[i].ip)); + if (addrs[i].ip.family == inst->address.ip_addr.family) { + inst->ntp_address.ip_addr = addrs[i].ip; break; } } diff --git a/privops.c b/privops.c index 3bc76d14..1c2dec88 100644 --- a/privops.c +++ b/privops.c @@ -106,7 +106,7 @@ typedef struct { #endif typedef struct { - IPAddr addresses[DNS_MAX_ADDRESSES]; + DNS_AddressLookupResult addresses[DNS_MAX_ADDRESSES]; } ResName2IPAddress; typedef struct { @@ -573,7 +573,7 @@ PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len) #ifdef PRIVOPS_NAME2IPADDRESS int -PRV_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs) +PRV_Name2IPAddress(const char *name, DNS_AddressLookupResult *ip_addrs, int max_addrs) { PrvRequest req; PrvResponse res; diff --git a/privops.h b/privops.h index 146580b7..48a35cab 100644 --- a/privops.h +++ b/privops.h @@ -28,6 +28,8 @@ #ifndef GOT_PRIVOPS_H #define GOT_PRIVOPS_H +#include "nameserv.h" + #ifdef PRIVOPS_ADJUSTTIME int PRV_AdjustTime(const struct timeval *delta, struct timeval *olddelta); #else @@ -53,7 +55,7 @@ int PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len); #endif #ifdef PRIVOPS_NAME2IPADDRESS -int PRV_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs); +int PRV_Name2IPAddress(const char *name, DNS_AddressLookupResult *ip_addrs, int max_addrs); #else #define PRV_Name2IPAddress DNS_Name2IPAddress #endif diff --git a/test/unit/ntp_sources.c b/test/unit/ntp_sources.c index be5d2ea8..22890154 100644 --- a/test/unit/ntp_sources.c +++ b/test/unit/ntp_sources.c @@ -53,7 +53,7 @@ static double get_mono_time(void); static void resolve_random_address(DNS_Status status, int rand_bits) { - IPAddr ip_addrs[DNS_MAX_ADDRESSES]; + DNS_AddressLookupResult addrs[DNS_MAX_ADDRESSES]; int i, n_addrs; TEST_CHECK(requested_name); @@ -61,13 +61,17 @@ resolve_random_address(DNS_Status status, int rand_bits) if (status == DNS_Success) { n_addrs = random() % DNS_MAX_ADDRESSES + 1; - for (i = 0; i < n_addrs; i++) - TST_GetRandomAddress(&ip_addrs[i], IPADDR_UNSPEC, rand_bits); + for (i = 0; i < n_addrs; i++) { + TST_GetRandomAddress(&addrs[i].ip, IPADDR_UNSPEC, rand_bits); + addrs[i].service_name[0] = 0; + } } else { n_addrs = 0; } - (resolve_handler)(status, n_addrs, ip_addrs, resolve_handler_arg); + DEBUG_LOG("Random resolving to %d addrs", n_addrs); + + (resolve_handler)(status, n_addrs, addrs, resolve_handler_arg); } static int From 19fd83712722e33ab79219d23f42a5ae98209cc6 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Wed, 7 Jan 2026 11:49:55 +0100 Subject: [PATCH 11/20] nts: Update nts server name from DNS lookup results. When using a server through an SRV record, the name used for the TLS certificate should match the service name, not the dns name where the SRV record was found. Therefore the name used for certificate validation needs to be updated when using the result of a DNS resolve that went through an SRV record. --- nameserv.h | 5 ++++ ntp_auth.c | 2 +- ntp_auth.h | 3 ++- ntp_core.c | 11 ++++---- ntp_core.h | 3 ++- ntp_sources.c | 51 ++++++++++++++++++++++---------------- ntp_sources.h | 2 +- nts_ntp_client.c | 29 ++++++++++++++-------- nts_ntp_client.h | 3 ++- test/unit/ntp_auth.c | 8 ++++-- test/unit/ntp_sources.c | 17 ++++++++----- test/unit/nts_ntp_client.c | 7 +++--- util.c | 17 +++++++++++++ util.h | 2 ++ 14 files changed, 108 insertions(+), 52 deletions(-) diff --git a/nameserv.h b/nameserv.h index acd698d7..69ef9262 100644 --- a/nameserv.h +++ b/nameserv.h @@ -43,6 +43,11 @@ typedef struct { char service_name[DNS_SERVICE_NAME_LEN]; } DNS_AddressLookupResult; +typedef struct { + DNS_AddressLookupResult ip; + uint16_t port; +} DNS_SockAddrLookupResult; + /* Resolve names only to selected address family */ extern void DNS_SetAddressFamily(int family); diff --git a/ntp_auth.c b/ntp_auth.c index 58374c57..014c8363 100644 --- a/ntp_auth.c +++ b/ntp_auth.c @@ -332,7 +332,7 @@ NAU_CheckResponseAuth(NAU_Instance instance, NTP_Packet *response, NTP_PacketInf /* ================================================== */ void -NAU_ChangeAddress(NAU_Instance instance, IPAddr *address) +NAU_ChangeAddress(NAU_Instance instance, DNS_AddressLookupResult *address) { switch (instance->mode) { case NTP_AUTH_NONE: diff --git a/ntp_auth.h b/ntp_auth.h index 0b8a8253..31b5ba6c 100644 --- a/ntp_auth.h +++ b/ntp_auth.h @@ -28,6 +28,7 @@ #define GOT_NTP_AUTH_H #include "addressing.h" +#include "nameserv.h" #include "ntp.h" #include "reports.h" @@ -73,7 +74,7 @@ extern int NAU_CheckResponseAuth(NAU_Instance instance, NTP_Packet *response, NTP_PacketInfo *info); /* Change an authentication-specific address (e.g. after replacing a source) */ -extern void NAU_ChangeAddress(NAU_Instance instance, IPAddr *address); +extern void NAU_ChangeAddress(NAU_Instance instance, DNS_AddressLookupResult *address); /* Save authentication-specific data to speed up the next start */ extern void NAU_DumpData(NAU_Instance instance); diff --git a/ntp_core.c b/ntp_core.c index 7395a638..9213a56e 100644 --- a/ntp_core.c +++ b/ntp_core.c @@ -823,14 +823,15 @@ NCR_ResetPoll(NCR_Instance instance) /* ================================================== */ void -NCR_ChangeRemoteAddress(NCR_Instance inst, NTP_Remote_Address *remote_addr, int ntp_only) +NCR_ChangeRemoteAddress(NCR_Instance inst, DNS_SockAddrLookupResult *remote_addr, int ntp_only) { NCR_ResetInstance(inst); if (!ntp_only) - NAU_ChangeAddress(inst->auth, &remote_addr->ip_addr); + NAU_ChangeAddress(inst->auth, &remote_addr->ip); - inst->remote_addr = *remote_addr; + inst->remote_addr.ip_addr = remote_addr->ip.ip; + inst->remote_addr.port = remote_addr->port; if (inst->mode == MODE_CLIENT) close_client_socket(inst); @@ -838,7 +839,7 @@ NCR_ChangeRemoteAddress(NCR_Instance inst, NTP_Remote_Address *remote_addr, int NIO_CloseServerSocket(inst->local_addr.sock_fd); inst->local_addr.ip_addr.family = IPADDR_UNSPEC; inst->local_addr.if_index = INVALID_IF_INDEX; - inst->local_addr.sock_fd = NIO_OpenServerSocket(remote_addr); + inst->local_addr.sock_fd = NIO_OpenServerSocket(&inst->remote_addr); } /* Reset the polling interval only if the source wasn't unreachable to @@ -848,7 +849,7 @@ NCR_ChangeRemoteAddress(NCR_Instance inst, NTP_Remote_Address *remote_addr, int NCR_ResetPoll(inst); /* Update the reference ID and reset the source/sourcestats instances */ - SRC_SetRefid(inst->source, UTI_IPToRefid(&remote_addr->ip_addr), + SRC_SetRefid(inst->source, UTI_IPToRefid(&remote_addr->ip.ip), &inst->remote_addr.ip_addr); SRC_ResetInstance(inst->source); diff --git a/ntp_core.h b/ntp_core.h index 67971ead..8b23f2f1 100644 --- a/ntp_core.h +++ b/ntp_core.h @@ -31,6 +31,7 @@ #include "addressing.h" #include "srcparams.h" +#include "nameserv.h" #include "ntp.h" #include "reports.h" @@ -71,7 +72,7 @@ extern void NCR_ResetInstance(NCR_Instance inst); extern void NCR_ResetPoll(NCR_Instance instance); /* Change the remote address of an instance */ -extern void NCR_ChangeRemoteAddress(NCR_Instance inst, NTP_Remote_Address *remote_addr, +extern void NCR_ChangeRemoteAddress(NCR_Instance inst, DNS_SockAddrLookupResult *remote_addr, int ntp_only); /* This routine is called when a new packet arrives off the network, diff --git a/ntp_sources.c b/ntp_sources.c index 4f197589..15f584a2 100644 --- a/ntp_sources.c +++ b/ntp_sources.c @@ -144,7 +144,7 @@ static ARR_Instance pools; /* Requested update of a source's address */ struct AddressUpdate { NTP_Remote_Address old_address; - NTP_Remote_Address new_address; + DNS_SockAddrLookupResult new_address; }; /* Update saved when record_lock is true */ @@ -431,21 +431,25 @@ add_source(NTP_Remote_Address *remote_addr, char *name, int family, NTP_Source_T /* ================================================== */ static NSR_Status -change_source_address(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr, +change_source_address(NTP_Remote_Address *old_addr, DNS_SockAddrLookupResult *new_address, int replacement) { + NTP_Remote_Address new_addr; int slot1, slot2, found; SourceRecord *record; LOG_Severity severity; char *name; + new_addr.ip_addr = new_address->ip.ip; + new_addr.port = new_address->port; + found = find_slot2(old_addr, &slot1); if (found != 2) return NSR_NoSuchSource; /* Make sure there is no other source using the new address (with the same or different port), but allow a source to have its port changed */ - found = find_slot2(new_addr, &slot2); + found = find_slot2(&new_addr, &slot2); if (found == 2 || (found != 0 && slot1 != slot2)) return NSR_AlreadyInUse; @@ -453,14 +457,14 @@ change_source_address(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr record_lock = 1; record = get_record(slot1); - NCR_ChangeRemoteAddress(record->data, new_addr, !replacement); + NCR_ChangeRemoteAddress(record->data, new_address, !replacement); if (replacement) - record->resolved_addr = new_addr->ip_addr; + record->resolved_addr = new_addr.ip_addr; BRIEF_ASSERT(record->remote_addr == NCR_GetRemoteAddress(record->data) && - UTI_CompareIPs(&record->remote_addr->ip_addr, &new_addr->ip_addr, NULL) == 0); + UTI_CompareIPs(&record->remote_addr->ip_addr, &new_addr.ip_addr, NULL) == 0); - if (!UTI_IsIPReal(&old_addr->ip_addr) && UTI_IsIPReal(&new_addr->ip_addr)) { + if (!UTI_IsIPReal(&old_addr->ip_addr) && UTI_IsIPReal(&new_addr.ip_addr)) { if (auto_start_sources) NCR_StartInstance(record->data); if (record->pool_id != INVALID_POOL) @@ -485,10 +489,10 @@ change_source_address(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr LOG(severity, "Source %s %s %s (%s)", UTI_IPToString(&old_addr->ip_addr), replacement ? "replaced with" : "changed to", - UTI_IPToString(&new_addr->ip_addr), name); + UTI_IPToString(&new_addr.ip_addr), name); } else { LOG(severity, "Source %s (%s) changed port to %d", - UTI_IPToString(&new_addr->ip_addr), name, new_addr->port); + UTI_IPToString(&new_addr.ip_addr), name, new_addr.port); } return NSR_Success; @@ -507,7 +511,7 @@ handle_saved_address_update(void) /* This is expected to happen only if the old address is wrong */ LOG(LOGS_ERR, "Could not change %s to %s", UTI_IPSockAddrToString(&saved_address_update.old_address), - UTI_IPSockAddrToString(&saved_address_update.new_address)); + UTI_DNS_SockAddrLookupResultToString(&saved_address_update.new_address)); saved_address_update.old_address.ip_addr.family = IPADDR_UNSPEC; } @@ -515,10 +519,14 @@ handle_saved_address_update(void) /* ================================================== */ static int -replace_source_connectable(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr) +replace_source_connectable(NTP_Remote_Address *old_addr, DNS_SockAddrLookupResult *new_addr) { - if (!NIO_IsServerConnectable(new_addr)) { - DEBUG_LOG("%s not connectable", UTI_IPToString(&new_addr->ip_addr)); + NTP_Remote_Address new_remote; + new_remote.ip_addr = new_addr->ip.ip; + new_remote.port = new_addr->port; + + if (!NIO_IsServerConnectable(&new_remote)) { + DEBUG_LOG("%s not connectable", UTI_IPToString(&new_addr->ip.ip)); return 0; } @@ -535,7 +543,8 @@ replace_source_connectable(NTP_Remote_Address *old_addr, NTP_Remote_Address *new static void process_resolved_name(struct UnresolvedSource *us, DNS_AddressLookupResult *addrs, int n_addrs) { - NTP_Remote_Address old_addr, new_addr; + NTP_Remote_Address old_addr; + DNS_SockAddrLookupResult new_addr; SourceRecord *record; unsigned short first = 0; int i, j, slot; @@ -558,12 +567,12 @@ process_resolved_name(struct UnresolvedSource *us, DNS_AddressLookupResult *addr UTI_GetRandomBytes(&first, sizeof (first)); for (i = 0; i < n_addrs; i++) { - new_addr.ip_addr = addrs[((unsigned int)i + first) % n_addrs].ip; + new_addr.ip = addrs[((unsigned int)i + first) % n_addrs]; - DEBUG_LOG("(%d) %s", i + 1, UTI_IPToString(&new_addr.ip_addr)); + DEBUG_LOG("(%d) %s", i + 1, UTI_IPToString(&new_addr.ip.ip)); /* Skip addresses not from the requested family */ - if (us->family != IPADDR_UNSPEC && us->family != new_addr.ip_addr.family) + if (us->family != IPADDR_UNSPEC && us->family != new_addr.ip.ip.family) continue; if (us->pool_id != INVALID_POOL) { @@ -1174,15 +1183,15 @@ NSR_RefreshAddresses(void) /* ================================================== */ NSR_Status -NSR_UpdateSourceNtpAddress(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr) +NSR_UpdateSourceNtpAddress(NTP_Remote_Address *old_addr, DNS_SockAddrLookupResult *new_addr) { int slot; - if (!UTI_IsIPReal(&old_addr->ip_addr) || !UTI_IsIPReal(&new_addr->ip_addr)) + if (!UTI_IsIPReal(&old_addr->ip_addr) || !UTI_IsIPReal(&new_addr->ip.ip)) return NSR_InvalidAF; - if (UTI_CompareIPs(&old_addr->ip_addr, &new_addr->ip_addr, NULL) != 0 && - find_slot(&new_addr->ip_addr, &slot)) + if (UTI_CompareIPs(&old_addr->ip_addr, &new_addr->ip.ip, NULL) != 0 && + find_slot(&new_addr->ip.ip, &slot)) return NSR_AlreadyInUse; /* If a record is being modified (e.g. by change_source_address(), or the diff --git a/ntp_sources.h b/ntp_sources.h index 89a322e9..3a9050d0 100644 --- a/ntp_sources.h +++ b/ntp_sources.h @@ -99,7 +99,7 @@ extern void NSR_RefreshAddresses(void); /* Procedure to update the address of a source. The update may be postponed. */ extern NSR_Status NSR_UpdateSourceNtpAddress(NTP_Remote_Address *old_addr, - NTP_Remote_Address *new_addr); + DNS_SockAddrLookupResult *new_addr); /* Procedure to get local reference ID corresponding to a source */ extern uint32_t NSR_GetLocalRefid(IPAddr *address); diff --git a/nts_ntp_client.c b/nts_ntp_client.c index 2c3464fe..91bd182d 100644 --- a/nts_ntp_client.c +++ b/nts_ntp_client.c @@ -194,28 +194,32 @@ check_cookies(NNC_Instance inst) static int set_ntp_address(NNC_Instance inst, NTP_Remote_Address *negotiated_address) { - NTP_Remote_Address old_address, new_address; + NTP_Remote_Address old_address; + DNS_SockAddrLookupResult new_address; old_address = inst->ntp_address; - new_address = *negotiated_address; + new_address.ip.service_name[0] = 0; + new_address.ip.ip = negotiated_address->ip_addr; + new_address.port = negotiated_address->port; - if (new_address.ip_addr.family == IPADDR_UNSPEC) - new_address.ip_addr = inst->nts_address.ip_addr; + if (new_address.ip.ip.family == IPADDR_UNSPEC) + new_address.ip.ip = inst->nts_address.ip_addr; if (new_address.port == 0) new_address.port = inst->default_ntp_port; - if (UTI_CompareIPs(&old_address.ip_addr, &new_address.ip_addr, NULL) == 0 && + if (UTI_CompareIPs(&old_address.ip_addr, &new_address.ip.ip, NULL) == 0 && old_address.port == new_address.port) /* Nothing to do */ return 1; if (NSR_UpdateSourceNtpAddress(&old_address, &new_address) != NSR_Success) { LOG(LOGS_ERR, "Could not change %s to negotiated address %s", - UTI_IPToString(&old_address.ip_addr), UTI_IPToString(&new_address.ip_addr)); + UTI_IPToString(&old_address.ip_addr), UTI_IPToString(&new_address.ip.ip)); return 0; } - inst->ntp_address = new_address; + inst->ntp_address.ip_addr = new_address.ip.ip; + inst->ntp_address.port = new_address.port; return 1; } @@ -545,12 +549,17 @@ NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet, /* ================================================== */ void -NNC_ChangeAddress(NNC_Instance inst, IPAddr *address) +NNC_ChangeAddress(NNC_Instance inst, DNS_AddressLookupResult *address) { save_cookies(inst); - inst->nts_address.ip_addr = *address; - inst->ntp_address.ip_addr = *address; + inst->nts_address.ip_addr = address->ip; + inst->ntp_address.ip_addr = address->ip; + + if (address->service_name[0] != '\0') { + free(inst->name); + inst->name = strdup(address->service_name); + } reset_instance(inst); diff --git a/nts_ntp_client.h b/nts_ntp_client.h index 2c314cca..b1928ffb 100644 --- a/nts_ntp_client.h +++ b/nts_ntp_client.h @@ -28,6 +28,7 @@ #define GOT_NTS_NTP_CLIENT_H #include "addressing.h" +#include "nameserv.h" #include "ntp.h" #include "reports.h" @@ -42,7 +43,7 @@ extern int NNC_GenerateRequestAuth(NNC_Instance inst, NTP_Packet *packet, extern int NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet, NTP_PacketInfo *info); -extern void NNC_ChangeAddress(NNC_Instance inst, IPAddr *address); +extern void NNC_ChangeAddress(NNC_Instance inst, DNS_AddressLookupResult *address); extern void NNC_DumpData(NNC_Instance inst); diff --git a/test/unit/ntp_auth.c b/test/unit/ntp_auth.c index e2924a33..d5adaf32 100644 --- a/test/unit/ntp_auth.c +++ b/test/unit/ntp_auth.c @@ -129,6 +129,7 @@ test_unit(void) RPT_AuthReport report; NTP_AuthMode mode; IPSockAddr nts_addr; + DNS_AddressLookupResult lookup; uint32_t key_id, kod; char conf[][100] = { "keyfile ntp_core.keys" @@ -204,8 +205,11 @@ test_unit(void) NAU_DumpData(inst); NAU_GetReport(inst, &report); - if (random() % 2) - NAU_ChangeAddress(inst, &nts_addr.ip_addr); + if (random() % 2) { + lookup.ip = nts_addr.ip_addr; + lookup.service_name[0] = 0; + NAU_ChangeAddress(inst, &lookup); + } if (inst->mode == NTP_AUTH_NTS) { for (j = random() % 5; j > 0; j--) diff --git a/test/unit/ntp_sources.c b/test/unit/ntp_sources.c index 22890154..5fd57756 100644 --- a/test/unit/ntp_sources.c +++ b/test/unit/ntp_sources.c @@ -42,7 +42,7 @@ static void *resolve_handler_arg = NULL; #define NIO_IsServerConnectable(addr) (random() % 2) #define SCH_GetLastEventMonoTime() get_mono_time() -static void change_remote_address(NCR_Instance inst, NTP_Remote_Address *remote_addr, +static void change_remote_address(NCR_Instance inst, DNS_SockAddrLookupResult *remote_addr, int ntp_only); static double get_mono_time(void); @@ -77,10 +77,11 @@ resolve_random_address(DNS_Status status, int rand_bits) static int update_random_address(NTP_Remote_Address *addr, int rand_bits) { - NTP_Remote_Address new_addr; + DNS_SockAddrLookupResult new_addr; NSR_Status status; - TST_GetRandomAddress(&new_addr.ip_addr, IPADDR_UNSPEC, rand_bits); + TST_GetRandomAddress(&new_addr.ip.ip, IPADDR_UNSPEC, rand_bits); + new_addr.ip.service_name[0] = 0; new_addr.port = random() % 1024; status = NSR_UpdateSourceNtpAddress(addr, &new_addr); @@ -96,19 +97,23 @@ update_random_address(NTP_Remote_Address *addr, int rand_bits) } static void -change_remote_address(NCR_Instance inst, NTP_Remote_Address *remote_addr, int ntp_only) +change_remote_address(NCR_Instance inst, DNS_SockAddrLookupResult *remote_addr, int ntp_only) { + NTP_Remote_Address rem_addr; int update = !ntp_only && random() % 4 == 0, update_pos = random() % 2, r = 0; + rem_addr.ip_addr = remote_addr->ip.ip; + rem_addr.port = remote_addr->port; + TEST_CHECK(record_lock); if (update && update_pos == 0) - r = update_random_address(random() % 2 ? remote_addr : NCR_GetRemoteAddress(inst), 4); + r = update_random_address(random() % 2 ? &rem_addr : NCR_GetRemoteAddress(inst), 4); NCR_ChangeRemoteAddress(inst, remote_addr, ntp_only); if (update && update_pos == 1) - r = update_random_address(random() % 2 ? remote_addr : NCR_GetRemoteAddress(inst), 4); + r = update_random_address(random() % 2 ? &rem_addr : NCR_GetRemoteAddress(inst), 4); if (r) TEST_CHECK(UTI_IsIPReal(&saved_address_update.old_address.ip_addr)); diff --git a/test/unit/nts_ntp_client.c b/test/unit/nts_ntp_client.c index 4b567324..501c92ae 100644 --- a/test/unit/nts_ntp_client.c +++ b/test/unit/nts_ntp_client.c @@ -245,7 +245,7 @@ test_unit(void) NTP_PacketInfo info; NTP_Packet packet; IPSockAddr addr; - IPAddr ip_addr; + DNS_AddressLookupResult ip_addr; int i, j, prev_num_cookies, valid; TEST_CHECK(SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256) > 0); @@ -293,9 +293,10 @@ test_unit(void) TEST_CHECK(inst->ok_response == valid); if (random() % 10 == 0) { - TST_GetRandomAddress(&ip_addr, IPADDR_INET4, 32); + TST_GetRandomAddress(&ip_addr.ip, IPADDR_INET4, 32); + ip_addr.service_name[0] = 0; NNC_ChangeAddress(inst, &ip_addr); - TEST_CHECK(UTI_CompareIPs(&inst->nts_address.ip_addr, &ip_addr, NULL) == 0); + TEST_CHECK(UTI_CompareIPs(&inst->nts_address.ip_addr, &ip_addr.ip, NULL) == 0); } } diff --git a/util.c b/util.c index 404e5b74..2a7e71ea 100644 --- a/util.c +++ b/util.c @@ -559,6 +559,23 @@ UTI_IPSockAddrToString(const IPSockAddr *sa) /* ================================================== */ +char * +UTI_DNS_SockAddrLookupResultToString(const DNS_SockAddrLookupResult *sa) +{ + char buf[BUFFER_LENGTH], *result; + + /* Copy to a separate buffer to avoid a compiler warning */ + snprintf(buf, sizeof (buf), "%s", UTI_IPToString(&sa->ip.ip)); + + result = NEXT_BUFFER; + snprintf(result, BUFFER_LENGTH, + sa->ip.ip.family != IPADDR_INET6 ? "%s:%hu" : "[%s]:%hu", buf, sa->port); + + return result; +} + +/* ================================================== */ + char * UTI_IPSubnetToString(IPAddr *subnet, int bits) { diff --git a/util.h b/util.h index 682c58f5..5147d005 100644 --- a/util.h +++ b/util.h @@ -30,6 +30,7 @@ #include "sysincl.h" #include "addressing.h" +#include "nameserv.h" #include "ntp.h" #include "candm.h" #include "cmac.h" @@ -119,6 +120,7 @@ extern void UTI_IPNetworkToHost(const IPAddr *src, IPAddr *dest); extern int UTI_CompareIPs(const IPAddr *a, const IPAddr *b, const IPAddr *mask); extern char *UTI_IPSockAddrToString(const IPSockAddr *sa); +extern char *UTI_DNS_SockAddrLookupResultToString(const DNS_SockAddrLookupResult *sa); extern char *UTI_IPSubnetToString(IPAddr *subnet, int bits); From 9b34d4cf651050c3abefc62bfbb1784444077033 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Wed, 7 Jan 2026 13:39:13 +0100 Subject: [PATCH 12/20] dns: Add parameter to DNS lookups to indicate whether its for NTS. This will allow the DNS lookup code to do service lookups only when doing name resolving for NTSKE servers. --- client.c | 10 +++++----- cmdparse.c | 2 +- conf.c | 2 +- nameserv.c | 2 +- nameserv.h | 2 +- nameserv_async.c | 6 ++++-- nameserv_async.h | 2 +- ntp_sources.c | 10 ++++++++-- nts_ke_client.c | 2 +- privops.c | 7 ++++--- privops.h | 2 +- test/unit/ntp_sources.c | 2 +- 12 files changed, 29 insertions(+), 20 deletions(-) diff --git a/client.c b/client.c index bb1312b4..2c384f1e 100644 --- a/client.c +++ b/client.c @@ -175,7 +175,7 @@ get_addresses(const char *hostnames, int port) addr->type = SCK_ADDR_UNIX; addr->addr.path = Strdup(hostname); } else { - if (DNS_Name2IPAddress(hostname, looked_up_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; } @@ -454,7 +454,7 @@ parse_source_address(char *word, IPAddr *address) if (UTI_StringToIdIP(word, address)) return 1; - if (DNS_Name2IPAddress(word, &lookup, 1) == DNS_Success) { + if (DNS_Name2IPAddress(word, &lookup, 1, 0) == DNS_Success) { *address = lookup.ip; return 1; } @@ -950,7 +950,7 @@ process_cmd_accheck(CMD_Request *msg, char *line) { DNS_AddressLookupResult lookup; msg->command = htons(REQ_ACCHECK); - if (DNS_Name2IPAddress(line, &lookup, 1) == DNS_Success) { + if (DNS_Name2IPAddress(line, &lookup, 1, 0) == DNS_Success) { UTI_IPHostToNetwork(&lookup.ip, &msg->data.ac_check.ip); return 1; } else { @@ -966,7 +966,7 @@ process_cmd_cmdaccheck(CMD_Request *msg, char *line) { DNS_AddressLookupResult lookup; msg->command = htons(REQ_CMDACCHECK); - if (DNS_Name2IPAddress(line, &lookup, 1) == DNS_Success) { + if (DNS_Name2IPAddress(line, &lookup, 1, 0) == DNS_Success) { UTI_IPHostToNetwork(&lookup.ip, &msg->data.ac_check.ip); return 1; } else { @@ -1055,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, &lookup, 1) != DNS_Success) { + DNS_Name2IPAddress(data.name, &lookup, 1, data.params.nts) != DNS_Success) { LOG(LOGS_ERR, "Invalid host/IP address"); break; } diff --git a/cmdparse.c b/cmdparse.c index 0e24b7b9..6c142055 100644 --- a/cmdparse.c +++ b/cmdparse.c @@ -295,7 +295,7 @@ CPS_ParseAllowDeny(char *line, int *all, IPAddr *ip, int *subnet_bits) } /* The last possibility is a hostname */ - if (bits < 0 && DNS_Name2IPAddress(net, &lookup_result, 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; diff --git a/conf.c b/conf.c index de0c5a64..cc29fdb9 100644 --- a/conf.c +++ b/conf.c @@ -1190,7 +1190,7 @@ parse_initstepslew(char *line) hostname = p; p = CPS_SplitWord(p); if (*hostname) { - if (DNS_Name2IPAddress(hostname, &addr, 1) == DNS_Success) { + 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); diff --git a/nameserv.c b/nameserv.c index 99609964..37a2d032 100644 --- a/nameserv.c +++ b/nameserv.c @@ -48,7 +48,7 @@ DNS_SetAddressFamily(int family) } DNS_Status -DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_addrs) +DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_addrs, int service_nts) { struct addrinfo hints, *res, *ai; int i, result; diff --git a/nameserv.h b/nameserv.h index 69ef9262..3f32c6bf 100644 --- a/nameserv.h +++ b/nameserv.h @@ -54,7 +54,7 @@ extern void DNS_SetAddressFamily(int family); /* Maximum number of addresses returned by DNS_Name2IPAddress */ #define DNS_MAX_ADDRESSES 16 -extern DNS_Status DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_addrs); +extern DNS_Status DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_addrs, int service_nts); extern int DNS_IPAddress2Name(IPAddr *ip_addr, char *name, int len); diff --git a/nameserv_async.c b/nameserv_async.c index f6aa7dd8..c23f0b27 100644 --- a/nameserv_async.c +++ b/nameserv_async.c @@ -42,6 +42,7 @@ struct DNS_Async_Instance { const char *name; + int service_nts; DNS_Status status; DNS_AddressLookupResult addresses[DNS_MAX_ADDRESSES]; DNS_NameResolveHandler handler; @@ -61,7 +62,7 @@ start_resolving(void *anything) struct DNS_Async_Instance *inst = (struct DNS_Async_Instance *)anything; pthread_mutex_lock(&privops_lock); - inst->status = PRV_Name2IPAddress(inst->name, inst->addresses, DNS_MAX_ADDRESSES); + inst->status = PRV_Name2IPAddress(inst->name, inst->addresses, DNS_MAX_ADDRESSES, inst->service_nts); pthread_mutex_unlock(&privops_lock); /* Notify the main thread that the result is ready */ @@ -99,12 +100,13 @@ end_resolving(int fd, int event, void *anything) /* ================================================== */ void -DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, void *anything) +DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, int service_nts, void *anything) { struct DNS_Async_Instance *inst; inst = MallocNew(struct DNS_Async_Instance); inst->name = name; + inst->service_nts = service_nts; inst->handler = handler; inst->arg = anything; inst->status = DNS_Failure; diff --git a/nameserv_async.h b/nameserv_async.h index 00bd2451..000fe6b4 100644 --- a/nameserv_async.h +++ b/nameserv_async.h @@ -35,6 +35,6 @@ typedef void (*DNS_NameResolveHandler)(DNS_Status status, int n_addrs, DNS_Addre /* Request resolving of a name to IP address. The handler will be called when the result is available. */ -extern void DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, void *anything); +extern void DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, int service_nts, void *anything); #endif diff --git a/ntp_sources.c b/ntp_sources.c index 15f584a2..c6a145ba 100644 --- a/ntp_sources.c +++ b/ntp_sources.c @@ -59,6 +59,7 @@ typedef struct { NCR_Instance data; /* Data for the protocol engine for this source */ char *name; /* Name of the source as it was specified (may be an IP address) */ + int nts; /* Whether the source is NTS enabled */ IPAddr resolved_addr; /* Address resolved from the name, which can be different from remote_addr (e.g. NTS-KE) */ int family; /* IP family of acceptable resolved addresses @@ -100,6 +101,8 @@ struct UnresolvedSource { int pool_id; /* Name to be resolved */ char *name; + /* Whether it should be resolved for NTS */ + int service_nts; /* Address family to filter resolved addresses */ int family; /* Flag indicating addresses should be used in a random order */ @@ -398,6 +401,7 @@ add_source(NTP_Remote_Address *remote_addr, char *name, int family, NTP_Source_T record = get_record(slot); record->name = Strdup(name ? name : UTI_IPToString(&remote_addr->ip_addr)); + record->nts = params->nts; record->data = NCR_CreateInstance(remote_addr, type, params, record->name); record->remote_addr = NCR_GetRemoteAddress(record->data); record->resolved_addr = remote_addr->ip_addr; @@ -671,7 +675,7 @@ name_resolve_handler(DNS_Status status, int n_addrs, DNS_AddressLookupResult *ad if (next) { /* Continue with the next source in the list */ DEBUG_LOG("resolving %s", next->name); - DNS_Name2IPAddressAsync(next->name, name_resolve_handler, next); + DNS_Name2IPAddressAsync(next->name, name_resolve_handler, next->service_nts, next); } else { /* This was the last source in the list. If some sources couldn't be resolved, try again in exponentially increasing interval. */ @@ -717,7 +721,7 @@ resolve_sources(void) resolving_source = us; DEBUG_LOG("resolving %s", us->name); - DNS_Name2IPAddressAsync(us->name, name_resolve_handler, us); + DNS_Name2IPAddressAsync(us->name, name_resolve_handler, us->service_nts, us); } /* ================================================== */ @@ -842,6 +846,7 @@ NSR_AddSourceByName(char *name, int family, int port, int pool, NTP_Source_Type us = MallocNew(struct UnresolvedSource); us->name = Strdup(name); + us->service_nts = params->nts; us->family = family; us->random_order = 0; us->refreshment = 0; @@ -1070,6 +1075,7 @@ resolve_source_replacement(SourceRecord *record, int refreshment) us = MallocNew(struct UnresolvedSource); us->name = Strdup(record->name); + us->service_nts = record->nts; us->family = record->family; /* Ignore the order of addresses from the resolver to not get stuck with a pair of unreachable or otherwise unusable servers diff --git a/nts_ke_client.c b/nts_ke_client.c index 563d2c56..77f30111 100644 --- a/nts_ke_client.c +++ b/nts_ke_client.c @@ -330,7 +330,7 @@ handle_message(void *arg) inst->server_name[length + 1] = '\0'; } - DNS_Name2IPAddressAsync(inst->server_name, name_resolve_handler, inst); + DNS_Name2IPAddressAsync(inst->server_name, name_resolve_handler, 0, inst); inst->resolving_name = 1; } } diff --git a/privops.c b/privops.c index 1c2dec88..3c47c9df 100644 --- a/privops.c +++ b/privops.c @@ -76,6 +76,7 @@ typedef struct { typedef struct { char name[256]; + int service_nts; } ReqName2IPAddress; typedef struct { @@ -283,7 +284,7 @@ do_name_to_ipaddress(ReqName2IPAddress *req, PrvResponse *res) req->name[sizeof (req->name) - 1] = '\0'; res->rc = DNS_Name2IPAddress(req->name, res->data.name_to_ipaddress.addresses, - DNS_MAX_ADDRESSES); + DNS_MAX_ADDRESSES, req->service_nts); } #endif @@ -573,14 +574,14 @@ PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len) #ifdef PRIVOPS_NAME2IPADDRESS int -PRV_Name2IPAddress(const char *name, DNS_AddressLookupResult *ip_addrs, int max_addrs) +PRV_Name2IPAddress(const char *name, DNS_AddressLookupResult *ip_addrs, int service_nts, int max_addrs) { PrvRequest req; PrvResponse res; int i; if (!have_helper()) - return DNS_Name2IPAddress(name, ip_addrs, max_addrs); + return DNS_Name2IPAddress(name, ip_addrs, service_nts, max_addrs); memset(&req, 0, sizeof (req)); req.op = OP_NAME2IPADDRESS; diff --git a/privops.h b/privops.h index 48a35cab..c0f4223a 100644 --- a/privops.h +++ b/privops.h @@ -55,7 +55,7 @@ int PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len); #endif #ifdef PRIVOPS_NAME2IPADDRESS -int PRV_Name2IPAddress(const char *name, DNS_AddressLookupResult *ip_addrs, int max_addrs); +int PRV_Name2IPAddress(const char *name, DNS_AddressLookupResult *ip_addrs, int service_nts, int max_addrs); #else #define PRV_Name2IPAddress DNS_Name2IPAddress #endif diff --git a/test/unit/ntp_sources.c b/test/unit/ntp_sources.c index 5fd57756..c904c17f 100644 --- a/test/unit/ntp_sources.c +++ b/test/unit/ntp_sources.c @@ -32,7 +32,7 @@ static char *requested_name = NULL; static DNS_NameResolveHandler resolve_handler = NULL; static void *resolve_handler_arg = NULL; -#define DNS_Name2IPAddressAsync(name, handler, arg) \ +#define DNS_Name2IPAddressAsync(name, handler, service_nts, arg) \ requested_name = (name), \ resolve_handler = (handler), \ resolve_handler_arg = (arg) From aa5c1277b9e6434b1479f22f4a1d330f2814413e Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Thu, 8 Jan 2026 15:29:55 +0100 Subject: [PATCH 13/20] dns: Implement SRV record indirection for NTSKE lookups. This allows the actual address of an NTSKE server to be behind an SRV record, allowing SRV record based pools. Since such records also modify the name expected on the certificate, we require succesfull DNSSEC validation for these records. --- configure | 24 ++++++++- nameserv.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 195b1ed7..ec66e657 100755 --- a/configure +++ b/configure @@ -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 @@ -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 @@ -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 @@ -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 ;; @@ -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="" @@ -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" @@ -1141,4 +1162,3 @@ done # ======================================================================= # vim:et:sw=2:ht=2:sts=2:fdm=marker:cms=#%s - diff --git a/nameserv.c b/nameserv.c index 37a2d032..97a5e3f6 100644 --- a/nameserv.c +++ b/nameserv.c @@ -32,7 +32,14 @@ #include #include +#ifdef FEAT_SRV +#include +#endif +#ifdef FEAT_SRV +#include "logging.h" +#include "memory.h" +#endif #include "nameserv.h" #include "socket.h" #include "util.h" @@ -41,6 +48,24 @@ static int address_family = IPADDR_UNSPEC; +#ifdef FEAT_SRV +#define NTS_SERVICE_NAME "_ntske._tcp." + +static getdns_context *dns_context = NULL; + +static void +reinit() +{ + if (dns_context != NULL) + { + getdns_context_destroy(dns_context); + dns_context = NULL; + } + if (getdns_context_create(&dns_context, 1)) + LOG_MESSAGE(LOGS_ERR, "Could not initialize DNS resolver."); +} +#endif + void DNS_SetAddressFamily(int family) { @@ -61,6 +86,10 @@ DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_add addrs[i].service_name[0] = 0; } + /* Avoid dealing with the max_addrs=0 edgecase below */ + if (max_addrs < 1) + return DNS_Success; + /* Avoid calling getaddrinfo() if the name is an IP address */ if (UTI_StringToIP(name, &ip)) { if (address_family != IPADDR_UNSPEC && ip.family != address_family) @@ -70,6 +99,126 @@ DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_add return DNS_Success; } +#ifdef FEAT_SRV + /* First try if we can do a service record based resolution" */ + if (service_nts) + { + int write_idx; + getdns_dict *extensions = NULL, *response = NULL; + getdns_list *service_addresses = NULL; + getdns_dict *service_entry = NULL; + getdns_bindata *raw_data = NULL; + char *service_domain = NULL; + char *last_name = NULL; + getdns_return_t getdns_status; + size_t returned_addresses; + + if (dns_context == NULL) + { + reinit(); + if (dns_context == NULL) + { +#ifdef FORCE_DNSRETRY + return DNS_TryAgain; +#else + return DNS_Failure; +#endif + } + } + + service_domain = Malloc(strlen(NTS_SERVICE_NAME) + strlen(name) + 1); + strcpy(service_domain, NTS_SERVICE_NAME); + strcat(service_domain, name); + + if ((extensions = getdns_dict_create()) == NULL) + LOG_FATAL("Could not allocate memory"); + if (getdns_dict_set_int(extensions, "dnssec_return_only_secure", GETDNS_EXTENSION_TRUE)) + LOG_FATAL("Could not allocate memory"); + getdns_status = getdns_service_sync(dns_context, service_domain, extensions, &response); + free(service_domain); + getdns_dict_destroy(extensions); + if (getdns_status) + { +#ifdef FORCE_DNSRETRY + return DNS_TryAgain; +#else + return DNS_Failure; +#endif + } + + if (getdns_dict_get_list(response, "/srv_addresses", &service_addresses)) + LOG_FATAL("Unrecoverable error calling getdns."); + if (getdns_list_get_length(service_addresses, &returned_addresses)) + LOG_FATAL("Unrecoverable error calling getdns."); + + write_idx = 0; + + for (i = 0; i < returned_addresses; i++) + { + if (getdns_list_get_dict(service_addresses, i, &service_entry)) + LOG_FATAL("Unrecoverable error calling getdns."); + if (getdns_dict_get_bindata(service_entry, "domain_name", &raw_data)) + LOG_FATAL("Unrecoverable error calling getdns."); + if (getdns_convert_dns_name_to_fqdn(raw_data, &service_domain)) + LOG_FATAL("Unrecoverable error calling getnds."); + //*Ignore too-long domain names */ + if (strlen(service_domain) >= DNS_SERVICE_NAME_LEN) + continue; + /* Ignore repeated names. This is needed to deal with multiple + addresses from the same service. */ + if (last_name != NULL && strcmp(last_name, service_domain) == 0) + continue; + if (getdns_dict_get_bindata(service_entry, "address_data", &raw_data)) { + // No pre-populated address, recurse to resolve name + if (DNS_Name2IPAddress(service_domain, &addrs[write_idx], 1, 0) == DNS_Success) { + strncpy(addrs[write_idx].service_name, service_domain, DNS_SERVICE_NAME_LEN-1); + write_idx++; + free(last_name); + last_name = service_domain; + service_domain = NULL; + } + } else { + switch (raw_data->size) { + case sizeof (addrs[write_idx].ip.addr.in4): + if (address_family != IPADDR_UNSPEC && address_family != IPADDR_INET4) + continue; + /* copy first to deal with the fact that alignment of data might not be okay. */ + memcpy(&addrs[write_idx].ip.addr.in4, raw_data->data, + sizeof (addrs[write_idx].ip.addr.in4)); + addrs[write_idx].ip.addr.in4 = htonl(addrs[write_idx].ip.addr.in4); + addrs[write_idx].ip.family = IPADDR_INET4; + strncpy(addrs[write_idx].service_name, service_domain, DNS_SERVICE_NAME_LEN-1); + write_idx++; + free(last_name); + last_name = service_domain; + service_domain = NULL; + break; +#ifdef FEAT_IPV6 + case sizeof (addrs[write_idx].ip.addr.in6): + if (address_family != IPADDR_UNSPEC && address_family != IPADDR_INET6) + continue; + memcpy(addrs[write_idx].ip.addr.in6, raw_data->data, + sizeof(addrs[write_idx].ip.addr.in6)); + addrs[write_idx].ip.family = IPADDR_INET6; + strncpy(addrs[write_idx].service_name, service_domain, DNS_SERVICE_NAME_LEN-1); + write_idx++; + free(last_name); + last_name = service_domain; + service_domain = NULL; + break; +#endif + } + } + free(service_domain); + } + + if (addrs[0].ip.family != IPADDR_UNSPEC) + return DNS_Success; + } + + /* Fall back to regular name resolution */ +#endif + memset(&hints, 0, sizeof (hints)); switch (address_family) { @@ -162,6 +311,9 @@ void DNS_Reload(void) { res_init(); +#ifdef FEAT_SRV + reinit(); +#endif } /* ================================================== */ From f0759f7fd623a5f455d348e173e781fa6997a277 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Mon, 12 Jan 2026 13:31:34 +0100 Subject: [PATCH 14/20] dns: Trim trailing dot from resolved SRV domain name. This solves issues with certificates not validating for the new domain name. --- nameserv.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nameserv.c b/nameserv.c index 97a5e3f6..253bcd69 100644 --- a/nameserv.c +++ b/nameserv.c @@ -112,6 +112,7 @@ DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_add char *last_name = NULL; getdns_return_t getdns_status; size_t returned_addresses; + size_t domain_name_len; if (dns_context == NULL) { @@ -161,6 +162,10 @@ DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_add LOG_FATAL("Unrecoverable error calling getdns."); if (getdns_convert_dns_name_to_fqdn(raw_data, &service_domain)) LOG_FATAL("Unrecoverable error calling getnds."); + /* Remove any potential trailing dot as it would interfere with certificate validation*/ + domain_name_len = strlen(service_domain); + if (service_domain[domain_name_len-1] == '.') + service_domain[domain_name_len-1] = 0; //*Ignore too-long domain names */ if (strlen(service_domain) >= DNS_SERVICE_NAME_LEN) continue; From 9ef30e52e33fc8056ef4904abf8b77ba43a75345 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Mon, 12 Jan 2026 13:37:45 +0100 Subject: [PATCH 15/20] dns: Use SRV record port. This allows the SRV record to specify a custom port for an NTSKE server. --- nameserv.c | 1 + nameserv.h | 1 + nts_ntp_client.c | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/nameserv.c b/nameserv.c index 253bcd69..2bb45401 100644 --- a/nameserv.c +++ b/nameserv.c @@ -84,6 +84,7 @@ DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_add for (i = 0; i < max_addrs; i++) { addrs[i].ip.family = IPADDR_UNSPEC; addrs[i].service_name[0] = 0; + addrs[i].service_port = 0; } /* Avoid dealing with the max_addrs=0 edgecase below */ diff --git a/nameserv.h b/nameserv.h index 3f32c6bf..627e494e 100644 --- a/nameserv.h +++ b/nameserv.h @@ -41,6 +41,7 @@ typedef enum { typedef struct { IPAddr ip; char service_name[DNS_SERVICE_NAME_LEN]; + uint16_t service_port; } DNS_AddressLookupResult; typedef struct { diff --git a/nts_ntp_client.c b/nts_ntp_client.c index 91bd182d..925563e3 100644 --- a/nts_ntp_client.c +++ b/nts_ntp_client.c @@ -561,6 +561,10 @@ NNC_ChangeAddress(NNC_Instance inst, DNS_AddressLookupResult *address) inst->name = strdup(address->service_name); } + if (address->service_port != 0) { + inst->nts_address.port = address->service_port; + } + reset_instance(inst); DEBUG_LOG("NTS reset"); From 027cb7cd32ccae1cf43e5bdd4bb7aa2f67b060bd Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Mon, 2 Feb 2026 16:34:40 +0100 Subject: [PATCH 16/20] rename ip->ip_addr in DNS_SockAddrLookupResult --- nameserv.h | 2 +- ntp_core.c | 6 +++--- ntp_sources.c | 18 +++++++++--------- nts_ntp_client.c | 14 +++++++------- util.c | 4 ++-- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/nameserv.h b/nameserv.h index 627e494e..054d83a4 100644 --- a/nameserv.h +++ b/nameserv.h @@ -45,7 +45,7 @@ typedef struct { } DNS_AddressLookupResult; typedef struct { - DNS_AddressLookupResult ip; + DNS_AddressLookupResult ip_addr; uint16_t port; } DNS_SockAddrLookupResult; diff --git a/ntp_core.c b/ntp_core.c index 9213a56e..a274c106 100644 --- a/ntp_core.c +++ b/ntp_core.c @@ -828,9 +828,9 @@ NCR_ChangeRemoteAddress(NCR_Instance inst, DNS_SockAddrLookupResult *remote_addr NCR_ResetInstance(inst); if (!ntp_only) - NAU_ChangeAddress(inst->auth, &remote_addr->ip); + NAU_ChangeAddress(inst->auth, &remote_addr->ip_addr); - inst->remote_addr.ip_addr = remote_addr->ip.ip; + inst->remote_addr.ip_addr = remote_addr->ip_addr.ip; inst->remote_addr.port = remote_addr->port; if (inst->mode == MODE_CLIENT) @@ -849,7 +849,7 @@ NCR_ChangeRemoteAddress(NCR_Instance inst, DNS_SockAddrLookupResult *remote_addr NCR_ResetPoll(inst); /* Update the reference ID and reset the source/sourcestats instances */ - SRC_SetRefid(inst->source, UTI_IPToRefid(&remote_addr->ip.ip), + SRC_SetRefid(inst->source, UTI_IPToRefid(&remote_addr->ip_addr.ip), &inst->remote_addr.ip_addr); SRC_ResetInstance(inst->source); diff --git a/ntp_sources.c b/ntp_sources.c index c6a145ba..b943a490 100644 --- a/ntp_sources.c +++ b/ntp_sources.c @@ -444,7 +444,7 @@ change_source_address(NTP_Remote_Address *old_addr, DNS_SockAddrLookupResult *ne LOG_Severity severity; char *name; - new_addr.ip_addr = new_address->ip.ip; + new_addr.ip_addr = new_address->ip_addr.ip; new_addr.port = new_address->port; found = find_slot2(old_addr, &slot1); @@ -526,11 +526,11 @@ static int replace_source_connectable(NTP_Remote_Address *old_addr, DNS_SockAddrLookupResult *new_addr) { NTP_Remote_Address new_remote; - new_remote.ip_addr = new_addr->ip.ip; + new_remote.ip_addr = new_addr->ip_addr.ip; new_remote.port = new_addr->port; if (!NIO_IsServerConnectable(&new_remote)) { - DEBUG_LOG("%s not connectable", UTI_IPToString(&new_addr->ip.ip)); + DEBUG_LOG("%s not connectable", UTI_IPToString(&new_addr->ip_addr.ip)); return 0; } @@ -571,12 +571,12 @@ process_resolved_name(struct UnresolvedSource *us, DNS_AddressLookupResult *addr UTI_GetRandomBytes(&first, sizeof (first)); for (i = 0; i < n_addrs; i++) { - new_addr.ip = addrs[((unsigned int)i + first) % n_addrs]; + new_addr.ip_addr = addrs[((unsigned int)i + first) % n_addrs]; - DEBUG_LOG("(%d) %s", i + 1, UTI_IPToString(&new_addr.ip.ip)); + DEBUG_LOG("(%d) %s", i + 1, UTI_IPToString(&new_addr.ip_addr.ip)); /* Skip addresses not from the requested family */ - if (us->family != IPADDR_UNSPEC && us->family != new_addr.ip.ip.family) + if (us->family != IPADDR_UNSPEC && us->family != new_addr.ip_addr.ip.family) continue; if (us->pool_id != INVALID_POOL) { @@ -1193,11 +1193,11 @@ NSR_UpdateSourceNtpAddress(NTP_Remote_Address *old_addr, DNS_SockAddrLookupResul { int slot; - if (!UTI_IsIPReal(&old_addr->ip_addr) || !UTI_IsIPReal(&new_addr->ip.ip)) + if (!UTI_IsIPReal(&old_addr->ip_addr) || !UTI_IsIPReal(&new_addr->ip_addr.ip)) return NSR_InvalidAF; - if (UTI_CompareIPs(&old_addr->ip_addr, &new_addr->ip.ip, NULL) != 0 && - find_slot(&new_addr->ip.ip, &slot)) + if (UTI_CompareIPs(&old_addr->ip_addr, &new_addr->ip_addr.ip, NULL) != 0 && + find_slot(&new_addr->ip_addr.ip, &slot)) return NSR_AlreadyInUse; /* If a record is being modified (e.g. by change_source_address(), or the diff --git a/nts_ntp_client.c b/nts_ntp_client.c index 925563e3..5fbcee8d 100644 --- a/nts_ntp_client.c +++ b/nts_ntp_client.c @@ -198,27 +198,27 @@ set_ntp_address(NNC_Instance inst, NTP_Remote_Address *negotiated_address) DNS_SockAddrLookupResult new_address; old_address = inst->ntp_address; - new_address.ip.service_name[0] = 0; - new_address.ip.ip = negotiated_address->ip_addr; + new_address.ip_addr.service_name[0] = 0; + new_address.ip_addr.ip = negotiated_address->ip_addr; new_address.port = negotiated_address->port; - if (new_address.ip.ip.family == IPADDR_UNSPEC) - new_address.ip.ip = inst->nts_address.ip_addr; + if (new_address.ip_addr.ip.family == IPADDR_UNSPEC) + new_address.ip_addr.ip = inst->nts_address.ip_addr; if (new_address.port == 0) new_address.port = inst->default_ntp_port; - if (UTI_CompareIPs(&old_address.ip_addr, &new_address.ip.ip, NULL) == 0 && + if (UTI_CompareIPs(&old_address.ip_addr, &new_address.ip_addr.ip, NULL) == 0 && old_address.port == new_address.port) /* Nothing to do */ return 1; if (NSR_UpdateSourceNtpAddress(&old_address, &new_address) != NSR_Success) { LOG(LOGS_ERR, "Could not change %s to negotiated address %s", - UTI_IPToString(&old_address.ip_addr), UTI_IPToString(&new_address.ip.ip)); + UTI_IPToString(&old_address.ip_addr), UTI_IPToString(&new_address.ip_addr.ip)); return 0; } - inst->ntp_address.ip_addr = new_address.ip.ip; + inst->ntp_address.ip_addr = new_address.ip_addr.ip; inst->ntp_address.port = new_address.port; return 1; diff --git a/util.c b/util.c index 2a7e71ea..7ac4d428 100644 --- a/util.c +++ b/util.c @@ -565,11 +565,11 @@ UTI_DNS_SockAddrLookupResultToString(const DNS_SockAddrLookupResult *sa) char buf[BUFFER_LENGTH], *result; /* Copy to a separate buffer to avoid a compiler warning */ - snprintf(buf, sizeof (buf), "%s", UTI_IPToString(&sa->ip.ip)); + snprintf(buf, sizeof (buf), "%s", UTI_IPToString(&sa->ip_addr.ip)); result = NEXT_BUFFER; snprintf(result, BUFFER_LENGTH, - sa->ip.ip.family != IPADDR_INET6 ? "%s:%hu" : "[%s]:%hu", buf, sa->port); + sa->ip_addr.ip.family != IPADDR_INET6 ? "%s:%hu" : "[%s]:%hu", buf, sa->port); return result; } From 23854a0393bda7d7142dc78affc48389af4a6cdd Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Mon, 2 Feb 2026 16:54:44 +0100 Subject: [PATCH 17/20] formatting style --- nameserv.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/nameserv.c b/nameserv.c index 2bb45401..424c2fc8 100644 --- a/nameserv.c +++ b/nameserv.c @@ -102,8 +102,7 @@ DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_add #ifdef FEAT_SRV /* First try if we can do a service record based resolution" */ - if (service_nts) - { + if (service_nts) { int write_idx; getdns_dict *extensions = NULL, *response = NULL; getdns_list *service_addresses = NULL; @@ -115,11 +114,9 @@ DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_add size_t returned_addresses; size_t domain_name_len; - if (dns_context == NULL) - { + if (dns_context == NULL) { reinit(); - if (dns_context == NULL) - { + if (dns_context == NULL) { #ifdef FORCE_DNSRETRY return DNS_TryAgain; #else @@ -139,8 +136,7 @@ DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_add getdns_status = getdns_service_sync(dns_context, service_domain, extensions, &response); free(service_domain); getdns_dict_destroy(extensions); - if (getdns_status) - { + if (getdns_status) { #ifdef FORCE_DNSRETRY return DNS_TryAgain; #else @@ -155,8 +151,7 @@ DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_add write_idx = 0; - for (i = 0; i < returned_addresses; i++) - { + for (i = 0; i < returned_addresses; i++) { if (getdns_list_get_dict(service_addresses, i, &service_entry)) LOG_FATAL("Unrecoverable error calling getdns."); if (getdns_dict_get_bindata(service_entry, "domain_name", &raw_data)) From d3654821b4fece81bf38d79af145dbb2949261ee Mon Sep 17 00:00:00 2001 From: Marc Schoolderman Date: Mon, 2 Feb 2026 16:39:27 +0100 Subject: [PATCH 18/20] rename service_nts --- nameserv.c | 4 ++-- nameserv.h | 2 +- nameserv_async.c | 8 ++++---- nameserv_async.h | 2 +- ntp_sources.c | 10 +++++----- privops.c | 8 ++++---- privops.h | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/nameserv.c b/nameserv.c index 424c2fc8..db6b3360 100644 --- a/nameserv.c +++ b/nameserv.c @@ -73,7 +73,7 @@ DNS_SetAddressFamily(int family) } DNS_Status -DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_addrs, int service_nts) +DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_addrs, int use_srv_lookup) { struct addrinfo hints, *res, *ai; int i, result; @@ -102,7 +102,7 @@ DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_add #ifdef FEAT_SRV /* First try if we can do a service record based resolution" */ - if (service_nts) { + if (use_srv_lookup) { int write_idx; getdns_dict *extensions = NULL, *response = NULL; getdns_list *service_addresses = NULL; diff --git a/nameserv.h b/nameserv.h index 054d83a4..29bb424e 100644 --- a/nameserv.h +++ b/nameserv.h @@ -55,7 +55,7 @@ extern void DNS_SetAddressFamily(int family); /* Maximum number of addresses returned by DNS_Name2IPAddress */ #define DNS_MAX_ADDRESSES 16 -extern DNS_Status DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_addrs, int service_nts); +extern DNS_Status DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_addrs, int use_srv_lookup); extern int DNS_IPAddress2Name(IPAddr *ip_addr, char *name, int len); diff --git a/nameserv_async.c b/nameserv_async.c index c23f0b27..805d9c68 100644 --- a/nameserv_async.c +++ b/nameserv_async.c @@ -42,7 +42,7 @@ struct DNS_Async_Instance { const char *name; - int service_nts; + int use_srv_lookup; DNS_Status status; DNS_AddressLookupResult addresses[DNS_MAX_ADDRESSES]; DNS_NameResolveHandler handler; @@ -62,7 +62,7 @@ start_resolving(void *anything) struct DNS_Async_Instance *inst = (struct DNS_Async_Instance *)anything; pthread_mutex_lock(&privops_lock); - inst->status = PRV_Name2IPAddress(inst->name, inst->addresses, DNS_MAX_ADDRESSES, inst->service_nts); + inst->status = PRV_Name2IPAddress(inst->name, inst->addresses, DNS_MAX_ADDRESSES, inst->use_srv_lookup); pthread_mutex_unlock(&privops_lock); /* Notify the main thread that the result is ready */ @@ -100,13 +100,13 @@ end_resolving(int fd, int event, void *anything) /* ================================================== */ void -DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, int service_nts, void *anything) +DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, int use_srv_lookup, void *anything) { struct DNS_Async_Instance *inst; inst = MallocNew(struct DNS_Async_Instance); inst->name = name; - inst->service_nts = service_nts; + inst->use_srv_lookup = use_srv_lookup; inst->handler = handler; inst->arg = anything; inst->status = DNS_Failure; diff --git a/nameserv_async.h b/nameserv_async.h index 000fe6b4..e5662907 100644 --- a/nameserv_async.h +++ b/nameserv_async.h @@ -35,6 +35,6 @@ typedef void (*DNS_NameResolveHandler)(DNS_Status status, int n_addrs, DNS_Addre /* Request resolving of a name to IP address. The handler will be called when the result is available. */ -extern void DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, int service_nts, void *anything); +extern void DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, int perform_srv, void *anything); #endif diff --git a/ntp_sources.c b/ntp_sources.c index b943a490..d2294371 100644 --- a/ntp_sources.c +++ b/ntp_sources.c @@ -102,7 +102,7 @@ struct UnresolvedSource { /* Name to be resolved */ char *name; /* Whether it should be resolved for NTS */ - int service_nts; + int perform_srv; /* Address family to filter resolved addresses */ int family; /* Flag indicating addresses should be used in a random order */ @@ -675,7 +675,7 @@ name_resolve_handler(DNS_Status status, int n_addrs, DNS_AddressLookupResult *ad if (next) { /* Continue with the next source in the list */ DEBUG_LOG("resolving %s", next->name); - DNS_Name2IPAddressAsync(next->name, name_resolve_handler, next->service_nts, next); + DNS_Name2IPAddressAsync(next->name, name_resolve_handler, next->perform_srv, next); } else { /* This was the last source in the list. If some sources couldn't be resolved, try again in exponentially increasing interval. */ @@ -721,7 +721,7 @@ resolve_sources(void) resolving_source = us; DEBUG_LOG("resolving %s", us->name); - DNS_Name2IPAddressAsync(us->name, name_resolve_handler, us->service_nts, us); + DNS_Name2IPAddressAsync(us->name, name_resolve_handler, us->perform_srv, us); } /* ================================================== */ @@ -846,7 +846,7 @@ NSR_AddSourceByName(char *name, int family, int port, int pool, NTP_Source_Type us = MallocNew(struct UnresolvedSource); us->name = Strdup(name); - us->service_nts = params->nts; + us->perform_srv = params->nts; us->family = family; us->random_order = 0; us->refreshment = 0; @@ -1075,7 +1075,7 @@ resolve_source_replacement(SourceRecord *record, int refreshment) us = MallocNew(struct UnresolvedSource); us->name = Strdup(record->name); - us->service_nts = record->nts; + us->perform_srv = record->nts; us->family = record->family; /* Ignore the order of addresses from the resolver to not get stuck with a pair of unreachable or otherwise unusable servers diff --git a/privops.c b/privops.c index 3c47c9df..4914dd81 100644 --- a/privops.c +++ b/privops.c @@ -76,7 +76,7 @@ typedef struct { typedef struct { char name[256]; - int service_nts; + int perform_srv; } ReqName2IPAddress; typedef struct { @@ -284,7 +284,7 @@ do_name_to_ipaddress(ReqName2IPAddress *req, PrvResponse *res) req->name[sizeof (req->name) - 1] = '\0'; res->rc = DNS_Name2IPAddress(req->name, res->data.name_to_ipaddress.addresses, - DNS_MAX_ADDRESSES, req->service_nts); + DNS_MAX_ADDRESSES, req->perform_srv); } #endif @@ -574,14 +574,14 @@ PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len) #ifdef PRIVOPS_NAME2IPADDRESS int -PRV_Name2IPAddress(const char *name, DNS_AddressLookupResult *ip_addrs, int service_nts, int max_addrs) +PRV_Name2IPAddress(const char *name, DNS_AddressLookupResult *ip_addrs, int perform_srv, int max_addrs) { PrvRequest req; PrvResponse res; int i; if (!have_helper()) - return DNS_Name2IPAddress(name, ip_addrs, service_nts, max_addrs); + return DNS_Name2IPAddress(name, ip_addrs, perform_srv, max_addrs); memset(&req, 0, sizeof (req)); req.op = OP_NAME2IPADDRESS; diff --git a/privops.h b/privops.h index c0f4223a..838e6419 100644 --- a/privops.h +++ b/privops.h @@ -55,7 +55,7 @@ int PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len); #endif #ifdef PRIVOPS_NAME2IPADDRESS -int PRV_Name2IPAddress(const char *name, DNS_AddressLookupResult *ip_addrs, int service_nts, int max_addrs); +int PRV_Name2IPAddress(const char *name, DNS_AddressLookupResult *ip_addrs, int perform_srv, int max_addrs); #else #define PRV_Name2IPAddress DNS_Name2IPAddress #endif From 0eea60d92cd0ab73c48586bb19a70e9156ce6937 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Thu, 12 Mar 2026 12:25:43 +0100 Subject: [PATCH 19/20] Fixup unit tests for ip->ipaddr rename. --- test/unit/ntp_sources.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/ntp_sources.c b/test/unit/ntp_sources.c index c904c17f..1890986f 100644 --- a/test/unit/ntp_sources.c +++ b/test/unit/ntp_sources.c @@ -80,8 +80,8 @@ update_random_address(NTP_Remote_Address *addr, int rand_bits) DNS_SockAddrLookupResult new_addr; NSR_Status status; - TST_GetRandomAddress(&new_addr.ip.ip, IPADDR_UNSPEC, rand_bits); - new_addr.ip.service_name[0] = 0; + TST_GetRandomAddress(&new_addr.ip_addr.ip, IPADDR_UNSPEC, rand_bits); + new_addr.ip_addr.service_name[0] = 0; new_addr.port = random() % 1024; status = NSR_UpdateSourceNtpAddress(addr, &new_addr); @@ -102,7 +102,7 @@ change_remote_address(NCR_Instance inst, DNS_SockAddrLookupResult *remote_addr, NTP_Remote_Address rem_addr; int update = !ntp_only && random() % 4 == 0, update_pos = random() % 2, r = 0; - rem_addr.ip_addr = remote_addr->ip.ip; + rem_addr.ip_addr = remote_addr->ip_addr.ip; rem_addr.port = remote_addr->port; TEST_CHECK(record_lock); From 38d806f94c08295e50346169de688f2839cbdfce Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Thu, 12 Mar 2026 12:26:07 +0100 Subject: [PATCH 20/20] Split off srv lookups into their own function. This makes code size a bit more manageable. --- nameserv.c | 232 ++++++++++++++++++++++++++++------------------------- 1 file changed, 123 insertions(+), 109 deletions(-) diff --git a/nameserv.c b/nameserv.c index db6b3360..5d26b0f9 100644 --- a/nameserv.c +++ b/nameserv.c @@ -72,6 +72,126 @@ DNS_SetAddressFamily(int family) address_family = family; } +#ifdef FEAT_SRV +static DNS_Status +srv_lookup(const char *name, DNS_AddressLookupResult *addrs, int max_addrs) { + int i, write_idx; + getdns_dict *extensions = NULL, *response = NULL; + getdns_list *service_addresses = NULL; + getdns_dict *service_entry = NULL; + getdns_bindata *raw_data = NULL; + char *service_domain = NULL; + char *last_name = NULL; + getdns_return_t getdns_status; + size_t returned_addresses; + size_t domain_name_len; + + if (dns_context == NULL) { + reinit(); + if (dns_context == NULL) { +#ifdef FORCE_DNSRETRY + return DNS_TryAgain; +#else + return DNS_Failure; +#endif + } + } + + service_domain = Malloc(strlen(NTS_SERVICE_NAME) + strlen(name) + 1); + strcpy(service_domain, NTS_SERVICE_NAME); + strcat(service_domain, name); + + if ((extensions = getdns_dict_create()) == NULL) + LOG_FATAL("Could not allocate memory"); + if (getdns_dict_set_int(extensions, "dnssec_return_only_secure", GETDNS_EXTENSION_TRUE)) + LOG_FATAL("Could not allocate memory"); + getdns_status = getdns_service_sync(dns_context, service_domain, extensions, &response); + free(service_domain); + getdns_dict_destroy(extensions); + if (getdns_status) { +#ifdef FORCE_DNSRETRY + return DNS_TryAgain; +#else + return DNS_Failure; +#endif + } + + if (getdns_dict_get_list(response, "/srv_addresses", &service_addresses)) + LOG_FATAL("Unrecoverable error calling getdns."); + if (getdns_list_get_length(service_addresses, &returned_addresses)) + LOG_FATAL("Unrecoverable error calling getdns."); + + write_idx = 0; + + for (i = 0; i < returned_addresses; i++) { + if (getdns_list_get_dict(service_addresses, i, &service_entry)) + LOG_FATAL("Unrecoverable error calling getdns."); + if (getdns_dict_get_bindata(service_entry, "domain_name", &raw_data)) + LOG_FATAL("Unrecoverable error calling getdns."); + if (getdns_convert_dns_name_to_fqdn(raw_data, &service_domain)) + LOG_FATAL("Unrecoverable error calling getnds."); + + /* Remove any potential trailing dot as it would interfere with certificate validation*/ + domain_name_len = strlen(service_domain); + if (service_domain[domain_name_len-1] == '.') + service_domain[domain_name_len-1] = 0; + + //*Ignore too-long domain names */ + if (strlen(service_domain) >= DNS_SERVICE_NAME_LEN) + continue; + + /* Ignore repeated names. This is needed to deal with multiple + addresses from the same service. */ + if (last_name != NULL && strcmp(last_name, service_domain) == 0) + continue; + + if (getdns_dict_get_bindata(service_entry, "address_data", &raw_data)) { + // No pre-populated address, recurse to resolve name + if (DNS_Name2IPAddress(service_domain, &addrs[write_idx], 1, 0) == DNS_Success) { + strncpy(addrs[write_idx].service_name, service_domain, DNS_SERVICE_NAME_LEN-1); + write_idx++; + free(last_name); + last_name = service_domain; + service_domain = NULL; + } + } else { + switch (raw_data->size) { + case sizeof (addrs[write_idx].ip.addr.in4): + if (address_family != IPADDR_UNSPEC && address_family != IPADDR_INET4) + continue; + /* copy first to deal with the fact that alignment of data might not be okay. */ + memcpy(&addrs[write_idx].ip.addr.in4, raw_data->data, + sizeof (addrs[write_idx].ip.addr.in4)); + addrs[write_idx].ip.addr.in4 = htonl(addrs[write_idx].ip.addr.in4); + addrs[write_idx].ip.family = IPADDR_INET4; + strncpy(addrs[write_idx].service_name, service_domain, DNS_SERVICE_NAME_LEN-1); + write_idx++; + free(last_name); + last_name = service_domain; + service_domain = NULL; + break; + +#ifdef FEAT_IPV6 + case sizeof (addrs[write_idx].ip.addr.in6): + if (address_family != IPADDR_UNSPEC && address_family != IPADDR_INET6) + continue; + memcpy(addrs[write_idx].ip.addr.in6, raw_data->data, + sizeof(addrs[write_idx].ip.addr.in6)); + addrs[write_idx].ip.family = IPADDR_INET6; + strncpy(addrs[write_idx].service_name, service_domain, DNS_SERVICE_NAME_LEN-1); + write_idx++; + free(last_name); + last_name = service_domain; + service_domain = NULL; + break; +#endif + } + } + free(service_domain); + } +} +#endif + DNS_Status DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_addrs, int use_srv_lookup) { @@ -103,115 +223,9 @@ DNS_Name2IPAddress(const char *name, DNS_AddressLookupResult *addrs, int max_add #ifdef FEAT_SRV /* First try if we can do a service record based resolution" */ if (use_srv_lookup) { - int write_idx; - getdns_dict *extensions = NULL, *response = NULL; - getdns_list *service_addresses = NULL; - getdns_dict *service_entry = NULL; - getdns_bindata *raw_data = NULL; - char *service_domain = NULL; - char *last_name = NULL; - getdns_return_t getdns_status; - size_t returned_addresses; - size_t domain_name_len; - - if (dns_context == NULL) { - reinit(); - if (dns_context == NULL) { -#ifdef FORCE_DNSRETRY - return DNS_TryAgain; -#else - return DNS_Failure; -#endif - } - } - - service_domain = Malloc(strlen(NTS_SERVICE_NAME) + strlen(name) + 1); - strcpy(service_domain, NTS_SERVICE_NAME); - strcat(service_domain, name); - - if ((extensions = getdns_dict_create()) == NULL) - LOG_FATAL("Could not allocate memory"); - if (getdns_dict_set_int(extensions, "dnssec_return_only_secure", GETDNS_EXTENSION_TRUE)) - LOG_FATAL("Could not allocate memory"); - getdns_status = getdns_service_sync(dns_context, service_domain, extensions, &response); - free(service_domain); - getdns_dict_destroy(extensions); - if (getdns_status) { -#ifdef FORCE_DNSRETRY - return DNS_TryAgain; -#else - return DNS_Failure; -#endif - } - - if (getdns_dict_get_list(response, "/srv_addresses", &service_addresses)) - LOG_FATAL("Unrecoverable error calling getdns."); - if (getdns_list_get_length(service_addresses, &returned_addresses)) - LOG_FATAL("Unrecoverable error calling getdns."); - - write_idx = 0; - - for (i = 0; i < returned_addresses; i++) { - if (getdns_list_get_dict(service_addresses, i, &service_entry)) - LOG_FATAL("Unrecoverable error calling getdns."); - if (getdns_dict_get_bindata(service_entry, "domain_name", &raw_data)) - LOG_FATAL("Unrecoverable error calling getdns."); - if (getdns_convert_dns_name_to_fqdn(raw_data, &service_domain)) - LOG_FATAL("Unrecoverable error calling getnds."); - /* Remove any potential trailing dot as it would interfere with certificate validation*/ - domain_name_len = strlen(service_domain); - if (service_domain[domain_name_len-1] == '.') - service_domain[domain_name_len-1] = 0; - //*Ignore too-long domain names */ - if (strlen(service_domain) >= DNS_SERVICE_NAME_LEN) - continue; - /* Ignore repeated names. This is needed to deal with multiple - addresses from the same service. */ - if (last_name != NULL && strcmp(last_name, service_domain) == 0) - continue; - if (getdns_dict_get_bindata(service_entry, "address_data", &raw_data)) { - // No pre-populated address, recurse to resolve name - if (DNS_Name2IPAddress(service_domain, &addrs[write_idx], 1, 0) == DNS_Success) { - strncpy(addrs[write_idx].service_name, service_domain, DNS_SERVICE_NAME_LEN-1); - write_idx++; - free(last_name); - last_name = service_domain; - service_domain = NULL; - } - } else { - switch (raw_data->size) { - case sizeof (addrs[write_idx].ip.addr.in4): - if (address_family != IPADDR_UNSPEC && address_family != IPADDR_INET4) - continue; - /* copy first to deal with the fact that alignment of data might not be okay. */ - memcpy(&addrs[write_idx].ip.addr.in4, raw_data->data, - sizeof (addrs[write_idx].ip.addr.in4)); - addrs[write_idx].ip.addr.in4 = htonl(addrs[write_idx].ip.addr.in4); - addrs[write_idx].ip.family = IPADDR_INET4; - strncpy(addrs[write_idx].service_name, service_domain, DNS_SERVICE_NAME_LEN-1); - write_idx++; - free(last_name); - last_name = service_domain; - service_domain = NULL; - break; -#ifdef FEAT_IPV6 - case sizeof (addrs[write_idx].ip.addr.in6): - if (address_family != IPADDR_UNSPEC && address_family != IPADDR_INET6) - continue; - memcpy(addrs[write_idx].ip.addr.in6, raw_data->data, - sizeof(addrs[write_idx].ip.addr.in6)); - addrs[write_idx].ip.family = IPADDR_INET6; - strncpy(addrs[write_idx].service_name, service_domain, DNS_SERVICE_NAME_LEN-1); - write_idx++; - free(last_name); - last_name = service_domain; - service_domain = NULL; - break; -#endif - } - } - free(service_domain); - } + result = srv_lookup(name, addrs, max_addrs); + if (result != DNS_Success) + return result; if (addrs[0].ip.family != IPADDR_UNSPEC) return DNS_Success;