diff options
Diffstat (limited to 'test/unit/ntp_core.c')
-rw-r--r-- | test/unit/ntp_core.c | 527 |
1 files changed, 527 insertions, 0 deletions
diff --git a/test/unit/ntp_core.c b/test/unit/ntp_core.c new file mode 100644 index 0000000..b5c148a --- /dev/null +++ b/test/unit/ntp_core.c @@ -0,0 +1,527 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2017-2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include <sysincl.h> +#include <cmdparse.h> +#include <conf.h> +#include <keys.h> +#include <ntp_ext.h> +#include <ntp_io.h> +#include <sched.h> +#include <local.h> +#include "test.h" + +#ifdef FEAT_NTP + +static struct timespec current_time; +static NTP_Packet req_buffer, res_buffer; +static int req_length, res_length; + +#define NIO_OpenServerSocket(addr) ((addr)->ip_addr.family != IPADDR_UNSPEC ? 100 : 0) +#define NIO_CloseServerSocket(fd) assert(fd == 100) +#define NIO_OpenClientSocket(addr) ((addr)->ip_addr.family != IPADDR_UNSPEC ? 101 : 0) +#define NIO_CloseClientSocket(fd) assert(fd == 101) +#define NIO_IsServerSocket(fd) (fd == 100) +#define NIO_IsServerSocketOpen() 1 +#define NIO_SendPacket(msg, to, from, len, process_tx) (memcpy(&req_buffer, msg, len), req_length = len, 1) +#define SCH_AddTimeoutByDelay(delay, handler, arg) (1 ? 102 : (handler(arg), 1)) +#define SCH_AddTimeoutInClass(delay, separation, randomness, class, handler, arg) \ + add_timeout_in_class(delay, separation, randomness, class, handler, arg) +#define SCH_RemoveTimeout(id) assert(!id || id == 102) +#define LCL_ReadRawTime(ts) (*ts = current_time) +#define LCL_ReadCookedTime(ts, err) do {double *p = err; *ts = current_time; if (p) *p = 0.0;} while (0) +#define LCL_GetSysPrecisionAsLog() (random() % 10 - 30) +#define SRC_UpdateReachability(inst, reach) +#define SRC_ResetReachability(inst) + +static SCH_TimeoutID +add_timeout_in_class(double min_delay, double separation, double randomness, + SCH_TimeoutClass class, SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg) +{ + return 102; +} + +#include <ntp_core.c> + +static void +advance_time(double x) +{ + UTI_AddDoubleToTimespec(¤t_time, x, ¤t_time); +} + +static uint32_t +get_random_key_id(void) +{ + uint32_t id; + + do { + id = random() % 8 + 2; + } while (!KEY_KeyKnown(id)); + + return id; +} + +static void +send_request(NCR_Instance inst) +{ + NTP_Local_Address local_addr; + NTP_Local_Timestamp local_ts; + uint32_t prev_tx_count; + + prev_tx_count = inst->report.total_tx_count; + + transmit_timeout(inst); + TEST_CHECK(!inst->valid_rx); + TEST_CHECK(prev_tx_count + 1 == inst->report.total_tx_count); + + advance_time(1e-5); + + if (random() % 2) { + local_addr.ip_addr.family = IPADDR_UNSPEC; + local_addr.if_index = INVALID_IF_INDEX; + local_addr.sock_fd = 101; + local_ts.ts = current_time; + local_ts.err = 0.0; + local_ts.source = NTP_TS_KERNEL; + + NCR_ProcessTxKnown(inst, &local_addr, &local_ts, &req_buffer, req_length); + } +} + +static void +process_request(NTP_Remote_Address *remote_addr) +{ + NTP_Local_Address local_addr; + NTP_Local_Timestamp local_ts; + + local_addr.ip_addr.family = IPADDR_UNSPEC; + local_addr.if_index = INVALID_IF_INDEX; + local_addr.sock_fd = 100; + local_ts.ts = current_time; + local_ts.err = 0.0; + local_ts.source = NTP_TS_KERNEL; + + res_length = 0; + NCR_ProcessRxUnknown(remote_addr, &local_addr, &local_ts, + &req_buffer, req_length); + res_length = req_length; + res_buffer = req_buffer; + + advance_time(1e-5); + + if (random() % 2) { + local_ts.ts = current_time; + NCR_ProcessTxUnknown(remote_addr, &local_addr, &local_ts, + &res_buffer, res_length); + } +} + +static void +send_response(int interleaved, int authenticated, int allow_update, int valid_ts, int valid_auth) +{ + NTP_Packet *req, *res; + uint32_t key_id = 0; + int i, auth_len = 0, ef_len, efs; + + req = &req_buffer; + res = &res_buffer; + + TEST_CHECK(req_length >= NTP_HEADER_LENGTH); + + res->lvm = NTP_LVM(LEAP_Normal, NTP_LVM_TO_VERSION(req->lvm), + NTP_LVM_TO_MODE(req->lvm) == MODE_CLIENT ? MODE_SERVER : MODE_ACTIVE); + res->stratum = 1; + res->poll = req->poll; + res->precision = -20; + res->root_delay = UTI_DoubleToNtp32(0.1); + res->root_dispersion = UTI_DoubleToNtp32(0.1); + res->reference_id = 0; + UTI_ZeroNtp64(&res->reference_ts); + res->originate_ts = interleaved ? req->receive_ts : req->transmit_ts; + + advance_time(TST_GetRandomDouble(1e-4, 1e-2)); + UTI_TimespecToNtp64(¤t_time, &res->receive_ts, NULL); + advance_time(TST_GetRandomDouble(-1e-4, 1e-3)); + UTI_TimespecToNtp64(¤t_time, &res->transmit_ts, NULL); + advance_time(TST_GetRandomDouble(1e-4, 1e-2)); + + if (!valid_ts) { + switch (random() % (allow_update ? 4 : 5)) { + case 0: + res->originate_ts.hi = random(); + break; + case 1: + res->originate_ts.lo = random(); + break; + case 2: + UTI_ZeroNtp64(&res->originate_ts); + break; + case 3: + UTI_ZeroNtp64(&res->receive_ts); + break; + case 4: + UTI_ZeroNtp64(&res->transmit_ts); + break; + default: + assert(0); + } + } + + res_length = NTP_HEADER_LENGTH; + + if (NTP_LVM_TO_VERSION(res->lvm) == 4 && random() % 2) { + unsigned char buf[128]; + + memset(buf, 0, sizeof (buf)); + efs = random() % 5; + + for (i = 0; i < efs; i++) { + ef_len = (i + 1 == efs ? NTP_MAX_V4_MAC_LENGTH + 4 : NTP_MIN_EF_LENGTH) + + 4 * (random() % 10); + TEST_CHECK(NEF_SetField((unsigned char *)res, sizeof (*res), res_length, 0, + buf, ef_len - 4, &ef_len)); + res_length += ef_len; + } + } + + if (authenticated) { + key_id = ntohl(*(uint32_t *)req->extensions); + if (key_id == 0) + key_id = get_random_key_id(); + auth_len = KEY_GetAuthLength(key_id); + assert(auth_len); + if (NTP_LVM_TO_VERSION(res->lvm) == 4) + auth_len = MIN(auth_len, NTP_MAX_V4_MAC_LENGTH - 4); + + if (KEY_GenerateAuth(key_id, res, res_length, + (unsigned char *)res + res_length + 4, auth_len) != auth_len) + assert(0); + res_length += 4 + auth_len; + } + + if (!valid_auth && authenticated) { + assert(auth_len); + + switch (random() % 5) { + case 0: + key_id++; + break; + case 1: + key_id ^= 1; + if (KEY_GenerateAuth(key_id, res, res_length - auth_len - 4, + (unsigned char *)res + res_length - auth_len, auth_len) != auth_len) + assert(0); + break; + case 2: + ((unsigned char *)res)[res_length - auth_len + random() % auth_len]++; + break; + case 3: + res_length -= 4 + auth_len; + auth_len = 4 * (random() % (auth_len / 4)); + res_length += 4 + auth_len; + break; + case 4: + if (NTP_LVM_TO_VERSION(res->lvm) == 4 && random() % 2 && + KEY_GetAuthLength(key_id) > NTP_MAX_V4_MAC_LENGTH - 4) { + res_length -= 4 + auth_len; + auth_len += 4 + 4 * (random() % + ((KEY_GetAuthLength(key_id) - NTP_MAX_V4_MAC_LENGTH - 4) / 4)); + if (KEY_GenerateAuth(key_id, res, res_length, + (unsigned char *)res + res_length + 4, auth_len) != auth_len) + assert(0); + res_length += 4 + auth_len; + } else { + memset((unsigned char *)res + res_length, 0, 4); + auth_len += 4; + res_length += 4; + } + break; + default: + assert(0); + } + } + + assert(res_length <= sizeof (*res)); + assert(res_length >= NTP_HEADER_LENGTH + auth_len); + + if (authenticated) + *(uint32_t *)((unsigned char *)res + res_length - auth_len - 4) = htonl(key_id); +} + +static void +proc_response(NCR_Instance inst, int good, int valid, int updated_sync, int updated_init) +{ + NTP_Local_Address local_addr; + NTP_Local_Timestamp local_ts; + NTP_Packet *res; + uint32_t prev_rx_count, prev_valid_count; + struct timespec prev_rx_ts, prev_init_rx_ts; + int ret; + + res = &res_buffer; + + local_addr.ip_addr.family = IPADDR_UNSPEC; + local_addr.if_index = INVALID_IF_INDEX; + local_addr.sock_fd = NTP_LVM_TO_MODE(res->lvm) != MODE_SERVER ? 100 : 101; + local_ts.ts = current_time; + local_ts.err = 0.0; + local_ts.source = NTP_TS_KERNEL; + + prev_rx_count = inst->report.total_rx_count; + prev_valid_count = inst->report.total_valid_count; + prev_rx_ts = inst->local_rx.ts; + prev_init_rx_ts = inst->init_local_rx.ts; + + ret = NCR_ProcessRxKnown(inst, &local_addr, &local_ts, res, res_length); + + if (good > 0) + TEST_CHECK(ret); + else if (!good) + TEST_CHECK(!ret); + + TEST_CHECK(prev_rx_count + 1 == inst->report.total_rx_count); + + if (valid) + TEST_CHECK(prev_valid_count + 1 == inst->report.total_valid_count); + else + TEST_CHECK(prev_valid_count == inst->report.total_valid_count); + + if (updated_sync) + TEST_CHECK(UTI_CompareTimespecs(&inst->local_rx.ts, &prev_rx_ts)); + else + TEST_CHECK(!UTI_CompareTimespecs(&inst->local_rx.ts, &prev_rx_ts)); + + if (updated_init > 0) + TEST_CHECK(UTI_CompareTimespecs(&inst->init_local_rx.ts, &prev_init_rx_ts)); + else if (!updated_init) + TEST_CHECK(!UTI_CompareTimespecs(&inst->init_local_rx.ts, &prev_init_rx_ts)); + + if (valid) { + TEST_CHECK(UTI_IsZeroTimespec(&inst->init_local_rx.ts)); + TEST_CHECK(UTI_IsZeroNtp64(&inst->init_remote_ntp_tx)); + } +} + +static void +process_replay(NCR_Instance inst, NTP_Packet *packet_queue, + int queue_length, int updated_init) +{ + do { + res_buffer = packet_queue[random() % queue_length]; + } while (!UTI_CompareNtp64(&res_buffer.transmit_ts, &inst->remote_ntp_tx)); + proc_response(inst, 0, 0, 0, updated_init); + advance_time(1e-6); +} + +#define PACKET_QUEUE_LENGTH 10 + +void +test_unit(void) +{ + char source_line[] = "127.0.0.1 maxdelaydevratio 1e6"; + char conf[][100] = { + "allow", + "port 0", + "local", + "keyfile ntp_core.keys" + }; + int i, j, k, interleaved, authenticated, valid, updated, has_updated; + CPS_NTP_Source source; + NTP_Remote_Address remote_addr; + NCR_Instance inst1, inst2; + NTP_Packet packet_queue[PACKET_QUEUE_LENGTH]; + + CNF_Initialise(0, 0); + for (i = 0; i < sizeof conf / sizeof conf[0]; i++) + CNF_ParseLine(NULL, i + 1, conf[i]); + + LCL_Initialise(); + TST_RegisterDummyDrivers(); + SCH_Initialise(); + SRC_Initialise(); + NIO_Initialise(); + NCR_Initialise(); + REF_Initialise(); + + TST_SuspendLogging(); + KEY_Initialise(); + TST_ResumeLogging(); + + CNF_SetupAccessRestrictions(); + + CPS_ParseNTPSourceAdd(source_line, &source); + + for (i = 0; i < 1000; i++) { + source.params.interleaved = random() % 2; + source.params.authkey = random() % 2 ? get_random_key_id() : INACTIVE_AUTHKEY; + source.params.version = random() % 4 + 1; + + UTI_ZeroTimespec(¤t_time); + advance_time(TST_GetRandomDouble(1.0, 1e9)); + + TST_GetRandomAddress(&remote_addr.ip_addr, IPADDR_UNSPEC, -1); + remote_addr.port = 123; + + inst1 = NCR_CreateInstance(&remote_addr, random() % 2 ? NTP_SERVER : NTP_PEER, + &source.params, NULL); + NCR_StartInstance(inst1); + has_updated = 0; + + for (j = 0; j < 50; j++) { + DEBUG_LOG("client/peer test iteration %d/%d", i, j); + + interleaved = random() % 2 && (inst1->mode != MODE_CLIENT || + inst1->tx_count < MAX_CLIENT_INTERLEAVED_TX); + authenticated = random() % 2; + valid = (!interleaved || (source.params.interleaved && has_updated)) && + ((source.params.authkey == INACTIVE_AUTHKEY) == !authenticated); + updated = (valid || inst1->mode == MODE_ACTIVE) && + ((source.params.authkey == INACTIVE_AUTHKEY) == !authenticated); + has_updated = has_updated || updated; + if (inst1->mode == MODE_CLIENT) + updated = 0; + + DEBUG_LOG("authkey=%d version=%d interleaved=%d authenticated=%d valid=%d updated=%d has_updated=%d", + (int)source.params.authkey, source.params.version, + interleaved, authenticated, valid, updated, has_updated); + + send_request(inst1); + + send_response(interleaved, authenticated, 1, 0, 1); + DEBUG_LOG("response 1"); + proc_response(inst1, 0, 0, 0, updated); + + if (source.params.authkey) { + send_response(interleaved, authenticated, 1, 1, 0); + DEBUG_LOG("response 2"); + proc_response(inst1, 0, 0, 0, 0); + } + + send_response(interleaved, authenticated, 1, 1, 1); + DEBUG_LOG("response 3"); + proc_response(inst1, -1, valid, valid, updated); + DEBUG_LOG("response 4"); + proc_response(inst1, 0, 0, 0, 0); + + advance_time(-1.0); + + send_response(interleaved, authenticated, 1, 1, 1); + DEBUG_LOG("response 5"); + proc_response(inst1, 0, 0, 0, updated && valid); + + advance_time(1.0); + + send_response(interleaved, authenticated, 1, 1, 1); + DEBUG_LOG("response 6"); + proc_response(inst1, 0, 0, valid && updated, updated); + } + + NCR_DestroyInstance(inst1); + + inst1 = NCR_CreateInstance(&remote_addr, random() % 2 ? NTP_SERVER : NTP_PEER, + &source.params, NULL); + NCR_StartInstance(inst1); + + for (j = 0; j < 20; j++) { + DEBUG_LOG("server test iteration %d/%d", i, j); + + send_request(inst1); + process_request(&remote_addr); + proc_response(inst1, 1, 1, 1, 0); + advance_time(1 << inst1->local_poll); + } + + NCR_DestroyInstance(inst1); + + inst1 = NCR_CreateInstance(&remote_addr, NTP_PEER, &source.params, NULL); + NCR_StartInstance(inst1); + inst2 = NCR_CreateInstance(&remote_addr, NTP_PEER, &source.params, NULL); + NCR_StartInstance(inst2); + + res_length = req_length = 0; + + for (j = 0; j < 20; j++) { + DEBUG_LOG("peer replay test iteration %d/%d", i, j); + + send_request(inst1); + res_buffer = req_buffer; + assert(!res_length || res_length == req_length); + res_length = req_length; + + TEST_CHECK(inst1->valid_timestamps == (j > 0)); + + DEBUG_LOG("response 1->2"); + proc_response(inst2, j > source.params.interleaved, j > 0, j > 0, 1); + + packet_queue[(j * 2) % PACKET_QUEUE_LENGTH] = res_buffer; + + for (k = 0; k < j % 4 + 1; k++) { + DEBUG_LOG("replay ?->1 %d", k); + process_replay(inst1, packet_queue, MIN(j * 2 + 1, PACKET_QUEUE_LENGTH), k ? -1 : 1); + DEBUG_LOG("replay ?->2 %d", k); + process_replay(inst2, packet_queue, MIN(j * 2 + 1, PACKET_QUEUE_LENGTH), -1); + } + + advance_time(1 << (source.params.minpoll - 1)); + + send_request(inst2); + res_buffer = req_buffer; + assert(res_length == req_length); + + TEST_CHECK(inst2->valid_timestamps == (j > 0)); + + DEBUG_LOG("response 2->1"); + proc_response(inst1, 1, 1, 1, 1); + + packet_queue[(j * 2 + 1) % PACKET_QUEUE_LENGTH] = res_buffer; + + for (k = 0; k < j % 4 + 1; k++) { + DEBUG_LOG("replay ?->1 %d", k); + process_replay(inst1, packet_queue, MIN(j * 2 + 2, PACKET_QUEUE_LENGTH), k ? -1 : 1); + DEBUG_LOG("replay ?->2 %d", k); + process_replay(inst2, packet_queue, MIN(j * 2 + 2, PACKET_QUEUE_LENGTH), -1); + } + + advance_time(1 << (source.params.minpoll - 1)); + } + + NCR_DestroyInstance(inst1); + NCR_DestroyInstance(inst2); + } + + KEY_Finalise(); + REF_Finalise(); + NCR_Finalise(); + NIO_Finalise(); + SRC_Finalise(); + SCH_Finalise(); + LCL_Finalise(); + CNF_Finalise(); + HSH_Finalise(); +} + +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif |