diff --git a/apps/netutils/ntpclient/Kconfig b/apps/netutils/ntpclient/Kconfig index b25012fc365eb52cba8b301f710797a90c2cd08e..b9faae86cb704b218433e7c6ba46bf172e6f5cf9 100644 --- a/apps/netutils/ntpclient/Kconfig +++ b/apps/netutils/ntpclient/Kconfig @@ -14,7 +14,10 @@ if NETUTILS_NTPCLIENT config NETUTILS_NTPCLIENT_SERVERHOSTNAME string "NTP server hostnames" - default "0.pool.ntp.org" + default "0.pool.ntp.org;1.pool.ntp.org;2.pool.ntp.org" + ---help--- + List of NTP server hostnames to use. Server names need to be separated + by ';'. config NETUTILS_NTPCLIENT_PORTNO int "NTP server port number" diff --git a/apps/netutils/ntpclient/ntpclient.c b/apps/netutils/ntpclient/ntpclient.c index d4a8594bc4195de001b31818f36f32b0e8a8920a..747e726811e9b4eb4f71e063be8cc341cf005b5d 100644 --- a/apps/netutils/ntpclient/ntpclient.c +++ b/apps/netutils/ntpclient/ntpclient.c @@ -80,10 +80,17 @@ */ #define NTP2UNIX_TRANLSLATION 2208988800u -#define NTP_VERSION 3 + +#define NTP_VERSION_V3 3 +#define NTP_VERSION_V4 4 +#define NTP_VERSION NTP_VERSION_V4 + +#define MAX_SERVER_SELECTION_RETRIES 3 #ifndef CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES # define CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES 5 +#elif CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES < 1 +# error "NTP sample number below 1, invalid configuration" #endif #ifndef ARRAY_SIZE @@ -124,6 +131,7 @@ struct ntpc_daemon_s volatile uint8_t state; /* See enum ntpc_daemon_e */ sem_t interlock; /* Used to synchronize start and stop events */ pid_t pid; /* Task ID of the NTP daemon */ + sq_queue_t kod_list; /* KoD excluded server addresses */ }; /* NTP offset. */ @@ -142,6 +150,17 @@ struct ntp_servers_s in_addr_t list[CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES]; size_t num; size_t pos; + char *hostlist_str; + char *hostlist_saveptr; + char *hostnext; +}; + +/* KoD exclusion list. */ + +struct ntp_kod_exclude_s +{ + sq_entry_t node; + in_addr_t addr; }; /**************************************************************************** @@ -181,7 +200,7 @@ static int sample_cmp(const void *_a, const void *_b) * Name: int64abs ****************************************************************************/ -static int64_t int64abs(int64_t value) +static inline int64_t int64abs(int64_t value) { return value >= 0 ? value : -value; } @@ -320,6 +339,36 @@ static inline uint64_t ntpc_getuint64(FAR const uint8_t *ptr) return ((uint64_t)ntpc_getuint32(ptr) << 32) | ntpc_getuint32(&ptr[4]); } +/**************************************************************************** + * Name: ntpc_setuint32 + * + * Description: + * Write 4-byte value to buffer in network (big-endian) order. + * + ****************************************************************************/ + +static inline void ntpc_setuint32(FAR uint8_t *ptr, uint32_t value) +{ + ptr[3] = (uint8_t)value; + ptr[2] = (uint8_t)(value >> 8); + ptr[1] = (uint8_t)(value >> 16); + ptr[0] = (uint8_t)(value >> 24); +} + +/**************************************************************************** + * Name: ntpc_setuint64 + * + * Description: + * Write 8-byte value to buffer in network (big-endian) order. + * + ****************************************************************************/ + +static inline void ntpc_setuint64(FAR uint8_t *ptr, uint64_t value) +{ + ntpc_setuint32(ptr + 0, (uint32_t)(value >> 32)); + ntpc_setuint32(ptr + 4, (uint32_t)value); +} + /**************************************************************************** * Name: ntp_secpart ****************************************************************************/ @@ -477,11 +526,91 @@ static void ntpc_settime(int64_t offset) ntp_nsecpart(int64abs(offset)) / NSEC_PER_MSEC); } + +/**************************************************************************** + * Name: ntp_address_in_kod_list + * + * Description: Check if address is in KoD KoD exclusion list. + * + ****************************************************************************/ + +static bool ntp_address_in_kod_list(in_addr_t server_addr) +{ + struct ntp_kod_exclude_s *entry; + + entry = (void *)sq_peek(&g_ntpc_daemon.kod_list); + while (entry) + { + if (entry->addr == server_addr) + return true; + + entry = (void *)sq_next(&entry->node); + } + + return false; +} + +/**************************************************************************** + * Name: ntp_is_kiss_of_death + * + * Description: Check if this is KoD response from the server. If it is, + * add server to KoD exclusion list and return 'true'. + * + ****************************************************************************/ + +static bool ntp_is_kiss_of_death(const struct ntp_datagram_s *recv, + in_addr_t server_addr) +{ + /* KoD only specified for v4. */ + + if (GETVN(recv->lvm) != NTP_VERSION_V4) + { + if (recv->stratum == 0) + { + /* Stratum 0 is unspecified on v3, so ignore packet. */ + + return true; + } + else + { + return false; + } + } + + /* KoD message if stratum == 0. */ + + if (recv->stratum != 0) + { + return false; + } + + /* KoD message received. */ + + /* Check if we need to add server to access exclusion list. */ + + if (strncmp((char *)recv->refid, "DENY", 4) == 0 || + strncmp((char *)recv->refid, "RSTR", 4) == 0 || + strncmp((char *)recv->refid, "RATE", 4) == 0) + { + struct ntp_kod_exclude_s *entry; + + entry = calloc(1, sizeof(*entry)); + if (entry) + { + entry->addr = server_addr; + sq_addlast(&entry->node, &g_ntpc_daemon.kod_list); + } + } + + return true; +} + /**************************************************************************** * Name: ntpc_verify_recvd_ntp_datagram ****************************************************************************/ -static bool ntpc_verify_recvd_ntp_datagram(const struct ntp_datagram_s *recv, +static bool ntpc_verify_recvd_ntp_datagram(const struct ntp_datagram_s *xmit, + const struct ntp_datagram_s *recv, size_t nbytes, const struct sockaddr_in *xmitaddr, const struct sockaddr_in *recvaddr, @@ -509,7 +638,7 @@ static bool ntpc_verify_recvd_ntp_datagram(const struct ntp_datagram_s *recv, return false; } - if (GETVN(recv->lvm) != NTP_VERSION) + if (GETVN(recv->lvm) != NTP_VERSION_V3 && GETVN(recv->lvm) != NTP_VERSION_V4) { /* Wrong version. */ @@ -527,6 +656,43 @@ static bool ntpc_verify_recvd_ntp_datagram(const struct ntp_datagram_s *recv, return false; } + if (GETMODE(recv->lvm) != 4) + { + /* Response not in server mode. */ + + svdbg("wrong mode: %d\n", GETMODE(recv->lvm)); + + return false; + } + + if (ntp_is_kiss_of_death(recv, xmitaddr->sin_addr.s_addr)) + { + /* KoD, Kiss-o'-Death. Ignore response. */ + + svdbg("kiss-of-death response.\n"); + + return false; + } + + if (GETLI(recv->lvm) == 3) + { + /* Clock not synchronized. */ + + svdbg("LI: not synchronized\n"); + + return false; + } + + if (memcmp(recv->origtimestamp, xmit->xmittimestamp, 8) != 0) + { + /* "The Originate Timestamp in the server reply should match the + * Transmit Timestamp used in the client request." */ + + svdbg("recv->origtimestamp <=> xmit->xmittimestamp mismatch.\n"); + + return false; + } + if (ntpc_getuint32(recv->reftimestamp) == 0) { /* Invalid timestamp. */ @@ -653,13 +819,55 @@ static int ntp_get_next_hostip(struct ntp_servers_s *srvs, in_addr_t *addr) if (srvs->pos >= srvs->num) { + char *hostname; + srvs->pos = 0; srvs->num = 0; + /* Get next hostname. */ + + if (srvs->hostnext == NULL) + { + if (!srvs->hostlist_str) + { + /* Allocate hostname list buffer */ + + srvs->hostlist_str = + strdup(CONFIG_NETUTILS_NTPCLIENT_SERVERHOSTNAME); + if (!srvs->hostlist_str) + { + return ERROR; + } + } + else + { + /* Reset hostname list buffer */ + + strcpy(srvs->hostlist_str, + CONFIG_NETUTILS_NTPCLIENT_SERVERHOSTNAME); + } + + srvs->hostlist_saveptr = NULL; +#ifndef __clang_analyzer__ /* Silence false 'possible memory leak'. */ + srvs->hostnext = + strtok_r(srvs->hostlist_str, ";", &srvs->hostlist_saveptr); +#endif + } + + hostname = srvs->hostnext; + srvs->hostnext = strtok_r(NULL, ";", &srvs->hostlist_saveptr); + + if (!hostname) + { + /* Invalid configuration. */ + + errno = EINVAL; + return ERROR; + } + /* Refresh DNS for new IP-addresses. */ - ret = dns_gethostip_multi(CONFIG_NETUTILS_NTPCLIENT_SERVERHOSTNAME, - srvs->list, ARRAY_SIZE(srvs->list)); + ret = dns_gethostip_multi(hostname, srvs->list, ARRAY_SIZE(srvs->list)); if (ret <= 0) { return ERROR; @@ -669,6 +877,7 @@ static int ntp_get_next_hostip(struct ntp_servers_s *srvs, in_addr_t *addr) } *addr = srvs->list[srvs->pos++]; + return OK; } @@ -688,7 +897,7 @@ static int ntpc_get_ntp_sample(struct ntp_servers_s *srvs, socklen_t socklen; ssize_t nbytes; int errval; - bool retry = true; + int retry = 0; int nsamples = curr_idx; bool addr_ok; int ret; @@ -701,7 +910,7 @@ static int ntpc_get_ntp_sample(struct ntp_servers_s *srvs, memset(&server, 0, sizeof(struct sockaddr_in)); server.sin_family = AF_INET; - server.sin_port = htons(CONFIG_NETUTILS_NTPCLIENT_PORTNO); + server.sin_port = HTONS(CONFIG_NETUTILS_NTPCLIENT_PORTNO); do { @@ -717,6 +926,25 @@ static int ntpc_get_ntp_sample(struct ntp_servers_s *srvs, goto sock_error; } + /* Make sure that server not in exclusion list. */ + + if (ntp_address_in_kod_list(server.sin_addr.s_addr)) + { + if (retry < MAX_SERVER_SELECTION_RETRIES) + { + svdbg("on KoD list. retry DNS.\n"); + + retry++; + addr_ok = false; + continue; + } + else + { + errval = -EALREADY; + goto sock_error; + } + } + /* Make sure that this sample is from new server. */ for (i = 0; i < nsamples; i++) @@ -727,9 +955,9 @@ static int ntpc_get_ntp_sample(struct ntp_servers_s *srvs, svdbg("retry DNS.\n"); - if (retry) + if (retry < MAX_SERVER_SELECTION_RETRIES) { - retry = false; + retry++; addr_ok = false; break; } @@ -758,11 +986,12 @@ static int ntpc_get_ntp_sample(struct ntp_servers_s *srvs, /* Format the transmit datagram */ memset(&xmit, 0, sizeof(xmit)); - xmit.lvm = MKLVM(0, 3, NTP_VERSION); + xmit.lvm = MKLVM(0, NTP_VERSION, 3); - svdbg("Sending a NTP packet\n"); + svdbg("Sending a NTPv%d packet\n", NTP_VERSION); xmit_time = ntp_localtime(); + ntpc_setuint64(xmit.xmittimestamp, xmit_time); ret = sendto(sd, &xmit, sizeof(struct ntp_datagram_s), 0, (FAR struct sockaddr *)&server, sizeof(struct sockaddr_in)); @@ -789,7 +1018,7 @@ static int ntpc_get_ntp_sample(struct ntp_servers_s *srvs, */ if (nbytes > 0 && ntpc_verify_recvd_ntp_datagram( - &recv, nbytes, &server, &recvaddr, socklen)) + &xmit, &recv, nbytes, &server, &recvaddr, socklen)) { close(sd); sd = -1; @@ -848,12 +1077,18 @@ sock_error: static int ntpc_daemon(int argc, char **argv) { struct ntp_sample_s samples[CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES]; - struct ntp_servers_s srvs = { .num = 0, .pos = 0 }; + struct ntp_servers_s srvs; int exitcode = EXIT_SUCCESS; int retries = 0; int nsamples; int ret; + srvs.num = 0; + srvs.pos = 0; + srvs.hostlist_saveptr = NULL; + srvs.hostlist_str = NULL; + srvs.hostnext = NULL; + /* Indicate that we have started */ g_ntpc_daemon.state = NTP_RUNNING; @@ -1001,6 +1236,7 @@ static int ntpc_daemon(int argc, char **argv) g_ntpc_daemon.state = NTP_STOPPED; sem_post(&g_ntpc_daemon.interlock); + free(srvs.hostlist_str); return exitcode; }