diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:09:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:09:41 +0000 |
commit | 3271d1ac389d2ec93db9c5b9ce0991ce478476cf (patch) | |
tree | 35ff7d180e1ccc061f28535d7435b5ba1789e734 /ntp_core.c | |
parent | Initial commit. (diff) | |
download | chrony-8af3d017d76eb11727ef3ef14480daf2b126a6ec.tar.xz chrony-8af3d017d76eb11727ef3ef14480daf2b126a6ec.zip |
Adding upstream version 4.3.upstream/4.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ntp_core.c')
-rw-r--r-- | ntp_core.c | 2999 |
1 files changed, 2999 insertions, 0 deletions
diff --git a/ntp_core.c b/ntp_core.c new file mode 100644 index 0000000..63c5a3a --- /dev/null +++ b/ntp_core.c @@ -0,0 +1,2999 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 1997-2003 + * Copyright (C) Miroslav Lichvar 2009-2022 + * + * 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. + * + ********************************************************************** + + ======================================================================= + + Core NTP protocol engine + */ + +#include "config.h" + +#include "sysincl.h" + +#include "array.h" +#include "ntp_auth.h" +#include "ntp_core.h" +#include "ntp_ext.h" +#include "ntp_io.h" +#include "memory.h" +#include "quantiles.h" +#include "sched.h" +#include "reference.h" +#include "local.h" +#include "samplefilt.h" +#include "smooth.h" +#include "sources.h" +#include "util.h" +#include "conf.h" +#include "logging.h" +#include "addrfilt.h" +#include "clientlog.h" + +/* ================================================== */ + +static LOG_FileID logfileid; +static int log_raw_measurements; + +/* ================================================== */ +/* Enumeration used for remembering the operating mode of one of the + sources */ + +typedef enum { + MD_OFFLINE, /* No sampling at all */ + MD_ONLINE, /* Normal sampling based on sampling interval */ + MD_BURST_WAS_OFFLINE, /* Burst sampling, return to offline afterwards */ + MD_BURST_WAS_ONLINE, /* Burst sampling, return to online afterwards */ +} OperatingMode; + +/* ================================================== */ +/* Structure used for holding a single peer/server's + protocol machine */ + +struct NCR_Instance_Record { + NTP_Remote_Address remote_addr; /* Needed for routing transmit packets */ + NTP_Local_Address local_addr; /* Local address/socket used to send packets */ + NTP_Mode mode; /* The source's NTP mode + (client/server or symmetric active peer) */ + int interleaved; /* Boolean enabling interleaved NTP mode */ + OperatingMode opmode; /* Whether we are sampling this source + or not and in what way */ + SCH_TimeoutID rx_timeout_id; /* Timeout ID for latest received response */ + SCH_TimeoutID tx_timeout_id; /* Timeout ID for next transmission */ + int tx_suspended; /* Boolean indicating we can't transmit yet */ + + int auto_iburst; /* If 1, initiate a burst when going online */ + int auto_burst; /* If 1, initiate a burst on each poll */ + int auto_offline; /* If 1, automatically go offline when requests + cannot be sent */ + + int local_poll; /* Log2 of polling interval at our end */ + int remote_poll; /* Log2 of server/peer's polling interval (recovered + from received packets) */ + int remote_stratum; /* Stratum of the server/peer (recovered from + received packets) */ + double remote_root_delay; /* Root delay from last valid packet */ + double remote_root_dispersion;/* Root dispersion from last valid packet */ + + int presend_minpoll; /* If the current polling interval is + at least this, an extra client packet + will be send some time before normal + transmit. This ensures that both + us and the server/peer have an ARP + entry for each other ready, which + means our measurement is not + botched by an ARP round-trip on one + side or the other. */ + + int presend_done; /* The presend packet has been sent */ + + int minpoll; /* Log2 of minimum defined polling interval */ + int maxpoll; /* Log2 of maximum defined polling interval */ + + int min_stratum; /* Increase stratum in received packets to the + minimum */ + + int copy; /* Boolean suppressing own refid and stratum */ + + int poll_target; /* Target number of sourcestats samples */ + + int version; /* Version set in packets for server/peer */ + + double poll_score; /* Score of current local poll */ + + double max_delay; /* Maximum round-trip delay to the + peer that we can tolerate and still + use the sample for generating + statistics from */ + + double max_delay_ratio; /* Largest ratio of delay / + min_delay_in_register that we can + tolerate. */ + + double max_delay_dev_ratio; /* Maximum ratio of increase in delay / stddev */ + + double offset_correction; /* Correction applied to measured offset + (e.g. for asymmetry in network delay) */ + + int ext_field_flags; /* Enabled extension fields */ + + uint32_t remote_mono_epoch; /* ID of the source's monotonic scale */ + double mono_doffset; /* Accumulated offset between source's + real-time and monotonic scales */ + + NAU_Instance auth; /* Authentication */ + + /* Count of transmitted packets since last valid response */ + unsigned int tx_count; + + /* Flag indicating a valid response was received since last request */ + int valid_rx; + + /* Flag indicating the timestamps below are from a valid packet and may + be used for synchronisation */ + int valid_timestamps; + + /* Receive and transmit timestamps from the last valid response */ + NTP_int64 remote_ntp_monorx; + NTP_int64 remote_ntp_rx; + NTP_int64 remote_ntp_tx; + + /* Local timestamp when the last valid response was received from the + source. We have to be prepared to tinker with this if the local + clock has its frequency adjusted before we repond. The value we + store here is what our own local time was when the same arrived. + Before replying, we have to correct this to fit with the + parameters for the current reference. (It must be stored + relative to local time to permit frequency and offset adjustments + to be made when we trim the local clock). */ + NTP_int64 local_ntp_rx; + NTP_Local_Timestamp local_rx; + + /* Local timestamp when we last transmitted a packet to the source. + We store two versions. The first is in NTP format, and is used + to validate the next received packet from the source. + Additionally, this is corrected to bring it into line with the + current reference. The second is in timespec format, and is kept + relative to the local clock. We modify this in accordance with + local clock frequency/offset changes, and use this for computing + statistics about the source when a return packet arrives. */ + NTP_int64 local_ntp_tx; + NTP_Local_Timestamp local_tx; + + /* Previous values of some variables needed in interleaved mode */ + NTP_Local_Timestamp prev_local_tx; + int prev_local_poll; + unsigned int prev_tx_count; + + /* Flag indicating the two timestamps below were updated since the + last transmission */ + int updated_init_timestamps; + + /* Timestamps used for (re)starting the symmetric protocol, when we + need to respond to a packet which is not a valid response */ + NTP_int64 init_remote_ntp_tx; + NTP_Local_Timestamp init_local_rx; + + /* The instance record in the main source management module. This + performs the statistical analysis on the samples we generate */ + + SRC_Instance source; + + /* Optional long-term quantile estimate of peer delay */ + QNT_Instance delay_quant; + + /* Optional median filter for NTP measurements */ + SPF_Instance filter; + int filter_count; + + int burst_good_samples_to_go; + int burst_total_samples_to_go; + + /* Report from last valid response */ + RPT_NTPReport report; +}; + +typedef struct { + NTP_Remote_Address addr; + NTP_Local_Address local_addr; + NAU_Instance auth; + int interval; +} BroadcastDestination; + +/* Array of BroadcastDestination */ +static ARR_Instance broadcasts; + +/* ================================================== */ +/* Initial delay period before first packet is transmitted (in seconds) */ +#define INITIAL_DELAY 0.2 + +/* Spacing required between samples for any two servers/peers (to + minimise risk of network collisions) (in seconds) */ +#define MIN_SAMPLING_SEPARATION 0.002 +#define MAX_SAMPLING_SEPARATION 0.2 + +/* Randomness added to spacing between samples for one server/peer */ +#define SAMPLING_RANDOMNESS 0.02 + +/* Adjustment of the peer polling interval */ +#define PEER_SAMPLING_ADJ 1.1 + +/* Maximum spacing between samples in the burst mode as an absolute + value and ratio to the normal polling interval */ +#define MAX_BURST_INTERVAL 2.0 +#define MAX_BURST_POLL_RATIO 0.25 + +/* Number of samples in initial burst */ +#define IBURST_GOOD_SAMPLES 4 +#define IBURST_TOTAL_SAMPLES SOURCE_REACH_BITS + +/* Number of samples in automatic burst */ +#define BURST_GOOD_SAMPLES 1 +#define MAX_BURST_TOTAL_SAMPLES 4 + +/* Time to wait after sending packet to 'warm up' link */ +#define WARM_UP_DELAY 2.0 + +/* Compatible NTP protocol versions */ +#define NTP_MAX_COMPAT_VERSION NTP_VERSION +#define NTP_MIN_COMPAT_VERSION 1 + +/* Maximum allowed dispersion - as defined in RFC 5905 (16 seconds) */ +#define NTP_MAX_DISPERSION 16.0 + +/* Maximum allowed time for server to process client packet */ +#define MAX_SERVER_INTERVAL 4.0 + +/* Maximum acceptable delay in transmission for timestamp correction */ +#define MAX_TX_DELAY 1.0 + +/* Maximum allowed values of maxdelay parameters */ +#define MAX_MAXDELAY 1.0e3 +#define MAX_MAXDELAYRATIO 1.0e6 +#define MAX_MAXDELAYDEVRATIO 1.0e6 + +/* Parameters for the peer delay quantile */ +#define DELAY_QUANT_Q 100 +#define DELAY_QUANT_REPEAT 7 + +/* Minimum and maximum allowed poll interval */ +#define MIN_POLL -7 +#define MAX_POLL 24 + +/* Enable sub-second polling intervals only when the peer delay is not + longer than 10 milliseconds to restrict them to local networks */ +#define MIN_NONLAN_POLL 0 +#define MAX_LAN_PEER_DELAY 0.01 + +/* Kiss-o'-Death codes */ +#define KOD_RATE 0x52415445UL /* RATE */ + +/* Maximum poll interval set by KoD RATE */ +#define MAX_KOD_RATE_POLL SRC_DEFAULT_MAXPOLL + +/* Maximum number of missed responses to accept samples using old timestamps + in the interleaved client/server mode */ +#define MAX_CLIENT_INTERLEAVED_TX 4 + +/* Maximum ratio of local intervals in the timestamp selection of the + interleaved mode to prefer a sample using previous timestamps */ +#define MAX_INTERLEAVED_L2L_RATIO 0.1 + +/* Maximum acceptable change in server mono<->real offset */ +#define MAX_MONO_DOFFSET 16.0 + +/* Invalid socket, different from the one in ntp_io.c */ +#define INVALID_SOCK_FD -2 + +/* ================================================== */ + +/* Server IPv4/IPv6 sockets */ +static int server_sock_fd4; +static int server_sock_fd6; + +static ADF_AuthTable access_auth_table; + +/* Current offset between monotonic and cooked time, and its epoch ID + which is reset on clock steps */ +static double server_mono_offset; +static uint32_t server_mono_epoch; + +/* Characters for printing synchronisation status and timestamping source */ +static const char leap_chars[4] = {'N', '+', '-', '?'}; +static const char tss_chars[3] = {'D', 'K', 'H'}; + +/* ================================================== */ +/* Forward prototypes */ + +static void transmit_timeout(void *arg); +static double get_transmit_delay(NCR_Instance inst, int on_tx, double last_tx); +static double get_separation(int poll); +static int parse_packet(NTP_Packet *packet, int length, NTP_PacketInfo *info); +static void process_sample(NCR_Instance inst, NTP_Sample *sample); +static void set_connectivity(NCR_Instance inst, SRC_Connectivity connectivity); + +/* ================================================== */ + +static void +do_size_checks(void) +{ + /* Assertions to check the sizes of certain data types + and the positions of certain record fields */ + + /* Check that certain invariants are true */ + assert(sizeof(NTP_int32) == 4); + assert(sizeof(NTP_int64) == 8); + + /* Check offsets of all fields in the NTP packet format */ + assert(offsetof(NTP_Packet, lvm) == 0); + assert(offsetof(NTP_Packet, stratum) == 1); + assert(offsetof(NTP_Packet, poll) == 2); + assert(offsetof(NTP_Packet, precision) == 3); + assert(offsetof(NTP_Packet, root_delay) == 4); + assert(offsetof(NTP_Packet, root_dispersion) == 8); + assert(offsetof(NTP_Packet, reference_id) == 12); + assert(offsetof(NTP_Packet, reference_ts) == 16); + assert(offsetof(NTP_Packet, originate_ts) == 24); + assert(offsetof(NTP_Packet, receive_ts) == 32); + assert(offsetof(NTP_Packet, transmit_ts) == 40); +} + +/* ================================================== */ + +static void +do_time_checks(void) +{ + struct timespec now; + time_t warning_advance = 3600 * 24 * 365 * 10; /* 10 years */ + +#ifdef HAVE_LONG_TIME_T + /* Check that time before NTP_ERA_SPLIT underflows correctly */ + + struct timespec ts1 = {NTP_ERA_SPLIT, 1}, ts2 = {NTP_ERA_SPLIT - 1, 1}; + NTP_int64 nts1, nts2; + int r; + + UTI_TimespecToNtp64(&ts1, &nts1, NULL); + UTI_TimespecToNtp64(&ts2, &nts2, NULL); + UTI_Ntp64ToTimespec(&nts1, &ts1); + UTI_Ntp64ToTimespec(&nts2, &ts2); + + r = ts1.tv_sec == NTP_ERA_SPLIT && + ts1.tv_sec + (1ULL << 32) - 1 == ts2.tv_sec; + + assert(r); + + LCL_ReadRawTime(&now); + if (ts2.tv_sec - now.tv_sec < warning_advance) + LOG(LOGS_WARN, "Assumed NTP time ends at %s!", UTI_TimeToLogForm(ts2.tv_sec)); +#else + LCL_ReadRawTime(&now); + if (now.tv_sec > 0x7fffffff - warning_advance) + LOG(LOGS_WARN, "System time ends at %s!", UTI_TimeToLogForm(0x7fffffff)); +#endif +} + +/* ================================================== */ + +static void +zero_local_timestamp(NTP_Local_Timestamp *ts) +{ + UTI_ZeroTimespec(&ts->ts); + ts->err = 0.0; + ts->source = NTP_TS_DAEMON; +} + +/* ================================================== */ + +static void +handle_slew(struct timespec *raw, struct timespec *cooked, double dfreq, + double doffset, LCL_ChangeType change_type, void *anything) +{ + if (change_type == LCL_ChangeAdjust) { + server_mono_offset += doffset; + } else { + UTI_GetRandomBytes(&server_mono_epoch, sizeof (server_mono_epoch)); + server_mono_offset = 0.0; + } +} + +/* ================================================== */ + +void +NCR_Initialise(void) +{ + do_size_checks(); + do_time_checks(); + + logfileid = CNF_GetLogMeasurements(&log_raw_measurements) ? LOG_FileOpen("measurements", + " Date (UTC) Time IP Address L St 123 567 ABCD LP RP Score Offset Peer del. Peer disp. Root del. Root disp. Refid MTxRx") + : -1; + + access_auth_table = ADF_CreateTable(); + broadcasts = ARR_CreateInstance(sizeof (BroadcastDestination)); + + /* Server socket will be opened when access is allowed */ + server_sock_fd4 = INVALID_SOCK_FD; + server_sock_fd6 = INVALID_SOCK_FD; + + LCL_AddParameterChangeHandler(handle_slew, NULL); + handle_slew(NULL, NULL, 0.0, 0.0, LCL_ChangeUnknownStep, NULL); +} + +/* ================================================== */ + +void +NCR_Finalise(void) +{ + unsigned int i; + + LCL_RemoveParameterChangeHandler(handle_slew, NULL); + + if (server_sock_fd4 != INVALID_SOCK_FD) + NIO_CloseServerSocket(server_sock_fd4); + if (server_sock_fd6 != INVALID_SOCK_FD) + NIO_CloseServerSocket(server_sock_fd6); + + for (i = 0; i < ARR_GetSize(broadcasts); i++) { + NIO_CloseServerSocket(((BroadcastDestination *)ARR_GetElement(broadcasts, i))->local_addr.sock_fd); + NAU_DestroyInstance(((BroadcastDestination *)ARR_GetElement(broadcasts, i))->auth); + } + + ARR_DestroyInstance(broadcasts); + ADF_DestroyTable(access_auth_table); +} + +/* ================================================== */ + +static void +restart_timeout(NCR_Instance inst, double delay) +{ + /* Check if we can transmit */ + if (inst->tx_suspended) { + assert(!inst->tx_timeout_id); + return; + } + + /* Stop both rx and tx timers if running */ + SCH_RemoveTimeout(inst->rx_timeout_id); + inst->rx_timeout_id = 0; + SCH_RemoveTimeout(inst->tx_timeout_id); + + /* Start new timer for transmission */ + inst->tx_timeout_id = SCH_AddTimeoutInClass(delay, get_separation(inst->local_poll), + SAMPLING_RANDOMNESS, + inst->mode == MODE_CLIENT ? + SCH_NtpClientClass : SCH_NtpPeerClass, + transmit_timeout, (void *)inst); +} + +/* ================================================== */ + +static void +start_initial_timeout(NCR_Instance inst) +{ + double delay, last_tx; + struct timespec now; + + if (!inst->tx_timeout_id) { + /* This will be the first transmission after mode change */ + + /* Mark source active */ + SRC_SetActive(inst->source); + } + + /* In case the offline period was too short, adjust the delay to keep + the interval between packets at least as long as the current polling + interval */ + if (!UTI_IsZeroTimespec(&inst->local_tx.ts)) { + SCH_GetLastEventTime(&now, NULL, NULL); + last_tx = UTI_DiffTimespecsToDouble(&now, &inst->local_tx.ts); + if (last_tx < 0.0) + last_tx = 0.0; + delay = get_transmit_delay(inst, 0, 0.0) - last_tx; + } else { + delay = 0.0; + } + + if (delay < INITIAL_DELAY) + delay = INITIAL_DELAY; + + restart_timeout(inst, delay); +} + +/* ================================================== */ + +static void +close_client_socket(NCR_Instance inst) +{ + if (inst->mode == MODE_CLIENT && inst->local_addr.sock_fd != INVALID_SOCK_FD) { + NIO_CloseClientSocket(inst->local_addr.sock_fd); + inst->local_addr.sock_fd = INVALID_SOCK_FD; + } + + SCH_RemoveTimeout(inst->rx_timeout_id); + inst->rx_timeout_id = 0; +} + +/* ================================================== */ + +static void +take_offline(NCR_Instance inst) +{ + inst->opmode = MD_OFFLINE; + + SCH_RemoveTimeout(inst->tx_timeout_id); + inst->tx_timeout_id = 0; + + /* Mark source unreachable */ + SRC_ResetReachability(inst->source); + + /* And inactive */ + SRC_UnsetActive(inst->source); + + close_client_socket(inst); + + NCR_ResetInstance(inst); +} + +/* ================================================== */ + +static void +reset_report(NCR_Instance inst) +{ + memset(&inst->report, 0, sizeof (inst->report)); + inst->report.remote_addr = inst->remote_addr.ip_addr; + inst->report.remote_port = inst->remote_addr.port; +} + +/* ================================================== */ + +NCR_Instance +NCR_CreateInstance(NTP_Remote_Address *remote_addr, NTP_Source_Type type, + SourceParameters *params, const char *name) +{ + NCR_Instance result; + + result = MallocNew(struct NCR_Instance_Record); + + result->remote_addr = *remote_addr; + result->local_addr.ip_addr.family = IPADDR_UNSPEC; + result->local_addr.if_index = INVALID_IF_INDEX; + + switch (type) { + case NTP_SERVER: + /* Client socket will be obtained when sending request */ + result->local_addr.sock_fd = INVALID_SOCK_FD; + result->mode = MODE_CLIENT; + break; + case NTP_PEER: + result->local_addr.sock_fd = NIO_OpenServerSocket(remote_addr); + result->mode = MODE_ACTIVE; + break; + default: + assert(0); + } + + result->interleaved = params->interleaved; + + result->minpoll = params->minpoll; + if (result->minpoll < MIN_POLL) + result->minpoll = SRC_DEFAULT_MINPOLL; + else if (result->minpoll > MAX_POLL) + result->minpoll = MAX_POLL; + + result->maxpoll = params->maxpoll; + if (result->maxpoll < MIN_POLL) + result->maxpoll = SRC_DEFAULT_MAXPOLL; + else if (result->maxpoll > MAX_POLL) + result->maxpoll = MAX_POLL; + if (result->maxpoll < result->minpoll) + result->maxpoll = result->minpoll; + + result->min_stratum = params->min_stratum; + if (result->min_stratum >= NTP_MAX_STRATUM) + result->min_stratum = NTP_MAX_STRATUM - 1; + + /* Presend doesn't work in symmetric mode */ + result->presend_minpoll = params->presend_minpoll; + if (result->presend_minpoll <= MAX_POLL && result->mode != MODE_CLIENT) + result->presend_minpoll = MAX_POLL + 1; + + result->max_delay = CLAMP(0.0, params->max_delay, MAX_MAXDELAY); + result->max_delay_ratio = CLAMP(0.0, params->max_delay_ratio, MAX_MAXDELAYRATIO); + result->max_delay_dev_ratio = CLAMP(0.0, params->max_delay_dev_ratio, MAX_MAXDELAYDEVRATIO); + result->offset_correction = params->offset; + result->auto_iburst = params->iburst; + result->auto_burst = params->burst; + result->auto_offline = params->auto_offline; + result->copy = params->copy && result->mode == MODE_CLIENT; + result->poll_target = params->poll_target; + result->ext_field_flags = params->ext_fields; + + if (params->nts) { + IPSockAddr nts_address; + + if (result->mode == MODE_ACTIVE) + LOG(LOGS_WARN, "NTS not supported with peers"); + + nts_address.ip_addr = remote_addr->ip_addr; + nts_address.port = params->nts_port; + + result->auth = NAU_CreateNtsInstance(&nts_address, name, params->cert_set, + result->remote_addr.port); + } else if (params->authkey != INACTIVE_AUTHKEY) { + result->auth = NAU_CreateSymmetricInstance(params->authkey); + } else { + result->auth = NAU_CreateNoneInstance(); + } + + if (result->ext_field_flags || result->interleaved) + result->version = NTP_VERSION; + else + result->version = NAU_GetSuggestedNtpVersion(result->auth); + + if (params->version) + result->version = CLAMP(NTP_MIN_COMPAT_VERSION, params->version, NTP_VERSION); + + /* Create a source instance for this NTP source */ + result->source = SRC_CreateNewInstance(UTI_IPToRefid(&remote_addr->ip_addr), + SRC_NTP, NAU_IsAuthEnabled(result->auth), + params->sel_options, &result->remote_addr.ip_addr, + params->min_samples, params->max_samples, + params->min_delay, params->asymmetry); + + if (params->max_delay_quant > 0.0) { + int k = round(CLAMP(0.05, params->max_delay_quant, 0.95) * DELAY_QUANT_Q); + result->delay_quant = QNT_CreateInstance(k, k, DELAY_QUANT_Q, DELAY_QUANT_REPEAT, + LCL_GetSysPrecisionAsQuantum() / 2.0); + } else { + result->delay_quant = NULL; + } + + if (params->filter_length >= 1) + result->filter = SPF_CreateInstance(1, params->filter_length, NTP_MAX_DISPERSION, 0.0); + else + result->filter = NULL; + + result->rx_timeout_id = 0; + result->tx_timeout_id = 0; + result->tx_suspended = 1; + result->opmode = MD_OFFLINE; + result->local_poll = MAX(result->minpoll, MIN_NONLAN_POLL); + result->poll_score = 0.0; + zero_local_timestamp(&result->local_tx); + result->burst_good_samples_to_go = 0; + result->burst_total_samples_to_go = 0; + + NCR_ResetInstance(result); + + set_connectivity(result, params->connectivity); + + reset_report(result); + + return result; +} + +/* ================================================== */ + +/* Destroy an instance */ +void +NCR_DestroyInstance(NCR_Instance instance) +{ + if (instance->opmode != MD_OFFLINE) + take_offline(instance); + + if (instance->mode == MODE_ACTIVE) + NIO_CloseServerSocket(instance->local_addr.sock_fd); + + if (instance->delay_quant) + QNT_DestroyInstance(instance->delay_quant); + if (instance->filter) + SPF_DestroyInstance(instance->filter); + + NAU_DestroyInstance(instance->auth); + + /* This will destroy the source instance inside the + structure, which will cause reselection if this was the + synchronising source etc. */ + SRC_DestroyInstance(instance->source); + + /* Free the data structure */ + Free(instance); +} + +/* ================================================== */ + +void +NCR_StartInstance(NCR_Instance instance) +{ + instance->tx_suspended = 0; + if (instance->opmode != MD_OFFLINE) + start_initial_timeout(instance); +} + +/* ================================================== */ + +void +NCR_ResetInstance(NCR_Instance instance) +{ + instance->tx_count = 0; + instance->presend_done = 0; + + instance->remote_poll = 0; + instance->remote_stratum = 0; + instance->remote_root_delay = 0.0; + instance->remote_root_dispersion = 0.0; + instance->remote_mono_epoch = 0; + instance->mono_doffset = 0.0; + + instance->valid_rx = 0; + instance->valid_timestamps = 0; + UTI_ZeroNtp64(&instance->remote_ntp_monorx); + UTI_ZeroNtp64(&instance->remote_ntp_rx); + UTI_ZeroNtp64(&instance->remote_ntp_tx); + UTI_ZeroNtp64(&instance->local_ntp_rx); + UTI_ZeroNtp64(&instance->local_ntp_tx); + zero_local_timestamp(&instance->local_rx); + + zero_local_timestamp(&instance->prev_local_tx); + instance->prev_local_poll = 0; + instance->prev_tx_count = 0; + + instance->updated_init_timestamps = 0; + UTI_ZeroNtp64(&instance->init_remote_ntp_tx); + zero_local_timestamp(&instance->init_local_rx); + + if (instance->delay_quant) + QNT_Reset(instance->delay_quant); + if (instance->filter) + SPF_DropSamples(instance->filter); + instance->filter_count = 0; +} + +/* ================================================== */ + +void +NCR_ResetPoll(NCR_Instance instance) +{ + if (instance->local_poll != instance->minpoll) { + instance->local_poll = instance->minpoll; + + /* The timer was set with a longer poll interval, restart it */ + if (instance->tx_timeout_id) + restart_timeout(instance, get_transmit_delay(instance, 0, 0.0)); + } +} + +/* ================================================== */ + +void +NCR_ChangeRemoteAddress(NCR_Instance inst, NTP_Remote_Address *remote_addr, int ntp_only) +{ + NCR_ResetInstance(inst); + + if (!ntp_only) + NAU_ChangeAddress(inst->auth, &remote_addr->ip_addr); + + inst->remote_addr = *remote_addr; + + if (inst->mode == MODE_CLIENT) + close_client_socket(inst); + else { + 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); + } + + /* Update the reference ID and reset the source/sourcestats instances */ + SRC_SetRefid(inst->source, UTI_IPToRefid(&remote_addr->ip_addr), + &inst->remote_addr.ip_addr); + SRC_ResetInstance(inst->source); + + reset_report(inst); +} + +/* ================================================== */ + +static void +adjust_poll(NCR_Instance inst, double adj) +{ + NTP_Sample last_sample; + + inst->poll_score += adj; + + if (inst->poll_score >= 1.0) { + inst->local_poll += (int)inst->poll_score; + inst->poll_score -= (int)inst->poll_score; + } + + if (inst->poll_score < 0.0) { + inst->local_poll += (int)(inst->poll_score - 1.0); + inst->poll_score -= (int)(inst->poll_score - 1.0); + } + + /* Clamp polling interval to defined range */ + if (inst->local_poll < inst->minpoll) { + inst->local_poll = inst->minpoll; + inst->poll_score = 0; + } else if (inst->local_poll > inst->maxpoll) { + inst->local_poll = inst->maxpoll; + inst->poll_score = 1.0; + } + + /* Don't allow a sub-second polling interval if the source is not reachable + or it is not in a local network according to the measured delay */ + if (inst->local_poll < MIN_NONLAN_POLL && + (!SRC_IsReachable(inst->source) || + (SST_MinRoundTripDelay(SRC_GetSourcestats(inst->source)) > MAX_LAN_PEER_DELAY && + (!inst->filter || !SPF_GetLastSample(inst->filter, &last_sample) || + last_sample.peer_delay > MAX_LAN_PEER_DELAY)))) + inst->local_poll = MIN_NONLAN_POLL; +} + +/* ================================================== */ + +static double +get_poll_adj(NCR_Instance inst, double error_in_estimate, double peer_distance) +{ + double poll_adj; + int samples; + + if (error_in_estimate > peer_distance) { + /* If the prediction is not even within +/- the peer distance of the peer, + we are clearly not tracking the peer at all well, so we back off the + sampling rate depending on just how bad the situation is */ + poll_adj = -log(error_in_estimate / peer_distance) / log(2.0); + } else { + samples = SST_Samples(SRC_GetSourcestats(inst->source)); + + /* Adjust polling interval so that the number of sourcestats samples + remains close to the target value */ + poll_adj = ((double)samples / inst->poll_target - 1.0) / inst->poll_target; + + /* Make interval shortening quicker */ + if (samples < inst->poll_target) { + poll_adj *= 2.0; + } + } + + return poll_adj; +} + +/* ================================================== */ + +static int +get_transmit_poll(NCR_Instance inst) +{ + int poll; + + poll = inst->local_poll; + + /* In symmetric mode, if the peer is responding, use shorter of the local + and remote poll interval, but not shorter than the minimum */ + if (inst->mode == MODE_ACTIVE && poll > inst->remote_poll && + SRC_IsReachable(inst->source)) + poll = MAX(inst->remote_poll, inst->minpoll); + + return poll; +} + +/* ================================================== */ + +static double +get_transmit_delay(NCR_Instance inst, int on_tx, double last_tx) +{ + int poll_to_use, stratum_diff; + double delay_time; + + /* If we're in burst mode, queue for immediate dispatch. + + If we're operating in client/server mode, queue the timeout for + the poll interval hence. The fact that a timeout has been queued + in the transmit handler is immaterial - that is only done so that + we at least send something, if no reply is heard. + + If we're in symmetric mode, we have to take account of the peer's + wishes, otherwise his sampling regime will fall to pieces. If + we're in client/server mode, we don't care what poll interval the + server responded with last time. */ + + poll_to_use = get_transmit_poll(inst); + delay_time = UTI_Log2ToDouble(poll_to_use); + + switch (inst->opmode) { + case MD_OFFLINE: + assert(0); + break; + case MD_ONLINE: + switch(inst->mode) { + case MODE_CLIENT: + if (inst->presend_done) + delay_time = WARM_UP_DELAY; + break; + + case MODE_ACTIVE: + /* If the remote stratum is higher than ours, wait a bit for the next + packet before responding in order to minimize the delay of the + measurement and its error for the peer which has higher stratum. + If the remote stratum is equal to ours, try to interleave packets + evenly with the peer. */ + stratum_diff = inst->remote_stratum - REF_GetOurStratum(); + if ((stratum_diff > 0 && last_tx * PEER_SAMPLING_ADJ < delay_time) || + (!on_tx && !stratum_diff && + last_tx / delay_time > PEER_SAMPLING_ADJ - 0.5)) + delay_time *= PEER_SAMPLING_ADJ; + + /* Substract the already spend time */ + if (last_tx > 0.0) + delay_time -= last_tx; + if (delay_time < 0.0) + delay_time = 0.0; + + break; + default: + assert(0); + break; + } + break; + + case MD_BURST_WAS_ONLINE: + case MD_BURST_WAS_OFFLINE: + /* Burst modes */ + delay_time = MIN(MAX_BURST_INTERVAL, MAX_BURST_POLL_RATIO * delay_time); + break; + default: + assert(0); + break; + } + + return delay_time; +} + +/* ================================================== */ +/* Calculate sampling separation for given polling interval */ + +static double +get_separation(int poll) +{ + double separation; + + assert(poll >= MIN_POLL && poll <= MAX_POLL); + + /* Allow up to 8 sources using the same short interval to not be limited + by the separation */ + separation = UTI_Log2ToDouble(poll - 3); + + return CLAMP(MIN_SAMPLING_SEPARATION, separation, MAX_SAMPLING_SEPARATION); +} + +/* ================================================== */ +/* Timeout handler for closing the client socket when no acceptable + reply can be received from the server */ + +static void +receive_timeout(void *arg) +{ + NCR_Instance inst = (NCR_Instance)arg; + + DEBUG_LOG("Receive timeout for %s", UTI_IPSockAddrToString(&inst->remote_addr)); + + inst->rx_timeout_id = 0; + close_client_socket(inst); +} + +/* ================================================== */ + +static int +add_ext_exp1(NTP_Packet *message, NTP_PacketInfo *info, struct timespec *rx, + double root_delay, double root_dispersion) +{ + struct timespec mono_rx; + NTP_ExtFieldExp1 exp1; + NTP_int64 ts_fuzz; + + memset(&exp1, 0, sizeof (exp1)); + exp1.magic = htonl(NTP_EF_EXP1_MAGIC); + + if (info->mode != MODE_CLIENT) { + exp1.root_delay = UTI_DoubleToNtp32f28(root_delay); + exp1.root_dispersion = UTI_DoubleToNtp32f28(root_dispersion); + if (rx) + UTI_AddDoubleToTimespec(rx, server_mono_offset, &mono_rx); + else + UTI_ZeroTimespec(&mono_rx); + UTI_GetNtp64Fuzz(&ts_fuzz, message->precision); + UTI_TimespecToNtp64(&mono_rx, &exp1.mono_receive_ts, &ts_fuzz); + exp1.mono_epoch = htonl(server_mono_epoch); + } + + if (!NEF_AddField(message, info, NTP_EF_EXP1, &exp1, sizeof (exp1))) { + DEBUG_LOG("Could not add EF"); + return 0; + } + + info->ext_field_flags |= NTP_EF_FLAG_EXP1; + + return 1; +} + +/* ================================================== */ + +static int +transmit_packet(NTP_Mode my_mode, /* The mode this machine wants to be */ + int interleaved, /* Flag enabling interleaved mode */ + int my_poll, /* The log2 of the local poll interval */ + int version, /* The NTP version to be set in the packet */ + uint32_t kod, /* KoD code - 0 disabled */ + int ext_field_flags, /* Extension fields to be included in the packet */ + NAU_Instance auth, /* The authentication to be used for the packet */ + NTP_int64 *remote_ntp_rx, /* The receive timestamp from received packet */ + NTP_int64 *remote_ntp_tx, /* The transmit timestamp from received packet */ + NTP_Local_Timestamp *local_rx, /* The RX time of the received packet */ + NTP_Local_Timestamp *local_tx, /* The TX time of the previous packet + RESULT : TX time of this packet */ + NTP_int64 *local_ntp_rx, /* The receive timestamp from the previous packet + RESULT : receive timestamp from this packet */ + NTP_int64 *local_ntp_tx, /* The transmit timestamp from the previous packet + RESULT : transmit timestamp from this packet */ + NTP_Remote_Address *where_to, /* Where to address the reponse to */ + NTP_Local_Address *from, /* From what address to send it */ + NTP_Packet *request, /* The received packet if responding */ + NTP_PacketInfo *request_info /* and its info */ + ) +{ + NTP_PacketInfo info; + NTP_Packet message; + struct timespec local_receive, local_transmit; + double smooth_offset, local_transmit_err; + int ret, precision; + NTP_int64 ts_fuzz; + + /* Parameters read from reference module */ + int are_we_synchronised, our_stratum, smooth_time; + NTP_Leap leap_status; + uint32_t our_ref_id; + struct timespec our_ref_time; + double our_root_delay, our_root_dispersion; + + assert(auth || (request && request_info)); + + /* Don't reply with version higher than ours */ + if (version > NTP_VERSION) { + version = NTP_VERSION; + } + + /* Check if the packet can be formed in the interleaved mode */ + if (interleaved && (!remote_ntp_rx || !local_tx || UTI_IsZeroTimespec(&local_tx->ts))) + interleaved = 0; + + smooth_time = 0; + smooth_offset = 0.0; + + /* Get an initial transmit timestamp. A more accurate timestamp will be + taken later in this function. */ + SCH_GetLastEventTime(&local_transmit, NULL, NULL); + + if (my_mode == MODE_CLIENT) { + /* Don't reveal local time or state of the clock in client packets */ + precision = 32; + leap_status = our_stratum = our_ref_id = 0; + our_root_delay = our_root_dispersion = 0.0; + UTI_ZeroTimespec(&our_ref_time); + } else { + REF_GetReferenceParams(&local_transmit, + &are_we_synchronised, &leap_status, + &our_stratum, + &our_ref_id, &our_ref_time, + &our_root_delay, &our_root_dispersion); + + /* Get current smoothing offset when sending packet to a client */ + if (SMT_IsEnabled() && (my_mode == MODE_SERVER || my_mode == MODE_BROADCAST)) { + smooth_offset = SMT_GetOffset(&local_transmit); + smooth_time = fabs(smooth_offset) > LCL_GetSysPrecisionAsQuantum(); + + /* Suppress leap second when smoothing and slew mode are enabled */ + if (REF_GetLeapMode() == REF_LeapModeSlew && + (leap_status == LEAP_InsertSecond || leap_status == LEAP_DeleteSecond)) + leap_status = LEAP_Normal; + } + + precision = LCL_GetSysPrecisionAsLog(); + } + + if (smooth_time && !UTI_IsZeroTimespec(&local_rx->ts)) { + our_ref_id = NTP_REFID_SMOOTH; + UTI_AddDoubleToTimespec(&our_ref_time, smooth_offset, &our_ref_time); + UTI_AddDoubleToTimespec(&local_rx->ts, smooth_offset, &local_receive); + } else { + local_receive = local_rx->ts; + } + + if (kod != 0) { + leap_status = LEAP_Unsynchronised; + our_stratum = NTP_INVALID_STRATUM; + our_ref_id = kod; + } + + /* Generate transmit packet */ + message.lvm = NTP_LVM(leap_status, version, my_mode); + /* Stratum 16 and larger are invalid */ + if (our_stratum < NTP_MAX_STRATUM) { + message.stratum = our_stratum; + } else { + message.stratum = NTP_INVALID_STRATUM; + } + + message.poll = my_poll; + message.precision = precision; + message.root_delay = UTI_DoubleToNtp32(our_root_delay); + message.root_dispersion = UTI_DoubleToNtp32(our_root_dispersion); + message.reference_id = htonl(our_ref_id); + + /* Now fill in timestamps */ + + UTI_TimespecToNtp64(&our_ref_time, &message.reference_ts, NULL); + + /* Don't reveal timestamps which are not necessary for the protocol */ + + if (my_mode != MODE_CLIENT || interleaved) { + /* Originate - this comes from the last packet the source sent us */ + message.originate_ts = interleaved ? *remote_ntp_rx : *remote_ntp_tx; + + do { + /* Prepare random bits which will be added to the receive timestamp */ + UTI_GetNtp64Fuzz(&ts_fuzz, precision); + + /* Receive - this is when we received the last packet from the source. + This timestamp will have been adjusted so that it will now look to + the source like we have been running on our latest estimate of + frequency all along */ + UTI_TimespecToNtp64(&local_receive, &message.receive_ts, &ts_fuzz); + + /* Do not send a packet with a non-zero receive timestamp equal to the + originate timestamp or previous receive timestamp */ + } while (!UTI_IsZeroNtp64(&message.receive_ts) && + UTI_IsEqualAnyNtp64(&message.receive_ts, &message.originate_ts, + local_ntp_rx, NULL)); + } else { + UTI_ZeroNtp64(&message.originate_ts); + UTI_ZeroNtp64(&message.receive_ts); + } + + if (!parse_packet(&message, NTP_HEADER_LENGTH, &info)) + return 0; + + if (ext_field_flags) { + if (ext_field_flags & NTP_EF_FLAG_EXP1) { + if (!add_ext_exp1(&message, &info, smooth_time ? NULL : &local_receive, + our_root_delay, our_root_dispersion)) + return 0; + } + } + + do { + /* Prepare random bits which will be added to the transmit timestamp */ + UTI_GetNtp64Fuzz(&ts_fuzz, precision); + + /* Get a more accurate transmit timestamp if it needs to be saved in the + packet (i.e. in the server, symmetric, and broadcast basic modes) */ + if (!interleaved && precision < 32) { + LCL_ReadCookedTime(&local_transmit, &local_transmit_err); + if (smooth_time) + UTI_AddDoubleToTimespec(&local_transmit, smooth_offset, &local_transmit); + } + + UTI_TimespecToNtp64(interleaved ? &local_tx->ts : &local_transmit, + &message.transmit_ts, &ts_fuzz); + + /* Do not send a packet with a non-zero transmit timestamp which is + equal to any of the following timestamps: + - receive (to allow reliable detection of the interleaved mode) + - originate (to prevent the packet from being its own valid response + in the symmetric mode) + - previous transmit (to invalidate responses to the previous packet) + (the precision must be at least -30 to prevent an infinite loop!) */ + } while (!UTI_IsZeroNtp64(&message.transmit_ts) && + UTI_IsEqualAnyNtp64(&message.transmit_ts, &message.receive_ts, + &message.originate_ts, local_ntp_tx)); + + /* Encode in server timestamps a flag indicating RX timestamp to avoid + saving all RX timestamps for detection of interleaved requests */ + if (my_mode == MODE_SERVER || my_mode == MODE_PASSIVE) { + message.receive_ts.lo |= htonl(1); + message.transmit_ts.lo &= ~htonl(1); + } + + /* Generate the authentication data */ + if (auth) { + if (!NAU_GenerateRequestAuth(auth, &message, &info)) { + DEBUG_LOG("Could not generate request auth"); + return 0; + } + } else { + if (!NAU_GenerateResponseAuth(request, request_info, &message, &info, + where_to, from, kod)) { + DEBUG_LOG("Could not generate response auth"); + return 0; + } + } + + if (request_info && request_info->length < info.length) { + DEBUG_LOG("Response longer than request req_len=%d res_len=%d", + request_info->length, info.length); + return 0; + } + + /* If the transmit timestamp will be saved, get an even more + accurate daemon timestamp closer to the transmission */ + if (local_tx) + LCL_ReadCookedTime(&local_transmit, &local_transmit_err); + + ret = NIO_SendPacket(&message, where_to, from, info.length, local_tx != NULL); + + if (local_tx) { + if (smooth_time) + UTI_AddDoubleToTimespec(&local_transmit, smooth_offset, &local_transmit); + local_tx->ts = local_transmit; + local_tx->err = local_transmit_err; + local_tx->source = NTP_TS_DAEMON; + } + + if (local_ntp_rx) + *local_ntp_rx = message.receive_ts; + if (local_ntp_tx) + *local_ntp_tx = message.transmit_ts; + + return ret; +} + +/* ================================================== */ +/* Timeout handler for transmitting to a source. */ + +static void +transmit_timeout(void *arg) +{ + NCR_Instance inst = (NCR_Instance) arg; + NTP_Local_Address local_addr; + int interleaved, initial, sent; + + inst->tx_timeout_id = 0; + + switch (inst->opmode) { + case MD_BURST_WAS_ONLINE: + /* With online burst switch to online before last packet */ + if (inst->burst_total_samples_to_go <= 1) + inst->opmode = MD_ONLINE; + break; + case MD_BURST_WAS_OFFLINE: + if (inst->burst_total_samples_to_go <= 0) + take_offline(inst); + break; + case MD_ONLINE: + /* Start a new burst if the burst option is enabled and the average + polling interval including the burst will not fall below the + minimum polling interval */ + if (inst->auto_burst && inst->local_poll > inst->minpoll) + NCR_InitiateSampleBurst(inst, BURST_GOOD_SAMPLES, + MIN(1 << (inst->local_poll - inst->minpoll), + MAX_BURST_TOTAL_SAMPLES)); + break; + default: + break; + } + + if (inst->opmode == MD_OFFLINE) { + return; + } + + DEBUG_LOG("Transmit timeout for %s", UTI_IPSockAddrToString(&inst->remote_addr)); + + /* Prepare authentication */ + if (!NAU_PrepareRequestAuth(inst->auth)) { + if (inst->burst_total_samples_to_go > 0) + inst->burst_total_samples_to_go--; + adjust_poll(inst, 0.25); + SRC_UpdateReachability(inst->source, 0); + restart_timeout(inst, get_transmit_delay(inst, 1, 0.0)); + return; + } + + /* Open new client socket */ + if (inst->mode == MODE_CLIENT) { + close_client_socket(inst); + assert(inst->local_addr.sock_fd == INVALID_SOCK_FD); + inst->local_addr.sock_fd = NIO_OpenClientSocket(&inst->remote_addr); + } + + /* Don't require the packet to be sent from the same address as before */ + local_addr.ip_addr.family = IPADDR_UNSPEC; + local_addr.if_index = INVALID_IF_INDEX; + local_addr.sock_fd = inst->local_addr.sock_fd; + + /* In symmetric mode, don't send a packet in interleaved mode unless it + is the first response to the last valid request received from the peer + and there was just one response to the previous valid request. This + prevents the peer from matching the transmit timestamp with an older + response if it can't detect missed responses. In client mode, which has + at most one response per request, check how many responses are missing to + prevent the server from responding with a very old transmit timestamp. */ + interleaved = inst->interleaved && + ((inst->mode == MODE_CLIENT && + inst->tx_count < MAX_CLIENT_INTERLEAVED_TX) || + (inst->mode == MODE_ACTIVE && + inst->prev_tx_count == 1 && inst->tx_count == 0)); + + /* In symmetric mode, if no valid response was received since the previous + transmission, respond to the last received packet even if it failed some + specific NTP tests. This is necessary for starting and restarting the + protocol, e.g. when a packet was lost. */ + initial = inst->mode == MODE_ACTIVE && !inst->valid_rx && + !UTI_IsZeroNtp64(&inst->init_remote_ntp_tx); + + /* Prepare for the response */ + inst->valid_rx = 0; + inst->updated_init_timestamps = 0; + if (initial) + inst->valid_timestamps = 0; + + /* Check whether we need to 'warm up' the link to the other end by + sending an NTP exchange to ensure both ends' ARP caches are + primed or whether we need to send two packets first to ensure a + server in the interleaved mode has a fresh timestamp for us. */ + if (inst->presend_minpoll <= inst->local_poll && !inst->presend_done && + !inst->burst_total_samples_to_go) { + inst->presend_done = interleaved ? 2 : 1; + } else if (inst->presend_done > 0) { + inst->presend_done--; + } + + /* Send the request (which may also be a response in the symmetric mode) */ + sent = transmit_packet(inst->mode, interleaved, inst->local_poll, inst->version, 0, + inst->ext_field_flags, inst->auth, + initial ? NULL : &inst->remote_ntp_rx, + initial ? &inst->init_remote_ntp_tx : &inst->remote_ntp_tx, + initial ? &inst->init_local_rx : &inst->local_rx, + &inst->local_tx, &inst->local_ntp_rx, &inst->local_ntp_tx, + &inst->remote_addr, &local_addr, NULL, NULL); + + ++inst->tx_count; + if (sent) + inst->report.total_tx_count++; + + /* If the source loses connectivity and our packets are still being sent, + back off the sampling rate to reduce the network traffic. If it's the + source to which we are currently locked, back off slowly. */ + + if (inst->tx_count >= 2) { + /* Implies we have missed at least one transmission */ + + if (sent) { + adjust_poll(inst, SRC_IsSyncPeer(inst->source) ? 0.1 : 0.25); + } + + SRC_UpdateReachability(inst->source, 0); + + /* Count missing samples for the sample filter */ + process_sample(inst, NULL); + } + + /* With auto_offline take the source offline if sending failed */ + if (!sent && inst->auto_offline) + NCR_SetConnectivity(inst, SRC_OFFLINE); + + switch (inst->opmode) { + case MD_BURST_WAS_ONLINE: + /* When not reachable, don't stop online burst until sending succeeds */ + if (!sent && !SRC_IsReachable(inst->source)) + break; + /* Fall through */ + case MD_BURST_WAS_OFFLINE: + --inst->burst_total_samples_to_go; + break; + case MD_OFFLINE: + return; + default: + break; + } + + /* Restart timer for this message */ + restart_timeout(inst, get_transmit_delay(inst, 1, 0.0)); + + /* If a client packet was just sent, schedule a timeout to close the socket + at the time when all server replies would fail the delay test, so the + socket is not open for longer than necessary */ + if (inst->mode == MODE_CLIENT) + inst->rx_timeout_id = SCH_AddTimeoutByDelay(inst->max_delay + MAX_SERVER_INTERVAL, + receive_timeout, (void *)inst); +} + +/* ================================================== */ + +static int +is_zero_data(unsigned char *data, int length) +{ + int i; + + for (i = 0; i < length; i++) + if (data[i] != 0) + return 0; + return 1; +} + +/* ================================================== */ + +static int +parse_packet(NTP_Packet *packet, int length, NTP_PacketInfo *info) +{ + int parsed, remainder, ef_length, ef_type, ef_body_length; + unsigned char *data; + void *ef_body; + + if (length < NTP_HEADER_LENGTH || length % 4U != 0) { + DEBUG_LOG("NTP packet has invalid length %d", length); + return 0; + } + + info->length = length; + info->version = NTP_LVM_TO_VERSION(packet->lvm); + info->mode = NTP_LVM_TO_MODE(packet->lvm); + info->ext_fields = 0; + info->ext_field_flags = 0; + info->auth.mode = NTP_AUTH_NONE; + + if (info->version < NTP_MIN_COMPAT_VERSION || info->version > NTP_MAX_COMPAT_VERSION) { + DEBUG_LOG("NTP packet has invalid version %d", info->version); + return 0; + } + + data = (void *)packet; + parsed = NTP_HEADER_LENGTH; + remainder = info->length - parsed; + + /* Check if this is a plain NTP packet with no extension fields or MAC */ + if (remainder <= 0) + return 1; + + assert(remainder % 4 == 0); + + /* In NTPv3 and older packets don't have extension fields. Anything after + the header is assumed to be a MAC. */ + if (info->version <= 3) { + info->auth.mode = NTP_AUTH_SYMMETRIC; + info->auth.mac.start = parsed; + info->auth.mac.length = remainder; + info->auth.mac.key_id = ntohl(*(uint32_t *)(data + parsed)); + + /* Check if it is an MS-SNTP authenticator field or extended authenticator + field with zeroes as digest */ + if (info->version == 3 && info->auth.mac.key_id != 0) { + if (remainder == 20 && is_zero_data(data + parsed + 4, remainder - 4)) + info->auth.mode = NTP_AUTH_MSSNTP; + else if (remainder == 72 && is_zero_data(data + parsed + 8, remainder - 8)) + info->auth.mode = NTP_AUTH_MSSNTP_EXT; + } + + return 1; + } + + /* Check for a crypto NAK */ + if (remainder == 4 && ntohl(*(uint32_t *)(data + parsed)) == 0) { + info->auth.mode = NTP_AUTH_SYMMETRIC; + info->auth.mac.start = parsed; + info->auth.mac.length = remainder; + info->auth.mac.key_id = 0; + return 1; + } + + /* Parse the rest of the NTPv4 packet */ + + while (remainder > 0) { + /* Check if the remaining data is a MAC */ + if (remainder >= NTP_MIN_MAC_LENGTH && remainder <= NTP_MAX_V4_MAC_LENGTH) + break; + + /* Check if this is a valid NTPv4 extension field and skip it */ + if (!NEF_ParseField(packet, info->length, parsed, + &ef_length, &ef_type, &ef_body, &ef_body_length)) { + DEBUG_LOG("Invalid format"); + return 0; + } + + assert(ef_length > 0 && ef_length % 4 == 0); + + switch (ef_type) { + case NTP_EF_NTS_UNIQUE_IDENTIFIER: + case NTP_EF_NTS_COOKIE: + case NTP_EF_NTS_COOKIE_PLACEHOLDER: + case NTP_EF_NTS_AUTH_AND_EEF: + info->auth.mode = NTP_AUTH_NTS; + break; + case NTP_EF_EXP1: + if (ef_body_length == sizeof (NTP_ExtFieldExp1) && + ntohl(((NTP_ExtFieldExp1 *)ef_body)->magic) == NTP_EF_EXP1_MAGIC) + info->ext_field_flags |= NTP_EF_FLAG_EXP1; + break; + default: + DEBUG_LOG("Unknown extension field type=%x", (unsigned int)ef_type); + } + + info->ext_fields++; + parsed += ef_length; + remainder = info->length - parsed; + } + + if (remainder == 0) { + /* No MAC */ + return 1; + } else if (remainder >= NTP_MIN_MAC_LENGTH) { + info->auth.mode = NTP_AUTH_SYMMETRIC; + info->auth.mac.start = parsed; + info->auth.mac.length = remainder; + info->auth.mac.key_id = ntohl(*(uint32_t *)(data + parsed)); + return 1; + } + + DEBUG_LOG("Invalid format"); + return 0; +} + +/* ================================================== */ + +static int +check_delay_ratio(NCR_Instance inst, SST_Stats stats, + struct timespec *sample_time, double delay) +{ + double last_sample_ago, predicted_offset, min_delay, skew, std_dev; + double max_delay; + + if (inst->max_delay_ratio < 1.0 || + !SST_GetDelayTestData(stats, sample_time, &last_sample_ago, + &predicted_offset, &min_delay, &skew, &std_dev)) + return 1; + + max_delay = min_delay * inst->max_delay_ratio + + last_sample_ago * (skew + LCL_GetMaxClockError()); + + if (delay <= max_delay) + return 1; + + DEBUG_LOG("maxdelayratio: delay=%e max_delay=%e", delay, max_delay); + return 0; +} + +/* ================================================== */ + +static int +check_delay_quant(NCR_Instance inst, double delay) +{ + double quant; + + quant = QNT_GetQuantile(inst->delay_quant, QNT_GetMinK(inst->delay_quant)); + + if (delay <= quant) + return 1; + + DEBUG_LOG("maxdelayquant: delay=%e quant=%e", delay, quant); + return 0; +} + +/* ================================================== */ + +static int +check_delay_dev_ratio(NCR_Instance inst, SST_Stats stats, + struct timespec *sample_time, double offset, double delay) +{ + double last_sample_ago, predicted_offset, min_delay, skew, std_dev; + double delta, max_delta, error_in_estimate; + + if (!SST_GetDelayTestData(stats, sample_time, &last_sample_ago, + &predicted_offset, &min_delay, &skew, &std_dev)) + return 1; + + /* Require that the ratio of the increase in delay from the minimum to the + standard deviation is less than max_delay_dev_ratio. In the allowed + increase in delay include also dispersion. */ + + max_delta = std_dev * inst->max_delay_dev_ratio + + last_sample_ago * (skew + LCL_GetMaxClockError()); + delta = (delay - min_delay) / 2.0; + + if (delta <= max_delta) + return 1; + + error_in_estimate = offset + predicted_offset; + + /* Before we decide to drop the sample, make sure the difference between + measured offset and predicted offset is not significantly larger than + the increase in delay */ + if (fabs(error_in_estimate) - delta > max_delta) + return 1; + + DEBUG_LOG("maxdelaydevratio: error=%e delay=%e delta=%e max_delta=%e", + error_in_estimate, delay, delta, max_delta); + return 0; +} + +/* ================================================== */ + +static int +check_sync_loop(NCR_Instance inst, NTP_Packet *message, NTP_Local_Address *local_addr, + struct timespec *local_ts) +{ + double our_root_delay, our_root_dispersion; + int are_we_synchronised, our_stratum; + struct timespec our_ref_time; + NTP_Leap leap_status; + uint32_t our_ref_id; + + /* Check if a client or peer can be synchronised to us */ + if (!NIO_IsServerSocketOpen() || REF_GetMode() != REF_ModeNormal) + return 1; + + /* Check if the source indicates that it is synchronised to our address + (assuming it uses the same address as the one from which we send requests + to the source) */ + if (message->stratum > 1 && + message->reference_id == htonl(UTI_IPToRefid(&local_addr->ip_addr))) + return 0; + + /* Compare our reference data with the source to make sure it is not us + (e.g. due to a misconfiguration) */ + + REF_GetReferenceParams(local_ts, &are_we_synchronised, &leap_status, &our_stratum, + &our_ref_id, &our_ref_time, &our_root_delay, &our_root_dispersion); + + if (message->stratum == our_stratum && + message->reference_id == htonl(our_ref_id) && + message->root_delay == UTI_DoubleToNtp32(our_root_delay) && + !UTI_IsZeroNtp64(&message->reference_ts)) { + NTP_int64 ntp_ref_time; + + UTI_TimespecToNtp64(&our_ref_time, &ntp_ref_time, NULL); + if (UTI_CompareNtp64(&message->reference_ts, &ntp_ref_time) == 0) { + DEBUG_LOG("Source %s is me", UTI_IPToString(&inst->remote_addr.ip_addr)); + return 0; + } + } + + return 1; +} + +/* ================================================== */ + +static void +process_sample(NCR_Instance inst, NTP_Sample *sample) +{ + double estimated_offset, error_in_estimate; + NTP_Sample filtered_sample; + + /* Accumulate the sample to the median filter if enabled and wait for + the configured number of samples before processing (NULL indicates + a missing sample) */ + if (inst->filter) { + if (sample) + SPF_AccumulateSample(inst->filter, sample); + + if (++inst->filter_count < SPF_GetMaxSamples(inst->filter)) + return; + + if (!SPF_GetFilteredSample(inst->filter, &filtered_sample)) + return; + + sample = &filtered_sample; + inst->filter_count = 0; + } + + if (!sample) + return; + + /* Get the estimated offset predicted from previous samples. The + convention here is that positive means local clock FAST of + reference, i.e. backwards to the way that 'offset' is defined. */ + estimated_offset = SST_PredictOffset(SRC_GetSourcestats(inst->source), &sample->time); + + error_in_estimate = fabs(-sample->offset - estimated_offset); + + if (inst->mono_doffset != 0.0 && fabs(inst->mono_doffset) <= MAX_MONO_DOFFSET) { + DEBUG_LOG("Monotonic correction offset=%.9f", inst->mono_doffset); + SST_CorrectOffset(SRC_GetSourcestats(inst->source), inst->mono_doffset); + } + inst->mono_doffset = 0.0; + + SRC_AccumulateSample(inst->source, sample); + SRC_SelectSource(inst->source); + + adjust_poll(inst, get_poll_adj(inst, error_in_estimate, + sample->peer_dispersion + 0.5 * sample->peer_delay)); +} + +/* ================================================== */ + +static int +process_response(NCR_Instance inst, NTP_Local_Address *local_addr, + NTP_Local_Timestamp *rx_ts, NTP_Packet *message, NTP_PacketInfo *info) +{ + NTP_Sample sample; + SST_Stats stats; + + int pkt_leap, pkt_version; + uint32_t pkt_refid; + double pkt_root_delay; + double pkt_root_dispersion; + + /* The skew and estimated frequency offset relative to the remote source */ + double skew, source_freq_lo, source_freq_hi; + + /* RFC 5905 packet tests */ + int test1, test2n, test2i, test2, test3, test5, test6, test7; + int interleaved_packet, valid_packet, synced_packet; + + /* Additional tests */ + int testA, testB, testC, testD; + int good_packet; + + /* Kiss-o'-Death codes */ + int kod_rate; + + /* Extension fields */ + int parsed, ef_length, ef_type, ef_body_length; + void *ef_body; + NTP_ExtFieldExp1 *ef_exp1; + + NTP_Local_Timestamp local_receive, local_transmit; + double remote_interval, local_interval, response_time; + double delay_time, precision, mono_doffset; + int updated_timestamps; + + /* ==================== */ + + stats = SRC_GetSourcestats(inst->source); + + ef_exp1 = NULL; + + /* Find requested non-authentication extension fields */ + if (inst->ext_field_flags & info->ext_field_flags) { + for (parsed = NTP_HEADER_LENGTH; parsed < info->length; parsed += ef_length) { + if (!NEF_ParseField(message, info->length, parsed, + &ef_length, &ef_type, &ef_body, &ef_body_length)) + break; + + switch (ef_type) { + case NTP_EF_EXP1: + if (inst->ext_field_flags & NTP_EF_FLAG_EXP1 && + ef_body_length == sizeof (*ef_exp1) && + ntohl(((NTP_ExtFieldExp1 *)ef_body)->magic) == NTP_EF_EXP1_MAGIC) + ef_exp1 = ef_body; + break; + } + } + } + + pkt_leap = NTP_LVM_TO_LEAP(message->lvm); + pkt_version = NTP_LVM_TO_VERSION(message->lvm); + pkt_refid = ntohl(message->reference_id); + if (ef_exp1) { + pkt_root_delay = UTI_Ntp32f28ToDouble(ef_exp1->root_delay); + pkt_root_dispersion = UTI_Ntp32f28ToDouble(ef_exp1->root_dispersion); + } else { + pkt_root_delay = UTI_Ntp32ToDouble(message->root_delay); + pkt_root_dispersion = UTI_Ntp32ToDouble(message->root_dispersion); + } + + /* Check if the packet is valid per RFC 5905, section 8. + The test values are 1 when passed and 0 when failed. */ + + /* Test 1 checks for duplicate packet */ + test1 = UTI_CompareNtp64(&message->receive_ts, &inst->remote_ntp_rx) || + UTI_CompareNtp64(&message->transmit_ts, &inst->remote_ntp_tx); + + /* Test 2 checks for bogus packet in the basic and interleaved modes. This + ensures the source is responding to the latest packet we sent to it. */ + test2n = !UTI_CompareNtp64(&message->originate_ts, &inst->local_ntp_tx); + test2i = inst->interleaved && + !UTI_CompareNtp64(&message->originate_ts, &inst->local_ntp_rx); + test2 = test2n || test2i; + interleaved_packet = !test2n && test2i; + + /* Test 3 checks for invalid timestamps. This can happen when the + association if not properly 'up'. */ + test3 = !UTI_IsZeroNtp64(&message->originate_ts) && + !UTI_IsZeroNtp64(&message->receive_ts) && + !UTI_IsZeroNtp64(&message->transmit_ts); + + /* Test 4 would check for denied access. It would always pass as this + function is called only for known sources. */ + + /* Test 5 checks for authentication failure */ + test5 = NAU_CheckResponseAuth(inst->auth, message, info); + + /* Test 6 checks for unsynchronised server */ + test6 = pkt_leap != LEAP_Unsynchronised && + message->stratum < NTP_MAX_STRATUM && + message->stratum != NTP_INVALID_STRATUM; + + /* Test 7 checks for bad data. The root distance must be smaller than a + defined maximum. */ + test7 = pkt_root_delay / 2.0 + pkt_root_dispersion < NTP_MAX_DISPERSION; + + /* The packet is considered valid if the tests 1-5 passed. The timestamps + can be used for synchronisation if the tests 6 and 7 passed too. */ + valid_packet = test1 && test2 && test3 && test5; + synced_packet = valid_packet && test6 && test7; + + /* Check for Kiss-o'-Death codes */ + kod_rate = 0; + if (test1 && test2 && test5 && pkt_leap == LEAP_Unsynchronised && + message->stratum == NTP_INVALID_STRATUM) { + if (pkt_refid == KOD_RATE) + kod_rate = 1; + } + + if (synced_packet && (!interleaved_packet || inst->valid_timestamps)) { + /* These are the timespec equivalents of the remote and local epochs */ + struct timespec remote_receive, remote_transmit, remote_request_receive; + struct timespec local_average, remote_average, prev_remote_transmit; + double prev_remote_poll_interval, root_delay, root_dispersion; + + /* If the remote monotonic timestamps are available and are from the same + epoch, calculate the change in the offset between the monotonic and + real-time clocks, i.e. separate the source's time corrections from + frequency corrections. The offset is accumulated between measurements. + It will correct old measurements kept in sourcestats before accumulating + the new sample. In the interleaved mode, cancel the correction out in + remote timestamps of the previous request and response, which were + captured before the source accumulated the new time corrections. */ + if (ef_exp1 && inst->remote_mono_epoch == ntohl(ef_exp1->mono_epoch) && + !UTI_IsZeroNtp64(&ef_exp1->mono_receive_ts) && + !UTI_IsZeroNtp64(&inst->remote_ntp_monorx)) { + mono_doffset = + UTI_DiffNtp64ToDouble(&ef_exp1->mono_receive_ts, &inst->remote_ntp_monorx) - + UTI_DiffNtp64ToDouble(&message->receive_ts, &inst->remote_ntp_rx); + if (fabs(mono_doffset) > MAX_MONO_DOFFSET) + mono_doffset = 0.0; + } else { + mono_doffset = 0.0; + } + + /* Select remote and local timestamps for the new sample */ + if (interleaved_packet) { + /* Prefer previous local TX and remote RX timestamps if it will make + the intervals significantly shorter in order to improve the accuracy + of the measured delay */ + if (!UTI_IsZeroTimespec(&inst->prev_local_tx.ts) && + MAX_INTERLEAVED_L2L_RATIO * + UTI_DiffTimespecsToDouble(&inst->local_tx.ts, &inst->local_rx.ts) > + UTI_DiffTimespecsToDouble(&inst->local_rx.ts, &inst->prev_local_tx.ts)) { + UTI_Ntp64ToTimespec(&inst->remote_ntp_rx, &remote_receive); + UTI_AddDoubleToTimespec(&remote_receive, -mono_doffset, &remote_receive); + remote_request_receive = remote_receive; + local_transmit = inst->prev_local_tx; + root_delay = inst->remote_root_delay; + root_dispersion = inst->remote_root_dispersion; + } else { + UTI_Ntp64ToTimespec(&message->receive_ts, &remote_receive); + UTI_Ntp64ToTimespec(&inst->remote_ntp_rx, &remote_request_receive); + local_transmit = inst->local_tx; + root_delay = MAX(pkt_root_delay, inst->remote_root_delay); + root_dispersion = MAX(pkt_root_dispersion, inst->remote_root_dispersion); + } + UTI_Ntp64ToTimespec(&message->transmit_ts, &remote_transmit); + UTI_AddDoubleToTimespec(&remote_transmit, -mono_doffset, &remote_transmit); + UTI_Ntp64ToTimespec(&inst->remote_ntp_tx, &prev_remote_transmit); + local_receive = inst->local_rx; + } else { + UTI_Ntp64ToTimespec(&message->receive_ts, &remote_receive); + UTI_Ntp64ToTimespec(&message->transmit_ts, &remote_transmit); + UTI_ZeroTimespec(&prev_remote_transmit); + remote_request_receive = remote_receive; + local_receive = *rx_ts; + local_transmit = inst->local_tx; + root_delay = pkt_root_delay; + root_dispersion = pkt_root_dispersion; + } + + /* Calculate intervals between remote and local timestamps */ + UTI_AverageDiffTimespecs(&remote_receive, &remote_transmit, + &remote_average, &remote_interval); + UTI_AverageDiffTimespecs(&local_transmit.ts, &local_receive.ts, + &local_average, &local_interval); + response_time = fabs(UTI_DiffTimespecsToDouble(&remote_transmit, + &remote_request_receive)); + + precision = LCL_GetSysPrecisionAsQuantum() + UTI_Log2ToDouble(message->precision); + + /* Calculate delay */ + sample.peer_delay = fabs(local_interval - remote_interval); + if (sample.peer_delay < precision) + sample.peer_delay = precision; + + /* Calculate offset. Following the NTP definition, this is negative + if we are fast of the remote source. */ + sample.offset = UTI_DiffTimespecsToDouble(&remote_average, &local_average); + + /* Apply configured correction */ + sample.offset += inst->offset_correction; + + /* We treat the time of the sample as being midway through the local + measurement period. An analysis assuming constant relative + frequency and zero network delay shows this is the only possible + choice to estimate the frequency difference correctly for every + sample pair. */ + sample.time = local_average; + + SST_GetFrequencyRange(stats, &source_freq_lo, &source_freq_hi); + + /* Calculate skew */ + skew = (source_freq_hi - source_freq_lo) / 2.0; + + /* and then calculate peer dispersion and the rest of the sample */ + sample.peer_dispersion = MAX(precision, MAX(local_transmit.err, local_receive.err)) + + skew * fabs(local_interval); + sample.root_delay = root_delay + sample.peer_delay; + sample.root_dispersion = root_dispersion + sample.peer_dispersion; + + /* If the source is an active peer, this is the minimum assumed interval + between previous two transmissions (if not constrained by minpoll) */ + prev_remote_poll_interval = UTI_Log2ToDouble(MIN(inst->remote_poll, + inst->prev_local_poll)); + + /* Additional tests required to pass before accumulating the sample */ + + /* Test A requires that the minimum estimate of the peer delay is not + larger than the configured maximum, in both client modes that the server + processing time is sane, in interleaved client/server mode that the + previous response was not in basic mode (which prevents using timestamps + that minimise delay error), and in interleaved symmetric mode that the + measured delay and intervals between remote timestamps don't indicate + a missed response */ + testA = sample.peer_delay - sample.peer_dispersion <= inst->max_delay && + precision <= inst->max_delay && + !(inst->mode == MODE_CLIENT && response_time > MAX_SERVER_INTERVAL) && + !(inst->mode == MODE_CLIENT && interleaved_packet && + UTI_IsZeroTimespec(&inst->prev_local_tx.ts) && + UTI_CompareTimespecs(&local_transmit.ts, &inst->local_tx.ts) == 0) && + !(inst->mode == MODE_ACTIVE && interleaved_packet && + (sample.peer_delay > 0.5 * prev_remote_poll_interval || + UTI_CompareNtp64(&message->receive_ts, &message->transmit_ts) <= 0 || + (inst->remote_poll <= inst->prev_local_poll && + UTI_DiffTimespecsToDouble(&remote_transmit, &prev_remote_transmit) > + 1.5 * prev_remote_poll_interval))); + + /* Test B requires in client mode that the ratio of the round trip delay + to the minimum one currently in the stats data register is less than an + administrator-defined value */ + testB = check_delay_ratio(inst, stats, &sample.time, sample.peer_delay); + + /* Test C either requires that the delay is less than an estimate of an + administrator-defined quantile, or (if the quantile is not specified) + it requires that the ratio of the increase in delay from the minimum + one in the stats data register to the standard deviation of the offsets + in the register is less than an administrator-defined value or the + difference between measured offset and predicted offset is larger than + the increase in delay */ + if (inst->delay_quant) + testC = check_delay_quant(inst, sample.peer_delay); + else + testC = check_delay_dev_ratio(inst, stats, &sample.time, sample.offset, + sample.peer_delay); + + /* Test D requires that the source is not synchronised to us and is not us + to prevent a synchronisation loop */ + testD = check_sync_loop(inst, message, local_addr, &rx_ts->ts); + } else { + remote_interval = local_interval = response_time = 0.0; + sample.offset = sample.peer_delay = sample.peer_dispersion = 0.0; + sample.root_delay = sample.root_dispersion = 0.0; + sample.time = rx_ts->ts; + mono_doffset = 0.0; + local_receive = *rx_ts; + local_transmit = inst->local_tx; + testA = testB = testC = testD = 0; + } + + /* The packet is considered good for synchronisation if + the additional tests passed */ + good_packet = testA && testB && testC && testD; + + /* Update the NTP timestamps. If it's a valid packet from a synchronised + source, the timestamps may be used later when processing a packet in the + interleaved mode. Protect the timestamps against replay attacks in client + mode, and also in symmetric mode as long as the peers use the same polling + interval and never start with clocks in future or very distant past. + The authentication test (test5) is required to prevent DoS attacks using + unauthenticated packets on authenticated symmetric associations. */ + if ((inst->mode == MODE_CLIENT && valid_packet && !inst->valid_rx) || + (inst->mode == MODE_ACTIVE && valid_packet && + (!inst->valid_rx || + UTI_CompareNtp64(&inst->remote_ntp_tx, &message->transmit_ts) < 0))) { + inst->remote_ntp_rx = message->receive_ts; + inst->remote_ntp_tx = message->transmit_ts; + inst->local_rx = *rx_ts; + inst->valid_timestamps = synced_packet; + + UTI_ZeroNtp64(&inst->init_remote_ntp_tx); + zero_local_timestamp(&inst->init_local_rx); + inst->updated_init_timestamps = 0; + updated_timestamps = 2; + + /* If available, update the monotonic timestamp and accumulate the offset. + This needs to be done here to not lose changes in remote_ntp_rx in + symmetric mode when there are multiple responses per request. */ + if (ef_exp1 && !UTI_IsZeroNtp64(&ef_exp1->mono_receive_ts)) { + inst->remote_mono_epoch = ntohl(ef_exp1->mono_epoch); + inst->remote_ntp_monorx = ef_exp1->mono_receive_ts; + inst->mono_doffset += mono_doffset; + } else { + inst->remote_mono_epoch = 0; + UTI_ZeroNtp64(&inst->remote_ntp_monorx); + inst->mono_doffset = 0.0; + } + + /* Don't use the same set of timestamps for the next sample */ + if (interleaved_packet) + inst->prev_local_tx = inst->local_tx; + else + zero_local_timestamp(&inst->prev_local_tx); + } else if (inst->mode == MODE_ACTIVE && + test1 && !UTI_IsZeroNtp64(&message->transmit_ts) && test5 && + (!inst->updated_init_timestamps || + UTI_CompareNtp64(&inst->init_remote_ntp_tx, &message->transmit_ts) < 0)) { + inst->init_remote_ntp_tx = message->transmit_ts; + inst->init_local_rx = *rx_ts; + inst->updated_init_timestamps = 1; + updated_timestamps = 1; + } else { + updated_timestamps = 0; + } + + /* Accept at most one response per request. The NTP specification recommends + resetting local_ntp_tx to make the following packets fail test2 or test3, + but that would not allow the code above to make multiple updates of the + timestamps in symmetric mode. Also, ignore presend responses. */ + if (inst->valid_rx) { + test2 = test3 = 0; + valid_packet = synced_packet = good_packet = 0; + } else if (valid_packet) { + if (inst->presend_done) { + testA = 0; + good_packet = 0; + } + inst->valid_rx = 1; + } + + if ((unsigned int)local_receive.source >= sizeof (tss_chars) || + (unsigned int)local_transmit.source >= sizeof (tss_chars)) + assert(0); + + DEBUG_LOG("NTP packet lvm=%o stratum=%d poll=%d prec=%d root_delay=%.9f root_disp=%.9f refid=%"PRIx32" [%s]", + message->lvm, message->stratum, message->poll, message->precision, + pkt_root_delay, pkt_root_dispersion, pkt_refid, + message->stratum == NTP_INVALID_STRATUM || message->stratum == 1 ? + UTI_RefidToString(pkt_refid) : ""); + DEBUG_LOG("reference=%s origin=%s receive=%s transmit=%s", + UTI_Ntp64ToString(&message->reference_ts), + UTI_Ntp64ToString(&message->originate_ts), + UTI_Ntp64ToString(&message->receive_ts), + UTI_Ntp64ToString(&message->transmit_ts)); + DEBUG_LOG("offset=%.9f delay=%.9f dispersion=%f root_delay=%f root_dispersion=%f", + sample.offset, sample.peer_delay, sample.peer_dispersion, + sample.root_delay, sample.root_dispersion); + DEBUG_LOG("remote_interval=%.9f local_interval=%.9f response_time=%.9f mono_doffset=%.9f txs=%c rxs=%c", + remote_interval, local_interval, response_time, mono_doffset, + tss_chars[local_transmit.source], tss_chars[local_receive.source]); + DEBUG_LOG("test123=%d%d%d test567=%d%d%d testABCD=%d%d%d%d kod_rate=%d interleaved=%d" + " presend=%d valid=%d good=%d updated=%d", + test1, test2, test3, test5, test6, test7, testA, testB, testC, testD, + kod_rate, interleaved_packet, inst->presend_done, valid_packet, good_packet, + updated_timestamps); + + if (valid_packet) { + inst->remote_poll = message->poll; + inst->remote_stratum = message->stratum != NTP_INVALID_STRATUM ? + MIN(message->stratum, NTP_MAX_STRATUM) : NTP_MAX_STRATUM; + inst->remote_root_delay = pkt_root_delay; + inst->remote_root_dispersion = pkt_root_dispersion; + + inst->prev_local_poll = inst->local_poll; + inst->prev_tx_count = inst->tx_count; + inst->tx_count = 0; + + SRC_UpdateReachability(inst->source, synced_packet); + + if (synced_packet) { + if (inst->copy && inst->remote_stratum > 0) { + /* Assume the reference ID and stratum of the server */ + inst->remote_stratum--; + SRC_SetRefid(inst->source, ntohl(message->reference_id), &inst->remote_addr.ip_addr); + } + + SRC_UpdateStatus(inst->source, MAX(inst->remote_stratum, inst->min_stratum), pkt_leap); + + if (inst->delay_quant) + QNT_Accumulate(inst->delay_quant, sample.peer_delay); + } + + if (good_packet) { + /* Adjust the polling interval, accumulate the sample, etc. */ + process_sample(inst, &sample); + + /* If we're in burst mode, check whether the burst is completed and + revert to the previous mode */ + switch (inst->opmode) { + case MD_BURST_WAS_ONLINE: + case MD_BURST_WAS_OFFLINE: + --inst->burst_good_samples_to_go; + if (inst->burst_good_samples_to_go <= 0) { + if (inst->opmode == MD_BURST_WAS_ONLINE) + inst->opmode = MD_ONLINE; + else + take_offline(inst); + } + break; + default: + break; + } + } else { + /* Slowly increase the polling interval if we can't get a good response */ + adjust_poll(inst, testD ? 0.02 : 0.1); + + /* Count missing samples for the sample filter */ + process_sample(inst, NULL); + } + + /* If in client mode, no more packets are expected to be coming from the + server and the socket can be closed */ + close_client_socket(inst); + + /* Update the local address and interface */ + inst->local_addr.ip_addr = local_addr->ip_addr; + inst->local_addr.if_index = local_addr->if_index; + + /* And now, requeue the timer */ + if (inst->opmode != MD_OFFLINE) { + delay_time = get_transmit_delay(inst, 0, + UTI_DiffTimespecsToDouble(&inst->local_rx.ts, &inst->local_tx.ts)); + + if (kod_rate) { + LOG(LOGS_WARN, "Received KoD RATE from %s", + UTI_IPToString(&inst->remote_addr.ip_addr)); + + /* Back off for a while and stop ongoing burst */ + delay_time += 4 * UTI_Log2ToDouble(inst->local_poll); + + if (inst->opmode == MD_BURST_WAS_OFFLINE || inst->opmode == MD_BURST_WAS_ONLINE) { + inst->burst_good_samples_to_go = 0; + } + } + + /* Get rid of old timeout and start a new one */ + assert(inst->tx_timeout_id); + restart_timeout(inst, delay_time); + } + + /* Update the NTP report */ + inst->report.local_addr = inst->local_addr.ip_addr; + inst->report.leap = pkt_leap; + inst->report.version = pkt_version; + inst->report.mode = NTP_LVM_TO_MODE(message->lvm); + inst->report.stratum = message->stratum; + inst->report.poll = message->poll; + inst->report.precision = message->precision; + inst->report.root_delay = pkt_root_delay; + inst->report.root_dispersion = pkt_root_dispersion; + inst->report.ref_id = pkt_refid; + UTI_Ntp64ToTimespec(&message->reference_ts, &inst->report.ref_time); + inst->report.offset = sample.offset; + inst->report.peer_delay = sample.peer_delay; + inst->report.peer_dispersion = sample.peer_dispersion; + inst->report.response_time = response_time; + inst->report.jitter_asymmetry = SST_GetJitterAsymmetry(stats); + inst->report.tests = ((((((((test1 << 1 | test2) << 1 | test3) << 1 | + test5) << 1 | test6) << 1 | test7) << 1 | + testA) << 1 | testB) << 1 | testC) << 1 | testD; + inst->report.interleaved = interleaved_packet; + inst->report.authenticated = NAU_IsAuthEnabled(inst->auth); + inst->report.tx_tss_char = tss_chars[local_transmit.source]; + inst->report.rx_tss_char = tss_chars[local_receive.source]; + + inst->report.total_valid_count++; + if (good_packet) + inst->report.total_good_count++; + } + + /* Do measurement logging */ + if (logfileid != -1 && (log_raw_measurements || synced_packet)) { + LOG_FileWrite(logfileid, "%s %-15s %1c %2d %1d%1d%1d %1d%1d%1d %1d%1d%1d%d %2d %2d %4.2f %10.3e %10.3e %10.3e %10.3e %10.3e %08"PRIX32" %1d%1c %1c %1c", + UTI_TimeToLogForm(sample.time.tv_sec), + UTI_IPToString(&inst->remote_addr.ip_addr), + leap_chars[pkt_leap], + message->stratum, + test1, test2, test3, test5, test6, test7, testA, testB, testC, testD, + inst->local_poll, message->poll, + inst->poll_score, + sample.offset, sample.peer_delay, sample.peer_dispersion, + pkt_root_delay, pkt_root_dispersion, pkt_refid, + NTP_LVM_TO_MODE(message->lvm), interleaved_packet ? 'I' : 'B', + tss_chars[local_transmit.source], + tss_chars[local_receive.source]); + } + + return good_packet; +} + +/* ================================================== */ +/* From RFC 5905, the standard handling of received packets, depending + on the mode of the packet and of the source, is : + + +------------------+---------------------------------------+ + | | Packet Mode | + +------------------+-------+-------+-------+-------+-------+ + | Association Mode | 1 | 2 | 3 | 4 | 5 | + +------------------+-------+-------+-------+-------+-------+ + | No Association 0 | NEWPS | DSCRD | FXMIT | MANY | NEWBC | + | Symm. Active 1 | PROC | PROC | DSCRD | DSCRD | DSCRD | + | Symm. Passive 2 | PROC | ERR | DSCRD | DSCRD | DSCRD | + | Client 3 | DSCRD | DSCRD | DSCRD | PROC | DSCRD | + | Server 4 | DSCRD | DSCRD | DSCRD | DSCRD | DSCRD | + | Broadcast 5 | DSCRD | DSCRD | DSCRD | DSCRD | DSCRD | + | Bcast Client 6 | DSCRD | DSCRD | DSCRD | DSCRD | PROC | + +------------------+-------+-------+-------+-------+-------+ + + Association mode 0 is implemented in NCR_ProcessRxUnknown(), other modes + in NCR_ProcessRxKnown(). + + Broadcast, manycast and ephemeral symmetric passive associations are not + supported yet. + */ + +/* ================================================== */ +/* This routine is called when a new packet arrives off the network, + and it relates to a source we have an ongoing protocol exchange with */ + +int +NCR_ProcessRxKnown(NCR_Instance inst, NTP_Local_Address *local_addr, + NTP_Local_Timestamp *rx_ts, NTP_Packet *message, int length) +{ + int proc_packet, proc_as_unknown; + NTP_PacketInfo info; + + inst->report.total_rx_count++; + + if (!parse_packet(message, length, &info)) + return 0; + + proc_packet = 0; + proc_as_unknown = 0; + + /* Now, depending on the mode we decide what to do */ + switch (info.mode) { + case MODE_ACTIVE: + switch (inst->mode) { + case MODE_ACTIVE: + /* Ordinary symmetric peering */ + proc_packet = 1; + break; + case MODE_PASSIVE: + /* In this software this case should not arise, we don't + support unconfigured peers */ + break; + case MODE_CLIENT: + /* This is where we have the remote configured as a server and he has + us configured as a peer, process as from an unknown source */ + proc_as_unknown = 1; + break; + default: + /* Discard */ + break; + } + break; + + case MODE_PASSIVE: + switch (inst->mode) { + case MODE_ACTIVE: + /* This would arise if we have the remote configured as a peer and + he does not have us configured */ + proc_packet = 1; + break; + case MODE_PASSIVE: + /* Error condition in RFC 5905 */ + break; + default: + /* Discard */ + break; + } + break; + + case MODE_CLIENT: + /* If message is client mode, we just respond with a server mode + packet, regardless of what we think the remote machine is + supposed to be. However, even though this is a configured + peer or server, we still implement access restrictions on + client mode operation. + + This copes with the case for an isolated network where one + machine is set by eye and is used as the master, with the + other machines pointed at it. If the master goes down, we + want to be able to reset its time at startup by relying on + one of the secondaries to flywheel it. The behaviour coded here + is required in the secondaries to make this possible. */ + + proc_as_unknown = 1; + break; + + case MODE_SERVER: + switch (inst->mode) { + case MODE_CLIENT: + /* Standard case where he's a server and we're the client */ + proc_packet = 1; + break; + default: + /* Discard */ + break; + } + break; + + case MODE_BROADCAST: + /* Just ignore these */ + break; + + default: + /* Obviously ignore */ + break; + } + + if (proc_packet) { + /* Check if the reply was received by the socket that sent the request */ + if (local_addr->sock_fd != inst->local_addr.sock_fd) { + DEBUG_LOG("Packet received by wrong socket %d (expected %d)", + local_addr->sock_fd, inst->local_addr.sock_fd); + return 0; + } + + /* Ignore packets from offline sources */ + if (inst->opmode == MD_OFFLINE || inst->tx_suspended) { + DEBUG_LOG("Packet from offline source"); + return 0; + } + + return process_response(inst, local_addr, rx_ts, message, &info); + } else if (proc_as_unknown) { + NCR_ProcessRxUnknown(&inst->remote_addr, local_addr, rx_ts, message, length); + /* It's not a reply to our request, don't return success */ + return 0; + } else { + DEBUG_LOG("NTP packet discarded mode=%d our_mode=%u", (int)info.mode, inst->mode); + return 0; + } +} + +/* ================================================== */ +/* This routine is called when a new packet arrives off the network, + and it relates to a source we don't know (not our server or peer) */ + +void +NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr, + NTP_Local_Timestamp *rx_ts, NTP_Packet *message, int length) +{ + NTP_PacketInfo info; + NTP_Mode my_mode; + NTP_Local_Timestamp local_tx, *tx_ts; + NTP_int64 ntp_rx, *local_ntp_rx; + int log_index, interleaved, poll, version; + uint32_t kod; + + /* Ignore the packet if it wasn't received by server socket */ + if (!NIO_IsServerSocket(local_addr->sock_fd)) { + DEBUG_LOG("NTP request packet received by client socket %d", local_addr->sock_fd); + return; + } + + if (!parse_packet(message, length, &info)) + return; + + if (!ADF_IsAllowed(access_auth_table, &remote_addr->ip_addr)) { + DEBUG_LOG("NTP packet received from unauthorised host %s", + UTI_IPToString(&remote_addr->ip_addr)); + return; + } + + switch (info.mode) { + case MODE_ACTIVE: + /* We are symmetric passive, even though we don't ever lock to him */ + my_mode = MODE_PASSIVE; + break; + case MODE_CLIENT: + /* Reply with server packet */ + my_mode = MODE_SERVER; + break; + case MODE_UNDEFINED: + /* Check if it is an NTPv1 client request (NTPv1 packets have a reserved + field instead of the mode field and the actual mode is determined from + the port numbers). Don't ever respond with a mode 0 packet! */ + if (info.version == 1 && remote_addr->port != NTP_PORT) { + my_mode = MODE_SERVER; + break; + } + /* Fall through */ + default: + /* Discard */ + DEBUG_LOG("NTP packet discarded mode=%d", (int)info.mode); + return; + } + + kod = 0; + log_index = CLG_LogServiceAccess(CLG_NTP, &remote_addr->ip_addr, &rx_ts->ts); + + /* Don't reply to all requests if the rate is excessive */ + if (log_index >= 0 && CLG_LimitServiceRate(CLG_NTP, log_index)) { + DEBUG_LOG("NTP packet discarded to limit response rate"); + return; + } + + /* Check authentication */ + if (!NAU_CheckRequestAuth(message, &info, &kod)) { + DEBUG_LOG("NTP packet failed auth mode=%d kod=%"PRIx32, (int)info.auth.mode, kod); + + /* Don't respond unless a non-zero KoD was returned */ + if (kod == 0) + return; + } else if (info.auth.mode != NTP_AUTH_NONE && info.auth.mode != NTP_AUTH_MSSNTP) { + CLG_LogAuthNtpRequest(); + } + + local_ntp_rx = NULL; + tx_ts = NULL; + interleaved = 0; + + /* Handle requests formed in the interleaved mode. As an optimisation to + avoid saving all receive timestamps, require that the origin timestamp + has the lowest bit equal to 1, which indicates it was set to one of our + receive timestamps instead of transmit timestamps or zero. Respond in the + interleaved mode if the receive timestamp is found and it has a non-zero + transmit timestamp (this is verified in transmit_packet()). For a new + client starting with a zero origin timestamp, the third response is the + earliest one that can be interleaved. */ + if (kod == 0 && log_index >= 0 && info.version == 4 && + message->originate_ts.lo & htonl(1) && + UTI_CompareNtp64(&message->receive_ts, &message->transmit_ts) != 0) { + ntp_rx = message->originate_ts; + local_ntp_rx = &ntp_rx; + UTI_ZeroTimespec(&local_tx.ts); + interleaved = CLG_GetNtpTxTimestamp(&ntp_rx, &local_tx.ts); + + tx_ts = &local_tx; + if (interleaved) + CLG_DisableNtpTimestamps(&ntp_rx); + } + + /* Suggest the client to increase its polling interval if it indicates + the interval is shorter than the rate limiting interval */ + poll = CLG_GetNtpMinPoll(); + poll = MAX(poll, message->poll); + + /* Respond with the same version */ + version = info.version; + + /* Send a reply */ + if (!transmit_packet(my_mode, interleaved, poll, version, kod, info.ext_field_flags, NULL, + &message->receive_ts, &message->transmit_ts, + rx_ts, tx_ts, local_ntp_rx, NULL, remote_addr, local_addr, + message, &info)) + return; + + if (local_ntp_rx) + CLG_SaveNtpTimestamps(local_ntp_rx, tx_ts ? &tx_ts->ts : NULL); +} + +/* ================================================== */ + +static void +update_tx_timestamp(NTP_Local_Timestamp *tx_ts, NTP_Local_Timestamp *new_tx_ts, + NTP_int64 *local_ntp_rx, NTP_int64 *local_ntp_tx, NTP_Packet *message) +{ + double delay; + + if (UTI_IsZeroTimespec(&tx_ts->ts)) { + DEBUG_LOG("Unexpected TX update"); + return; + } + + /* Check if this is the last packet that was sent */ + if ((local_ntp_rx && UTI_CompareNtp64(&message->receive_ts, local_ntp_rx)) || + (local_ntp_tx && UTI_CompareNtp64(&message->transmit_ts, local_ntp_tx))) { + DEBUG_LOG("RX/TX timestamp mismatch"); + return; + } + + delay = UTI_DiffTimespecsToDouble(&new_tx_ts->ts, &tx_ts->ts); + + if (delay < 0.0 || delay > MAX_TX_DELAY) { + DEBUG_LOG("Unacceptable TX delay %.9f", delay); + return; + } + + *tx_ts = *new_tx_ts; + + DEBUG_LOG("Updated TX timestamp delay=%.9f", delay); +} + +/* ================================================== */ + +void +NCR_ProcessTxKnown(NCR_Instance inst, NTP_Local_Address *local_addr, + NTP_Local_Timestamp *tx_ts, NTP_Packet *message, int length) +{ + NTP_PacketInfo info; + + if (!parse_packet(message, length, &info)) + return; + + /* Server and passive mode packets are responses to unknown sources */ + if (info.mode != MODE_CLIENT && info.mode != MODE_ACTIVE) { + NCR_ProcessTxUnknown(&inst->remote_addr, local_addr, tx_ts, message, length); + return; + } + + update_tx_timestamp(&inst->local_tx, tx_ts, &inst->local_ntp_rx, &inst->local_ntp_tx, + message); +} + +/* ================================================== */ + +void +NCR_ProcessTxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr, + NTP_Local_Timestamp *tx_ts, NTP_Packet *message, int length) +{ + NTP_Local_Timestamp old_tx, new_tx; + NTP_int64 *local_ntp_rx; + NTP_PacketInfo info; + + if (!parse_packet(message, length, &info)) + return; + + if (info.mode == MODE_BROADCAST) + return; + + if (SMT_IsEnabled() && info.mode == MODE_SERVER) + UTI_AddDoubleToTimespec(&tx_ts->ts, SMT_GetOffset(&tx_ts->ts), &tx_ts->ts); + + local_ntp_rx = &message->receive_ts; + new_tx = *tx_ts; + + if (!CLG_GetNtpTxTimestamp(local_ntp_rx, &old_tx.ts)) + return; + + /* Undo a clock adjustment between the RX and TX timestamps to minimise error + in the delay measured by the client */ + CLG_UndoNtpTxTimestampSlew(local_ntp_rx, &new_tx.ts); + + update_tx_timestamp(&old_tx, &new_tx, local_ntp_rx, NULL, message); + + CLG_UpdateNtpTxTimestamp(local_ntp_rx, &new_tx.ts); +} + +/* ================================================== */ + +void +NCR_SlewTimes(NCR_Instance inst, struct timespec *when, double dfreq, double doffset) +{ + double delta; + + if (!UTI_IsZeroTimespec(&inst->local_rx.ts)) + UTI_AdjustTimespec(&inst->local_rx.ts, when, &inst->local_rx.ts, &delta, dfreq, doffset); + if (!UTI_IsZeroTimespec(&inst->local_tx.ts)) + UTI_AdjustTimespec(&inst->local_tx.ts, when, &inst->local_tx.ts, &delta, dfreq, doffset); + if (!UTI_IsZeroTimespec(&inst->prev_local_tx.ts)) + UTI_AdjustTimespec(&inst->prev_local_tx.ts, when, &inst->prev_local_tx.ts, &delta, dfreq, + doffset); + if (!UTI_IsZeroTimespec(&inst->init_local_rx.ts)) + UTI_AdjustTimespec(&inst->init_local_rx.ts, when, &inst->init_local_rx.ts, &delta, dfreq, + doffset); + + if (inst->filter) + SPF_SlewSamples(inst->filter, when, dfreq, doffset); +} + +/* ================================================== */ + +static void +set_connectivity(NCR_Instance inst, SRC_Connectivity connectivity) +{ + if (connectivity == SRC_MAYBE_ONLINE) + connectivity = NIO_IsServerConnectable(&inst->remote_addr) ? SRC_ONLINE : SRC_OFFLINE; + + switch (connectivity) { + case SRC_ONLINE: + switch (inst->opmode) { + case MD_ONLINE: + /* Nothing to do */ + break; + case MD_OFFLINE: + inst->opmode = MD_ONLINE; + NCR_ResetInstance(inst); + start_initial_timeout(inst); + if (inst->auto_iburst) + NCR_InitiateSampleBurst(inst, IBURST_GOOD_SAMPLES, IBURST_TOTAL_SAMPLES); + break; + case MD_BURST_WAS_ONLINE: + /* Will revert */ + break; + case MD_BURST_WAS_OFFLINE: + inst->opmode = MD_BURST_WAS_ONLINE; + break; + default: + assert(0); + } + break; + case SRC_OFFLINE: + switch (inst->opmode) { + case MD_ONLINE: + take_offline(inst); + break; + case MD_OFFLINE: + break; + case MD_BURST_WAS_ONLINE: + inst->opmode = MD_BURST_WAS_OFFLINE; + break; + case MD_BURST_WAS_OFFLINE: + break; + default: + assert(0); + } + break; + default: + assert(0); + } +} + +/* ================================================== */ + +void +NCR_SetConnectivity(NCR_Instance inst, SRC_Connectivity connectivity) +{ + OperatingMode prev_opmode; + int was_online, is_online; + + prev_opmode = inst->opmode; + + set_connectivity(inst, connectivity); + + /* Report an important change */ + was_online = prev_opmode == MD_ONLINE || prev_opmode == MD_BURST_WAS_ONLINE; + is_online = inst->opmode == MD_ONLINE || inst->opmode == MD_BURST_WAS_ONLINE; + if (was_online != is_online) + LOG(LOGS_INFO, "Source %s %s", + UTI_IPToString(&inst->remote_addr.ip_addr), is_online ? "online" : "offline"); +} + +/* ================================================== */ + +void +NCR_ModifyMinpoll(NCR_Instance inst, int new_minpoll) +{ + if (new_minpoll < MIN_POLL || new_minpoll > MAX_POLL) + return; + inst->minpoll = new_minpoll; + LOG(LOGS_INFO, "Source %s new minpoll %d", UTI_IPToString(&inst->remote_addr.ip_addr), new_minpoll); + if (inst->maxpoll < inst->minpoll) + NCR_ModifyMaxpoll(inst, inst->minpoll); +} + +/* ================================================== */ + +void +NCR_ModifyMaxpoll(NCR_Instance inst, int new_maxpoll) +{ + if (new_maxpoll < MIN_POLL || new_maxpoll > MAX_POLL) + return; + inst->maxpoll = new_maxpoll; + LOG(LOGS_INFO, "Source %s new maxpoll %d", UTI_IPToString(&inst->remote_addr.ip_addr), new_maxpoll); + if (inst->minpoll > inst->maxpoll) + NCR_ModifyMinpoll(inst, inst->maxpoll); +} + +/* ================================================== */ + +void +NCR_ModifyMaxdelay(NCR_Instance inst, double new_max_delay) +{ + inst->max_delay = CLAMP(0.0, new_max_delay, MAX_MAXDELAY); + LOG(LOGS_INFO, "Source %s new maxdelay %f", + UTI_IPToString(&inst->remote_addr.ip_addr), inst->max_delay); +} + +/* ================================================== */ + +void +NCR_ModifyMaxdelayratio(NCR_Instance inst, double new_max_delay_ratio) +{ + inst->max_delay_ratio = CLAMP(0.0, new_max_delay_ratio, MAX_MAXDELAYRATIO); + LOG(LOGS_INFO, "Source %s new maxdelayratio %f", + UTI_IPToString(&inst->remote_addr.ip_addr), inst->max_delay_ratio); +} + +/* ================================================== */ + +void +NCR_ModifyMaxdelaydevratio(NCR_Instance inst, double new_max_delay_dev_ratio) +{ + inst->max_delay_dev_ratio = CLAMP(0.0, new_max_delay_dev_ratio, MAX_MAXDELAYDEVRATIO); + LOG(LOGS_INFO, "Source %s new maxdelaydevratio %f", + UTI_IPToString(&inst->remote_addr.ip_addr), inst->max_delay_dev_ratio); +} + +/* ================================================== */ + +void +NCR_ModifyMinstratum(NCR_Instance inst, int new_min_stratum) +{ + inst->min_stratum = new_min_stratum; + LOG(LOGS_INFO, "Source %s new minstratum %d", + UTI_IPToString(&inst->remote_addr.ip_addr), new_min_stratum); +} + +/* ================================================== */ + +void +NCR_ModifyPolltarget(NCR_Instance inst, int new_poll_target) +{ + inst->poll_target = new_poll_target; + LOG(LOGS_INFO, "Source %s new polltarget %d", + UTI_IPToString(&inst->remote_addr.ip_addr), new_poll_target); +} + +/* ================================================== */ + +void +NCR_InitiateSampleBurst(NCR_Instance inst, int n_good_samples, int n_total_samples) +{ + + if (inst->mode == MODE_CLIENT) { + + /* We want to prevent burst mode being used on symmetric active + associations - it will play havoc with the peer's sampling + strategy. (This obviously relies on us having the peer + configured that way if he has us configured symmetric active - + but there's not much else we can do.) */ + + switch (inst->opmode) { + case MD_BURST_WAS_OFFLINE: + case MD_BURST_WAS_ONLINE: + /* If already burst sampling, don't start again */ + break; + + case MD_ONLINE: + case MD_OFFLINE: + inst->opmode = inst->opmode == MD_ONLINE ? + MD_BURST_WAS_ONLINE : MD_BURST_WAS_OFFLINE; + inst->burst_good_samples_to_go = n_good_samples; + inst->burst_total_samples_to_go = n_total_samples; + start_initial_timeout(inst); + break; + default: + assert(0); + break; + } + } + +} + +/* ================================================== */ + +void +NCR_ReportSource(NCR_Instance inst, RPT_SourceReport *report, struct timespec *now) +{ + report->poll = get_transmit_poll(inst); + + switch (inst->mode) { + case MODE_CLIENT: + report->mode = RPT_NTP_CLIENT; + break; + case MODE_ACTIVE: + report->mode = RPT_NTP_PEER; + break; + default: + assert(0); + } +} + +/* ================================================== */ + +void +NCR_GetAuthReport(NCR_Instance inst, RPT_AuthReport *report) +{ + NAU_GetReport(inst->auth, report); +} + +/* ================================================== */ + +void +NCR_GetNTPReport(NCR_Instance inst, RPT_NTPReport *report) +{ + *report = inst->report; +} + +/* ================================================== */ + +int +NCR_AddAccessRestriction(IPAddr *ip_addr, int subnet_bits, int allow, int all) + { + ADF_Status status; + + if (allow) { + if (all) { + status = ADF_AllowAll(access_auth_table, ip_addr, subnet_bits); + } else { + status = ADF_Allow(access_auth_table, ip_addr, subnet_bits); + } + } else { + if (all) { + status = ADF_DenyAll(access_auth_table, ip_addr, subnet_bits); + } else { + status = ADF_Deny(access_auth_table, ip_addr, subnet_bits); + } + } + + if (status != ADF_SUCCESS) + return 0; + + /* Keep server sockets open only when an address allowed */ + if (allow) { + NTP_Remote_Address remote_addr; + + if (server_sock_fd4 == INVALID_SOCK_FD && + ADF_IsAnyAllowed(access_auth_table, IPADDR_INET4)) { + remote_addr.ip_addr.family = IPADDR_INET4; + remote_addr.port = 0; + server_sock_fd4 = NIO_OpenServerSocket(&remote_addr); + } + if (server_sock_fd6 == INVALID_SOCK_FD && + ADF_IsAnyAllowed(access_auth_table, IPADDR_INET6)) { + remote_addr.ip_addr.family = IPADDR_INET6; + remote_addr.port = 0; + server_sock_fd6 = NIO_OpenServerSocket(&remote_addr); + } + } else { + if (server_sock_fd4 != INVALID_SOCK_FD && + !ADF_IsAnyAllowed(access_auth_table, IPADDR_INET4)) { + NIO_CloseServerSocket(server_sock_fd4); + server_sock_fd4 = INVALID_SOCK_FD; + } + if (server_sock_fd6 != INVALID_SOCK_FD && + !ADF_IsAnyAllowed(access_auth_table, IPADDR_INET6)) { + NIO_CloseServerSocket(server_sock_fd6); + server_sock_fd6 = INVALID_SOCK_FD; + } + } + + return 1; +} + +/* ================================================== */ + +int +NCR_CheckAccessRestriction(IPAddr *ip_addr) +{ + return ADF_IsAllowed(access_auth_table, ip_addr); +} + +/* ================================================== */ + +void +NCR_IncrementActivityCounters(NCR_Instance inst, int *online, int *offline, + int *burst_online, int *burst_offline) +{ + switch (inst->opmode) { + case MD_BURST_WAS_OFFLINE: + ++*burst_offline; + break; + case MD_BURST_WAS_ONLINE: + ++*burst_online; + break; + case MD_ONLINE: + ++*online; + break; + case MD_OFFLINE: + ++*offline; + break; + default: + assert(0); + break; + } +} + +/* ================================================== */ + +NTP_Remote_Address * +NCR_GetRemoteAddress(NCR_Instance inst) +{ + return &inst->remote_addr; +} + +/* ================================================== */ + +uint32_t +NCR_GetLocalRefid(NCR_Instance inst) +{ + return UTI_IPToRefid(&inst->local_addr.ip_addr); +} + +/* ================================================== */ + +int NCR_IsSyncPeer(NCR_Instance inst) +{ + return SRC_IsSyncPeer(inst->source); +} + +/* ================================================== */ + +void +NCR_DumpAuthData(NCR_Instance inst) +{ + NAU_DumpData(inst->auth); +} + +/* ================================================== */ + +static void +broadcast_timeout(void *arg) +{ + BroadcastDestination *destination; + NTP_int64 orig_ts; + NTP_Local_Timestamp recv_ts; + int poll; + + destination = ARR_GetElement(broadcasts, (long)arg); + poll = round(log(destination->interval) / log(2.0)); + + UTI_ZeroNtp64(&orig_ts); + zero_local_timestamp(&recv_ts); + + transmit_packet(MODE_BROADCAST, 0, poll, NTP_VERSION, 0, 0, destination->auth, + &orig_ts, &orig_ts, &recv_ts, NULL, NULL, NULL, + &destination->addr, &destination->local_addr, NULL, NULL); + + /* Requeue timeout. We don't care if interval drifts gradually. */ + SCH_AddTimeoutInClass(destination->interval, get_separation(poll), SAMPLING_RANDOMNESS, + SCH_NtpBroadcastClass, broadcast_timeout, arg); +} + +/* ================================================== */ + +void +NCR_AddBroadcastDestination(NTP_Remote_Address *addr, int interval) +{ + BroadcastDestination *destination; + + destination = (BroadcastDestination *)ARR_GetNewElement(broadcasts); + + destination->addr = *addr; + destination->local_addr.ip_addr.family = IPADDR_UNSPEC; + destination->local_addr.if_index = INVALID_IF_INDEX; + destination->local_addr.sock_fd = NIO_OpenServerSocket(&destination->addr); + destination->auth = NAU_CreateNoneInstance(); + destination->interval = CLAMP(1, interval, 1 << MAX_POLL); + + SCH_AddTimeoutInClass(destination->interval, MAX_SAMPLING_SEPARATION, SAMPLING_RANDOMNESS, + SCH_NtpBroadcastClass, broadcast_timeout, + (void *)(long)(ARR_GetSize(broadcasts) - 1)); +} |