| /* |
| chronyd/chronyc - Programs for keeping computer clocks accurate. |
| |
| ********************************************************************** |
| * Copyright (C) Richard P. Curnow 1997-2003 |
| * Copyright (C) Miroslav Lichvar 2011-2012, 2014, 2016, 2020-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. |
| * |
| ********************************************************************** |
| |
| ======================================================================= |
| |
| Functions which manage the pool of NTP sources that we are currently |
| a client of or peering with. |
| |
| */ |
| |
| #include "config.h" |
| |
| #include "sysincl.h" |
| |
| #include "array.h" |
| #include "ntp_sources.h" |
| #include "ntp_core.h" |
| #include "ntp_io.h" |
| #include "util.h" |
| #include "logging.h" |
| #include "local.h" |
| #include "memory.h" |
| #include "nameserv_async.h" |
| #include "privops.h" |
| #include "sched.h" |
| |
| /* ================================================== */ |
| |
| /* Maximum number of sources */ |
| #define MAX_SOURCES 65536 |
| |
| /* Record type private to this file, used to store information about |
| particular sources */ |
| typedef struct { |
| NTP_Remote_Address *remote_addr; /* The address of this source, non-NULL |
| means this slot in table is in use |
| (an IPADDR_ID address means the address |
| is not resolved yet) */ |
| NCR_Instance data; /* Data for the protocol engine for this source */ |
| char *name; /* Name of the source as it was specified |
| (may be an IP address) */ |
| int pool_id; /* ID of the pool from which was this source |
| added or INVALID_POOL */ |
| int tentative; /* Flag indicating there was no valid response |
| received from the source yet */ |
| uint32_t conf_id; /* Configuration ID, which can be shared with |
| different sources in case of a pool */ |
| } SourceRecord; |
| |
| /* Hash table of SourceRecord, its size is a power of two and it's never |
| more than half full */ |
| static ARR_Instance records; |
| |
| /* Number of sources in the hash table */ |
| static int n_sources; |
| |
| /* Flag indicating new sources will be started automatically when added */ |
| static int auto_start_sources = 0; |
| |
| /* Flag indicating a record is currently being modified */ |
| static int record_lock; |
| |
| /* Last assigned address ID */ |
| static uint32_t last_address_id = 0; |
| |
| /* Last assigned configuration ID */ |
| static uint32_t last_conf_id = 0; |
| |
| /* Source scheduled for name resolving (first resolving or replacement) */ |
| struct UnresolvedSource { |
| /* Current address of the source (IPADDR_ID is used for a single source |
| with unknown address and IPADDR_UNSPEC for a pool of sources) */ |
| NTP_Remote_Address address; |
| /* ID of the pool if not a single source */ |
| int pool_id; |
| /* Name to be resolved */ |
| char *name; |
| /* Flag indicating addresses should be used in a random order */ |
| int random_order; |
| /* Next unresolved source in the list */ |
| struct UnresolvedSource *next; |
| }; |
| |
| #define RESOLVE_INTERVAL_UNIT 7 |
| #define MIN_RESOLVE_INTERVAL 2 |
| #define MAX_RESOLVE_INTERVAL 9 |
| #define MIN_REPLACEMENT_INTERVAL 8 |
| |
| static struct UnresolvedSource *unresolved_sources = NULL; |
| static int resolving_interval = 0; |
| static int resolving_restart = 0; |
| static SCH_TimeoutID resolving_id; |
| static struct UnresolvedSource *resolving_source = NULL; |
| static NSR_SourceResolvingEndHandler resolving_end_handler = NULL; |
| |
| #define MAX_POOL_SOURCES 16 |
| #define INVALID_POOL (-1) |
| |
| /* Pool of sources with the same name */ |
| struct SourcePool { |
| /* Number of all sources from the pool */ |
| int sources; |
| /* Number of sources with unresolved address */ |
| int unresolved_sources; |
| /* Number of non-tentative sources */ |
| int confirmed_sources; |
| /* Maximum number of confirmed sources */ |
| int max_sources; |
| }; |
| |
| /* Array of SourcePool (indexed by their ID) */ |
| static ARR_Instance pools; |
| |
| /* Requested update of a source's address */ |
| struct AddressUpdate { |
| NTP_Remote_Address old_address; |
| NTP_Remote_Address new_address; |
| }; |
| |
| /* Update saved when record_lock is true */ |
| static struct AddressUpdate saved_address_update; |
| |
| /* ================================================== */ |
| /* Forward prototypes */ |
| |
| static void resolve_sources(void); |
| static void rehash_records(void); |
| static void handle_saved_address_update(void); |
| static void clean_source_record(SourceRecord *record); |
| static void remove_pool_sources(int pool_id, int tentative, int unresolved); |
| static void remove_unresolved_source(struct UnresolvedSource *us); |
| |
| static void |
| slew_sources(struct timespec *raw, |
| struct timespec *cooked, |
| double dfreq, |
| double doffset, |
| LCL_ChangeType change_type, |
| void *anything); |
| |
| /* ================================================== */ |
| |
| /* Flag indicating whether module is initialised */ |
| static int initialised = 0; |
| |
| /* ================================================== */ |
| |
| static SourceRecord * |
| get_record(unsigned index) |
| { |
| return (SourceRecord *)ARR_GetElement(records, index); |
| } |
| |
| /* ================================================== */ |
| |
| static struct SourcePool * |
| get_pool(unsigned index) |
| { |
| return (struct SourcePool *)ARR_GetElement(pools, index); |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NSR_Initialise(void) |
| { |
| n_sources = 0; |
| initialised = 1; |
| |
| records = ARR_CreateInstance(sizeof (SourceRecord)); |
| rehash_records(); |
| |
| pools = ARR_CreateInstance(sizeof (struct SourcePool)); |
| |
| LCL_AddParameterChangeHandler(slew_sources, NULL); |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NSR_Finalise(void) |
| { |
| NSR_RemoveAllSources(); |
| |
| LCL_RemoveParameterChangeHandler(slew_sources, NULL); |
| |
| ARR_DestroyInstance(records); |
| ARR_DestroyInstance(pools); |
| |
| while (unresolved_sources) |
| remove_unresolved_source(unresolved_sources); |
| |
| initialised = 0; |
| } |
| |
| /* ================================================== */ |
| /* Find a slot matching an IP address. It is assumed that there can |
| only ever be one record for a particular IP address. */ |
| |
| static int |
| find_slot(IPAddr *ip_addr, int *slot) |
| { |
| SourceRecord *record; |
| uint32_t hash; |
| unsigned int i, size; |
| |
| size = ARR_GetSize(records); |
| |
| *slot = 0; |
| |
| switch (ip_addr->family) { |
| case IPADDR_INET4: |
| case IPADDR_INET6: |
| case IPADDR_ID: |
| break; |
| default: |
| return 0; |
| } |
| |
| hash = UTI_IPToHash(ip_addr); |
| |
| for (i = 0; i < size / 2; i++) { |
| /* Use quadratic probing */ |
| *slot = (hash + (i + i * i) / 2) % size; |
| record = get_record(*slot); |
| |
| if (!record->remote_addr) |
| break; |
| |
| if (UTI_CompareIPs(&record->remote_addr->ip_addr, ip_addr, NULL) == 0) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* ================================================== */ |
| /* Find a slot matching an IP address and port. The function returns: |
| 0 => IP not matched, empty slot returned if a valid address was provided |
| 1 => Only IP matched, port doesn't match |
| 2 => Both IP and port matched. */ |
| |
| static int |
| find_slot2(NTP_Remote_Address *remote_addr, int *slot) |
| { |
| if (!find_slot(&remote_addr->ip_addr, slot)) |
| return 0; |
| |
| return get_record(*slot)->remote_addr->port == remote_addr->port ? 2 : 1; |
| } |
| |
| /* ================================================== */ |
| /* Check if hash table of given size is sufficient to contain sources */ |
| |
| static int |
| check_hashtable_size(unsigned int sources, unsigned int size) |
| { |
| return sources * 2 <= size; |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| rehash_records(void) |
| { |
| SourceRecord *temp_records; |
| unsigned int i, old_size, new_size; |
| int slot; |
| |
| assert(!record_lock); |
| |
| old_size = ARR_GetSize(records); |
| |
| temp_records = MallocArray(SourceRecord, old_size); |
| memcpy(temp_records, ARR_GetElements(records), old_size * sizeof (SourceRecord)); |
| |
| /* The size of the hash table is always a power of two */ |
| for (new_size = 1; !check_hashtable_size(n_sources, new_size); new_size *= 2) |
| ; |
| |
| ARR_SetSize(records, new_size); |
| |
| for (i = 0; i < new_size; i++) |
| get_record(i)->remote_addr = NULL; |
| |
| for (i = 0; i < old_size; i++) { |
| if (!temp_records[i].remote_addr) |
| continue; |
| |
| if (find_slot2(temp_records[i].remote_addr, &slot) != 0) |
| assert(0); |
| |
| *get_record(slot) = temp_records[i]; |
| } |
| |
| Free(temp_records); |
| } |
| |
| /* ================================================== */ |
| |
| /* Procedure to add a new source */ |
| static NSR_Status |
| add_source(NTP_Remote_Address *remote_addr, char *name, NTP_Source_Type type, |
| SourceParameters *params, int pool_id, uint32_t conf_id) |
| { |
| SourceRecord *record; |
| int slot; |
| |
| assert(initialised); |
| |
| /* Find empty bin & check that we don't have the address already */ |
| if (find_slot2(remote_addr, &slot) != 0) { |
| return NSR_AlreadyInUse; |
| } else if (!name && !UTI_IsIPReal(&remote_addr->ip_addr)) { |
| /* Name is required for non-real addresses */ |
| return NSR_InvalidName; |
| } else if (n_sources >= MAX_SOURCES) { |
| return NSR_TooManySources; |
| } else { |
| if (remote_addr->ip_addr.family != IPADDR_INET4 && |
| remote_addr->ip_addr.family != IPADDR_INET6 && |
| remote_addr->ip_addr.family != IPADDR_ID) { |
| return NSR_InvalidAF; |
| } else { |
| n_sources++; |
| |
| if (!check_hashtable_size(n_sources, ARR_GetSize(records))) { |
| rehash_records(); |
| if (find_slot2(remote_addr, &slot) != 0) |
| assert(0); |
| } |
| |
| assert(!record_lock); |
| record_lock = 1; |
| |
| record = get_record(slot); |
| assert(!name || !UTI_IsStringIP(name)); |
| record->name = Strdup(name ? name : UTI_IPToString(&remote_addr->ip_addr)); |
| record->data = NCR_CreateInstance(remote_addr, type, params, record->name); |
| record->remote_addr = NCR_GetRemoteAddress(record->data); |
| record->pool_id = pool_id; |
| record->tentative = 1; |
| record->conf_id = conf_id; |
| |
| record_lock = 0; |
| |
| if (record->pool_id != INVALID_POOL) { |
| get_pool(record->pool_id)->sources++; |
| if (!UTI_IsIPReal(&remote_addr->ip_addr)) |
| get_pool(record->pool_id)->unresolved_sources++; |
| } |
| |
| if (auto_start_sources && UTI_IsIPReal(&remote_addr->ip_addr)) |
| NCR_StartInstance(record->data); |
| |
| /* The new instance is allowed to change its address immediately */ |
| handle_saved_address_update(); |
| |
| return NSR_Success; |
| } |
| } |
| } |
| |
| /* ================================================== */ |
| |
| static NSR_Status |
| change_source_address(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr, |
| int replacement) |
| { |
| int slot1, slot2, found; |
| SourceRecord *record; |
| LOG_Severity severity; |
| char *name; |
| |
| found = find_slot2(old_addr, &slot1); |
| if (found != 2) |
| return NSR_NoSuchSource; |
| |
| /* Make sure there is no other source using the new address (with the same |
| or different port), but allow a source to have its port changed */ |
| found = find_slot2(new_addr, &slot2); |
| if (found == 2 || (found != 0 && slot1 != slot2)) |
| return NSR_AlreadyInUse; |
| |
| assert(!record_lock); |
| record_lock = 1; |
| |
| record = get_record(slot1); |
| NCR_ChangeRemoteAddress(record->data, new_addr, !replacement); |
| |
| if (record->remote_addr != NCR_GetRemoteAddress(record->data) || |
| UTI_CompareIPs(&record->remote_addr->ip_addr, &new_addr->ip_addr, NULL) != 0) |
| assert(0); |
| |
| if (!UTI_IsIPReal(&old_addr->ip_addr) && UTI_IsIPReal(&new_addr->ip_addr)) { |
| if (auto_start_sources) |
| NCR_StartInstance(record->data); |
| if (record->pool_id != INVALID_POOL) |
| get_pool(record->pool_id)->unresolved_sources--; |
| } |
| |
| if (!record->tentative) { |
| record->tentative = 1; |
| |
| if (record->pool_id != INVALID_POOL) |
| get_pool(record->pool_id)->confirmed_sources--; |
| } |
| |
| record_lock = 0; |
| |
| name = record->name; |
| severity = UTI_IsIPReal(&old_addr->ip_addr) ? LOGS_INFO : LOGS_DEBUG; |
| |
| if (found == 0) { |
| /* The hash table must be rebuilt for the changed address */ |
| rehash_records(); |
| |
| LOG(severity, "Source %s %s %s (%s)", UTI_IPToString(&old_addr->ip_addr), |
| replacement ? "replaced with" : "changed to", |
| UTI_IPToString(&new_addr->ip_addr), name); |
| } else { |
| LOG(severity, "Source %s (%s) changed port to %d", |
| UTI_IPToString(&new_addr->ip_addr), name, new_addr->port); |
| } |
| |
| return NSR_Success; |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| handle_saved_address_update(void) |
| { |
| if (!UTI_IsIPReal(&saved_address_update.old_address.ip_addr)) |
| return; |
| |
| if (change_source_address(&saved_address_update.old_address, |
| &saved_address_update.new_address, 0) != NSR_Success) |
| /* This is expected to happen only if the old address is wrong */ |
| LOG(LOGS_ERR, "Could not change %s to %s", |
| UTI_IPSockAddrToString(&saved_address_update.old_address), |
| UTI_IPSockAddrToString(&saved_address_update.new_address)); |
| |
| saved_address_update.old_address.ip_addr.family = IPADDR_UNSPEC; |
| } |
| |
| /* ================================================== */ |
| |
| static int |
| replace_source_connectable(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr) |
| { |
| if (!NIO_IsServerConnectable(new_addr)) { |
| DEBUG_LOG("%s not connectable", UTI_IPToString(&new_addr->ip_addr)); |
| return 0; |
| } |
| |
| if (change_source_address(old_addr, new_addr, 1) == NSR_AlreadyInUse) |
| return 0; |
| |
| handle_saved_address_update(); |
| |
| return 1; |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| process_resolved_name(struct UnresolvedSource *us, IPAddr *ip_addrs, int n_addrs) |
| { |
| NTP_Remote_Address old_addr, new_addr; |
| SourceRecord *record; |
| unsigned short first = 0; |
| int i, j; |
| |
| if (us->random_order) |
| UTI_GetRandomBytes(&first, sizeof (first)); |
| |
| for (i = 0; i < n_addrs; i++) { |
| new_addr.ip_addr = ip_addrs[((unsigned int)i + first) % n_addrs]; |
| |
| DEBUG_LOG("(%d) %s", i + 1, UTI_IPToString(&new_addr.ip_addr)); |
| |
| if (us->pool_id != INVALID_POOL) { |
| /* In the pool resolving mode, try to replace a source from |
| the pool which does not have a real address yet */ |
| for (j = 0; j < ARR_GetSize(records); j++) { |
| record = get_record(j); |
| if (!record->remote_addr || record->pool_id != us->pool_id || |
| UTI_IsIPReal(&record->remote_addr->ip_addr)) |
| continue; |
| old_addr = *record->remote_addr; |
| new_addr.port = old_addr.port; |
| if (replace_source_connectable(&old_addr, &new_addr)) |
| ; |
| break; |
| } |
| } else { |
| new_addr.port = us->address.port; |
| if (replace_source_connectable(&us->address, &new_addr)) |
| break; |
| } |
| } |
| } |
| |
| /* ================================================== */ |
| |
| static int |
| is_resolved(struct UnresolvedSource *us) |
| { |
| int slot; |
| |
| if (us->pool_id != INVALID_POOL) { |
| return get_pool(us->pool_id)->unresolved_sources <= 0; |
| } else { |
| /* If the address is no longer present, it was removed or replaced |
| (i.e. resolved) */ |
| return find_slot2(&us->address, &slot) == 0; |
| } |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| resolve_sources_timeout(void *arg) |
| { |
| resolving_id = 0; |
| resolve_sources(); |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *anything) |
| { |
| struct UnresolvedSource *us, *next; |
| |
| us = (struct UnresolvedSource *)anything; |
| |
| assert(us == resolving_source); |
| assert(resolving_id == 0); |
| |
| DEBUG_LOG("%s resolved to %d addrs", us->name, n_addrs); |
| |
| switch (status) { |
| case DNS_TryAgain: |
| break; |
| case DNS_Success: |
| process_resolved_name(us, ip_addrs, n_addrs); |
| break; |
| case DNS_Failure: |
| LOG(LOGS_WARN, "Invalid host %s", us->name); |
| break; |
| default: |
| assert(0); |
| } |
| |
| next = us->next; |
| |
| /* Don't repeat the resolving if it (permanently) failed, it was a |
| replacement of a real address, or all addresses are already resolved */ |
| if (status == DNS_Failure || UTI_IsIPReal(&us->address.ip_addr) || is_resolved(us)) |
| remove_unresolved_source(us); |
| |
| /* If a restart was requested and this was the last source in the list, |
| start with the first source again (if there still is one) */ |
| if (!next && resolving_restart) { |
| next = unresolved_sources; |
| resolving_restart = 0; |
| } |
| |
| resolving_source = next; |
| |
| if (next) { |
| /* Continue with the next source in the list */ |
| DEBUG_LOG("resolving %s", next->name); |
| DNS_Name2IPAddressAsync(next->name, name_resolve_handler, next); |
| } else { |
| /* This was the last source in the list. If some sources couldn't |
| be resolved, try again in exponentially increasing interval. */ |
| if (unresolved_sources) { |
| resolving_interval = CLAMP(MIN_RESOLVE_INTERVAL, resolving_interval + 1, |
| MAX_RESOLVE_INTERVAL); |
| resolving_id = SCH_AddTimeoutByDelay(RESOLVE_INTERVAL_UNIT * (1 << resolving_interval), |
| resolve_sources_timeout, NULL); |
| } else { |
| resolving_interval = 0; |
| } |
| |
| /* This round of resolving is done */ |
| if (resolving_end_handler) |
| (resolving_end_handler)(); |
| } |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| resolve_sources(void) |
| { |
| struct UnresolvedSource *us, *next, *i; |
| |
| assert(!resolving_source); |
| |
| /* Remove sources that don't need to be resolved anymore */ |
| for (i = unresolved_sources; i; i = next) { |
| next = i->next; |
| if (is_resolved(i)) |
| remove_unresolved_source(i); |
| } |
| |
| if (!unresolved_sources) |
| return; |
| |
| PRV_ReloadDNS(); |
| |
| /* Start with the first source in the list, name_resolve_handler |
| will iterate over the rest */ |
| us = unresolved_sources; |
| |
| resolving_source = us; |
| DEBUG_LOG("resolving %s", us->name); |
| DNS_Name2IPAddressAsync(us->name, name_resolve_handler, us); |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| append_unresolved_source(struct UnresolvedSource *us) |
| { |
| struct UnresolvedSource **i; |
| |
| for (i = &unresolved_sources; *i; i = &(*i)->next) |
| ; |
| *i = us; |
| us->next = NULL; |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| remove_unresolved_source(struct UnresolvedSource *us) |
| { |
| struct UnresolvedSource **i; |
| |
| for (i = &unresolved_sources; *i; i = &(*i)->next) { |
| if (*i == us) { |
| *i = us->next; |
| Free(us->name); |
| Free(us); |
| break; |
| } |
| } |
| } |
| |
| /* ================================================== */ |
| |
| static int get_unused_pool_id(void) |
| { |
| struct UnresolvedSource *us; |
| int i; |
| |
| for (i = 0; i < ARR_GetSize(pools); i++) { |
| if (get_pool(i)->sources > 0) |
| continue; |
| |
| /* Make sure there is no name waiting to be resolved using this pool */ |
| for (us = unresolved_sources; us; us = us->next) { |
| if (us->pool_id == i) |
| break; |
| } |
| if (us) |
| continue; |
| |
| return i; |
| } |
| |
| return INVALID_POOL; |
| } |
| |
| /* ================================================== */ |
| |
| NSR_Status |
| NSR_AddSource(NTP_Remote_Address *remote_addr, NTP_Source_Type type, |
| SourceParameters *params, uint32_t *conf_id) |
| { |
| NSR_Status s; |
| |
| s = add_source(remote_addr, NULL, type, params, INVALID_POOL, last_conf_id + 1); |
| if (s != NSR_Success) |
| return s; |
| |
| last_conf_id++; |
| if (conf_id) |
| *conf_id = last_conf_id; |
| |
| return s; |
| } |
| |
| /* ================================================== */ |
| |
| NSR_Status |
| NSR_AddSourceByName(char *name, int port, int pool, NTP_Source_Type type, |
| SourceParameters *params, uint32_t *conf_id) |
| { |
| struct UnresolvedSource *us; |
| struct SourcePool *sp; |
| NTP_Remote_Address remote_addr; |
| int i, new_sources, pool_id; |
| |
| /* If the name is an IP address, add the source with the address directly */ |
| if (UTI_StringToIP(name, &remote_addr.ip_addr)) { |
| remote_addr.port = port; |
| return NSR_AddSource(&remote_addr, type, params, conf_id); |
| } |
| |
| /* Make sure the name is at least printable and has no spaces */ |
| for (i = 0; name[i] != '\0'; i++) { |
| if (!isgraph((unsigned char)name[i])) |
| return NSR_InvalidName; |
| } |
| |
| us = MallocNew(struct UnresolvedSource); |
| us->name = Strdup(name); |
| us->random_order = 0; |
| |
| remote_addr.ip_addr.family = IPADDR_ID; |
| remote_addr.ip_addr.addr.id = ++last_address_id; |
| remote_addr.port = port; |
| |
| if (!pool) { |
| us->pool_id = INVALID_POOL; |
| us->address = remote_addr; |
| new_sources = 1; |
| } else { |
| pool_id = get_unused_pool_id(); |
| if (pool_id != INVALID_POOL) { |
| sp = get_pool(pool_id); |
| } else { |
| sp = ARR_GetNewElement(pools); |
| pool_id = ARR_GetSize(pools) - 1; |
| } |
| |
| sp->sources = 0; |
| sp->unresolved_sources = 0; |
| sp->confirmed_sources = 0; |
| sp->max_sources = CLAMP(1, params->max_sources, MAX_POOL_SOURCES); |
| us->pool_id = pool_id; |
| us->address.ip_addr.family = IPADDR_UNSPEC; |
| new_sources = MIN(2 * sp->max_sources, MAX_POOL_SOURCES); |
| } |
| |
| append_unresolved_source(us); |
| |
| last_conf_id++; |
| if (conf_id) |
| *conf_id = last_conf_id; |
| |
| for (i = 0; i < new_sources; i++) { |
| if (i > 0) |
| remote_addr.ip_addr.addr.id = ++last_address_id; |
| if (add_source(&remote_addr, name, type, params, us->pool_id, last_conf_id) != NSR_Success) |
| return NSR_TooManySources; |
| } |
| |
| return NSR_UnresolvedName; |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NSR_SetSourceResolvingEndHandler(NSR_SourceResolvingEndHandler handler) |
| { |
| resolving_end_handler = handler; |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NSR_ResolveSources(void) |
| { |
| /* Try to resolve unresolved sources now */ |
| if (unresolved_sources) { |
| /* Allow only one resolving to be running at a time */ |
| if (!resolving_source) { |
| if (resolving_id != 0) { |
| SCH_RemoveTimeout(resolving_id); |
| resolving_id = 0; |
| resolving_interval--; |
| } |
| resolve_sources(); |
| } else { |
| /* Try again as soon as the current resolving ends */ |
| resolving_restart = 1; |
| } |
| } else { |
| /* No unresolved sources, we are done */ |
| if (resolving_end_handler) |
| (resolving_end_handler)(); |
| } |
| } |
| |
| /* ================================================== */ |
| |
| void NSR_StartSources(void) |
| { |
| NTP_Remote_Address *addr; |
| unsigned int i; |
| |
| for (i = 0; i < ARR_GetSize(records); i++) { |
| addr = get_record(i)->remote_addr; |
| if (!addr || !UTI_IsIPReal(&addr->ip_addr)) |
| continue; |
| NCR_StartInstance(get_record(i)->data); |
| } |
| } |
| |
| /* ================================================== */ |
| |
| void NSR_AutoStartSources(void) |
| { |
| auto_start_sources = 1; |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| clean_source_record(SourceRecord *record) |
| { |
| assert(record->remote_addr); |
| |
| if (record->pool_id != INVALID_POOL) { |
| struct SourcePool *pool = get_pool(record->pool_id); |
| |
| pool->sources--; |
| if (!UTI_IsIPReal(&record->remote_addr->ip_addr)) |
| pool->unresolved_sources--; |
| if (!record->tentative) |
| pool->confirmed_sources--; |
| if (pool->max_sources > pool->sources) |
| pool->max_sources = pool->sources; |
| } |
| |
| record->remote_addr = NULL; |
| NCR_DestroyInstance(record->data); |
| Free(record->name); |
| |
| n_sources--; |
| } |
| |
| /* ================================================== */ |
| |
| /* Procedure to remove a source. We don't bother whether the port |
| address is matched - we're only interested in removing a record for |
| the right IP address. */ |
| NSR_Status |
| NSR_RemoveSource(IPAddr *address) |
| { |
| int slot; |
| |
| assert(initialised); |
| |
| if (find_slot(address, &slot) == 0) |
| return NSR_NoSuchSource; |
| |
| clean_source_record(get_record(slot)); |
| |
| /* Rehash the table to make sure there are no broken probe sequences. |
| This is costly, but it's not expected to happen frequently. */ |
| |
| rehash_records(); |
| |
| return NSR_Success; |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NSR_RemoveSourcesById(uint32_t conf_id) |
| { |
| SourceRecord *record; |
| unsigned int i; |
| |
| for (i = 0; i < ARR_GetSize(records); i++) { |
| record = get_record(i); |
| if (!record->remote_addr || record->conf_id != conf_id) |
| continue; |
| clean_source_record(record); |
| } |
| |
| rehash_records(); |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NSR_RemoveAllSources(void) |
| { |
| SourceRecord *record; |
| unsigned int i; |
| |
| for (i = 0; i < ARR_GetSize(records); i++) { |
| record = get_record(i); |
| if (!record->remote_addr) |
| continue; |
| clean_source_record(record); |
| } |
| |
| rehash_records(); |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| resolve_source_replacement(SourceRecord *record) |
| { |
| struct UnresolvedSource *us; |
| |
| DEBUG_LOG("trying to replace %s (%s)", |
| UTI_IPToString(&record->remote_addr->ip_addr), record->name); |
| |
| us = MallocNew(struct UnresolvedSource); |
| us->name = Strdup(record->name); |
| /* If there never was a valid reply from this source (e.g. it was a bad |
| replacement), ignore the order of addresses from the resolver to not get |
| stuck to a pair of addresses if the order doesn't change, or a group of |
| IPv4/IPv6 addresses if the resolver prefers inaccessible IP family */ |
| us->random_order = record->tentative; |
| us->pool_id = INVALID_POOL; |
| us->address = *record->remote_addr; |
| |
| append_unresolved_source(us); |
| NSR_ResolveSources(); |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NSR_HandleBadSource(IPAddr *address) |
| { |
| static struct timespec last_replacement; |
| struct timespec now; |
| SourceRecord *record; |
| IPAddr ip_addr; |
| double diff; |
| int slot; |
| |
| if (!find_slot(address, &slot)) |
| return; |
| |
| record = get_record(slot); |
| |
| /* Don't try to replace a source specified by an IP address unless the |
| address changed since the source was added (e.g. by NTS-KE) */ |
| if (UTI_StringToIP(record->name, &ip_addr) && |
| UTI_CompareIPs(&record->remote_addr->ip_addr, &ip_addr, NULL) == 0) |
| return; |
| |
| /* Don't resolve names too frequently */ |
| SCH_GetLastEventTime(NULL, NULL, &now); |
| diff = UTI_DiffTimespecsToDouble(&now, &last_replacement); |
| if (fabs(diff) < RESOLVE_INTERVAL_UNIT * (1 << MIN_REPLACEMENT_INTERVAL)) { |
| DEBUG_LOG("replacement postponed"); |
| return; |
| } |
| last_replacement = now; |
| |
| resolve_source_replacement(record); |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NSR_RefreshAddresses(void) |
| { |
| SourceRecord *record; |
| unsigned int i; |
| |
| for (i = 0; i < ARR_GetSize(records); i++) { |
| record = get_record(i); |
| if (!record->remote_addr) |
| continue; |
| |
| resolve_source_replacement(record); |
| } |
| } |
| |
| /* ================================================== */ |
| |
| NSR_Status |
| NSR_UpdateSourceNtpAddress(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr) |
| { |
| int slot; |
| |
| if (!UTI_IsIPReal(&old_addr->ip_addr) || !UTI_IsIPReal(&new_addr->ip_addr)) |
| return NSR_InvalidAF; |
| |
| if (UTI_CompareIPs(&old_addr->ip_addr, &new_addr->ip_addr, NULL) != 0 && |
| find_slot(&new_addr->ip_addr, &slot)) |
| return NSR_AlreadyInUse; |
| |
| /* If a record is being modified (e.g. by change_source_address(), or the |
| source is just being created), postpone the change to avoid corruption */ |
| |
| if (!record_lock) |
| return change_source_address(old_addr, new_addr, 0); |
| |
| if (UTI_IsIPReal(&saved_address_update.old_address.ip_addr)) |
| return NSR_TooManySources; |
| |
| saved_address_update.old_address = *old_addr; |
| saved_address_update.new_address = *new_addr; |
| |
| return NSR_Success; |
| } |
| |
| /* ================================================== */ |
| |
| static void remove_pool_sources(int pool_id, int tentative, int unresolved) |
| { |
| SourceRecord *record; |
| unsigned int i, removed; |
| |
| for (i = removed = 0; i < ARR_GetSize(records); i++) { |
| record = get_record(i); |
| |
| if (!record->remote_addr || record->pool_id != pool_id) |
| continue; |
| |
| if ((tentative && !record->tentative) || |
| (unresolved && UTI_IsIPReal(&record->remote_addr->ip_addr))) |
| continue; |
| |
| DEBUG_LOG("removing %ssource %s", tentative ? "tentative " : "", |
| UTI_IPToString(&record->remote_addr->ip_addr)); |
| |
| clean_source_record(record); |
| removed++; |
| } |
| |
| if (removed) |
| rehash_records(); |
| } |
| |
| /* ================================================== */ |
| |
| uint32_t |
| NSR_GetLocalRefid(IPAddr *address) |
| { |
| int slot; |
| |
| if (!find_slot(address, &slot)) |
| return 0; |
| |
| return NCR_GetLocalRefid(get_record(slot)->data); |
| } |
| |
| /* ================================================== */ |
| |
| char * |
| NSR_GetName(IPAddr *address) |
| { |
| int slot; |
| |
| if (!find_slot(address, &slot)) |
| return NULL; |
| |
| return get_record(slot)->name; |
| } |
| |
| /* ================================================== */ |
| |
| /* This routine is called by ntp_io when a new packet arrives off the network, |
| possibly with an authentication tail */ |
| void |
| NSR_ProcessRx(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr, |
| NTP_Local_Timestamp *rx_ts, NTP_Packet *message, int length) |
| { |
| SourceRecord *record; |
| struct SourcePool *pool; |
| int slot; |
| |
| assert(initialised); |
| |
| /* Avoid unnecessary lookup if the packet cannot be a response from our |
| source. Otherwise, it must match both IP address and port number. */ |
| if (NTP_LVM_TO_MODE(message->lvm) != MODE_CLIENT && |
| find_slot2(remote_addr, &slot) == 2) { |
| record = get_record(slot); |
| |
| if (!NCR_ProcessRxKnown(record->data, local_addr, rx_ts, message, length)) |
| return; |
| |
| if (record->tentative) { |
| /* This was the first good reply from the source */ |
| record->tentative = 0; |
| |
| if (record->pool_id != INVALID_POOL) { |
| pool = get_pool(record->pool_id); |
| pool->confirmed_sources++; |
| |
| DEBUG_LOG("pool %s has %d confirmed sources", record->name, pool->confirmed_sources); |
| |
| /* If the number of sources from the pool reached the configured |
| maximum, remove the remaining tentative sources */ |
| if (pool->confirmed_sources >= pool->max_sources) |
| remove_pool_sources(record->pool_id, 1, 0); |
| } |
| } |
| } else { |
| NCR_ProcessRxUnknown(remote_addr, local_addr, rx_ts, message, length); |
| } |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NSR_ProcessTx(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr, |
| NTP_Local_Timestamp *tx_ts, NTP_Packet *message, int length) |
| { |
| SourceRecord *record; |
| int slot; |
| |
| /* Avoid unnecessary lookup if the packet cannot be a request to our |
| source. Otherwise, it must match both IP address and port number. */ |
| if (NTP_LVM_TO_MODE(message->lvm) != MODE_SERVER && |
| find_slot2(remote_addr, &slot) == 2) { |
| record = get_record(slot); |
| NCR_ProcessTxKnown(record->data, local_addr, tx_ts, message, length); |
| } else { |
| NCR_ProcessTxUnknown(remote_addr, local_addr, tx_ts, message, length); |
| } |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| slew_sources(struct timespec *raw, |
| struct timespec *cooked, |
| double dfreq, |
| double doffset, |
| LCL_ChangeType change_type, |
| void *anything) |
| { |
| SourceRecord *record; |
| unsigned int i; |
| |
| for (i = 0; i < ARR_GetSize(records); i++) { |
| record = get_record(i); |
| if (record->remote_addr) { |
| if (change_type == LCL_ChangeUnknownStep) { |
| NCR_ResetInstance(record->data); |
| NCR_ResetPoll(record->data); |
| } else { |
| NCR_SlewTimes(record->data, cooked, dfreq, doffset); |
| } |
| } |
| } |
| } |
| |
| /* ================================================== */ |
| |
| int |
| NSR_SetConnectivity(IPAddr *mask, IPAddr *address, SRC_Connectivity connectivity) |
| { |
| SourceRecord *record, *syncpeer; |
| unsigned int i, any; |
| |
| if (connectivity != SRC_OFFLINE) |
| NSR_ResolveSources(); |
| |
| any = 0; |
| syncpeer = NULL; |
| for (i = 0; i < ARR_GetSize(records); i++) { |
| record = get_record(i); |
| if (record->remote_addr) { |
| /* Ignore SRC_MAYBE_ONLINE connectivity change for unspecified unresolved |
| sources as they would always end up in the offline state */ |
| if ((address->family == IPADDR_UNSPEC && |
| (connectivity != SRC_MAYBE_ONLINE || UTI_IsIPReal(&record->remote_addr->ip_addr))) || |
| !UTI_CompareIPs(&record->remote_addr->ip_addr, address, mask)) { |
| any = 1; |
| if (NCR_IsSyncPeer(record->data)) { |
| syncpeer = record; |
| continue; |
| } |
| NCR_SetConnectivity(record->data, connectivity); |
| } |
| } |
| } |
| |
| /* Set the sync peer last to avoid unnecessary reference switching */ |
| if (syncpeer) |
| NCR_SetConnectivity(syncpeer->data, connectivity); |
| |
| return any; |
| } |
| |
| /* ================================================== */ |
| |
| int |
| NSR_ModifyMinpoll(IPAddr *address, int new_minpoll) |
| { |
| int slot; |
| |
| if (!find_slot(address, &slot)) |
| return 0; |
| |
| NCR_ModifyMinpoll(get_record(slot)->data, new_minpoll); |
| return 1; |
| } |
| |
| /* ================================================== */ |
| |
| int |
| NSR_ModifyMaxpoll(IPAddr *address, int new_maxpoll) |
| { |
| int slot; |
| |
| if (!find_slot(address, &slot)) |
| return 0; |
| |
| NCR_ModifyMaxpoll(get_record(slot)->data, new_maxpoll); |
| return 1; |
| } |
| |
| /* ================================================== */ |
| |
| int |
| NSR_ModifyMaxdelay(IPAddr *address, double new_max_delay) |
| { |
| int slot; |
| |
| if (!find_slot(address, &slot)) |
| return 0; |
| |
| NCR_ModifyMaxdelay(get_record(slot)->data, new_max_delay); |
| return 1; |
| } |
| |
| /* ================================================== */ |
| |
| int |
| NSR_ModifyMaxdelayratio(IPAddr *address, double new_max_delay_ratio) |
| { |
| int slot; |
| |
| if (!find_slot(address, &slot)) |
| return 0; |
| |
| NCR_ModifyMaxdelayratio(get_record(slot)->data, new_max_delay_ratio); |
| return 1; |
| } |
| |
| /* ================================================== */ |
| |
| int |
| NSR_ModifyMaxdelaydevratio(IPAddr *address, double new_max_delay_dev_ratio) |
| { |
| int slot; |
| |
| if (!find_slot(address, &slot)) |
| return 0; |
| |
| NCR_ModifyMaxdelaydevratio(get_record(slot)->data, new_max_delay_dev_ratio); |
| return 1; |
| } |
| |
| /* ================================================== */ |
| |
| int |
| NSR_ModifyMinstratum(IPAddr *address, int new_min_stratum) |
| { |
| int slot; |
| |
| if (!find_slot(address, &slot)) |
| return 0; |
| |
| NCR_ModifyMinstratum(get_record(slot)->data, new_min_stratum); |
| return 1; |
| } |
| |
| /* ================================================== */ |
| |
| int |
| NSR_ModifyPolltarget(IPAddr *address, int new_poll_target) |
| { |
| int slot; |
| |
| if (!find_slot(address, &slot)) |
| return 0; |
| |
| NCR_ModifyPolltarget(get_record(slot)->data, new_poll_target); |
| return 1; |
| } |
| |
| /* ================================================== */ |
| |
| int |
| NSR_InitiateSampleBurst(int n_good_samples, int n_total_samples, |
| IPAddr *mask, IPAddr *address) |
| { |
| SourceRecord *record; |
| unsigned int i; |
| int any; |
| |
| any = 0; |
| for (i = 0; i < ARR_GetSize(records); i++) { |
| record = get_record(i); |
| if (record->remote_addr) { |
| if (address->family == IPADDR_UNSPEC || |
| !UTI_CompareIPs(&record->remote_addr->ip_addr, address, mask)) { |
| any = 1; |
| NCR_InitiateSampleBurst(record->data, n_good_samples, n_total_samples); |
| } |
| } |
| } |
| |
| return any; |
| |
| } |
| |
| /* ================================================== */ |
| /* The ip address is assumed to be completed on input, that is how we |
| identify the source record. */ |
| |
| void |
| NSR_ReportSource(RPT_SourceReport *report, struct timespec *now) |
| { |
| int slot; |
| |
| if (find_slot(&report->ip_addr, &slot)) { |
| NCR_ReportSource(get_record(slot)->data, report, now); |
| } else { |
| report->poll = 0; |
| report->latest_meas_ago = 0; |
| } |
| } |
| |
| /* ================================================== */ |
| |
| int |
| NSR_GetAuthReport(IPAddr *address, RPT_AuthReport *report) |
| { |
| int slot; |
| |
| if (!find_slot(address, &slot)) |
| return 0; |
| |
| NCR_GetAuthReport(get_record(slot)->data, report); |
| return 1; |
| } |
| |
| /* ================================================== */ |
| /* The ip address is assumed to be completed on input, that is how we |
| identify the source record. */ |
| |
| int |
| NSR_GetNTPReport(RPT_NTPReport *report) |
| { |
| int slot; |
| |
| if (!find_slot(&report->remote_addr, &slot)) |
| return 0; |
| |
| NCR_GetNTPReport(get_record(slot)->data, report); |
| return 1; |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NSR_GetActivityReport(RPT_ActivityReport *report) |
| { |
| SourceRecord *record; |
| unsigned int i; |
| |
| report->online = 0; |
| report->offline = 0; |
| report->burst_online = 0; |
| report->burst_offline = 0; |
| report->unresolved = 0; |
| |
| for (i = 0; i < ARR_GetSize(records); i++) { |
| record = get_record(i); |
| if (!record->remote_addr) |
| continue; |
| |
| if (!UTI_IsIPReal(&record->remote_addr->ip_addr)) { |
| report->unresolved++; |
| } else { |
| NCR_IncrementActivityCounters(record->data, &report->online, &report->offline, |
| &report->burst_online, &report->burst_offline); |
| } |
| } |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NSR_DumpAuthData(void) |
| { |
| SourceRecord *record; |
| int i; |
| |
| for (i = 0; i < ARR_GetSize(records); i++) { |
| record = get_record(i); |
| if (!record->remote_addr) |
| continue; |
| NCR_DumpAuthData(record->data); |
| } |
| } |