| /* |
| chronyd/chronyc - Programs for keeping computer clocks accurate. |
| |
| ********************************************************************** |
| * Copyright (C) Richard P. Curnow 1997-2003 |
| * Copyright (C) Miroslav Lichvar 2009-2021 |
| * |
| * 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 "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 median filter for NTP measurements */ |
| SPF_Instance filter; |
| |
| 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 |
| |
| /* Minimum and maximum allowed poll interval */ |
| #define MIN_POLL -6 |
| #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 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); |
| } |
| |
| /* ================================================== */ |
| |
| 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->filter_length >= 1) |
| result->filter = SPF_CreateInstance(params->filter_length, 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 = result->minpoll; |
| 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; |
| memset(&result->report, 0, sizeof (result->report)); |
| |
| NCR_ResetInstance(result); |
| |
| set_connectivity(result, params->connectivity); |
| |
| 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->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->filter) |
| SPF_DropSamples(instance->filter); |
| } |
| |
| /* ================================================== */ |
| |
| 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) |
| { |
| memset(&inst->report, 0, sizeof (inst->report)); |
| 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); |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| adjust_poll(NCR_Instance inst, double adj) |
| { |
| 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->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); |
| } |
| |
| /* 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_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, filtered_sample_ago; |
| NTP_Sample filtered_sample; |
| int filtered_samples; |
| |
| /* Accumulate the sample to the median filter if it is enabled. When the |
| filter produces a result, check if it is not too old, i.e. the filter did |
| not miss too many samples due to missing responses or failing tests. */ |
| if (inst->filter) { |
| SPF_AccumulateSample(inst->filter, sample); |
| |
| filtered_samples = SPF_GetNumberOfSamples(inst->filter); |
| |
| if (!SPF_GetFilteredSample(inst->filter, &filtered_sample)) |
| return; |
| |
| filtered_sample_ago = UTI_DiffTimespecsToDouble(&sample->time, &filtered_sample.time); |
| |
| if (filtered_sample_ago > SOURCE_REACH_BITS / 2 * filtered_samples * |
| UTI_Log2ToDouble(inst->local_poll)) { |
| DEBUG_LOG("filtered sample dropped ago=%f poll=%d", filtered_sample_ago, |
| inst->local_poll); |
| return; |
| } |
| |
| sample = &filtered_sample; |
| } |
| |
| /* 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, 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_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 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 */ |
| 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 (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); |
| } |
| |
| /* 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.remote_addr = inst->remote_addr.ip_addr; |
| inst->report.local_addr = inst->local_addr.ip_addr; |
| inst->report.remote_port = inst->remote_addr.port; |
| 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++; |
| } |
| |
| /* 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)); |
| } |