nuttx-apps/netutils/ntpclient/ntpclient.c
zhanghongyu 768993fb15 ntpclient: change sendto/recv timeout to Kconfig
Change the timeout period of ntpclient to configurable to reduce the impact
The NTP packet is lost during the first TLS connection after the board is started.

Signed-off-by: zhanghongyu <zhanghongyu@xiaomi.com>
2023-08-03 06:16:54 -07:00

1596 lines
41 KiB
C

/****************************************************************************
* apps/netutils/ntpclient/ntpclient.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sched.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <unistd.h>
#include <netinet/in.h>
#ifdef CONFIG_LIBC_NETDB
# include <netdb.h>
# include <arpa/inet.h>
#endif
#include <nuttx/clock.h>
#include "netutils/ntpclient.h"
#include "ntpv3.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Configuration ************************************************************/
#ifndef CONFIG_HAVE_LONG_LONG
# error "64-bit integer support required for NTP client"
#endif
#if defined(CONFIG_LIBC_NETDB) && !defined(CONFIG_NETUTILS_NTPCLIENT_SERVER)
# error "CONFIG_NETUTILS_NTPCLIENT_SERVER must be provided"
#endif
#if !defined(CONFIG_LIBC_NETDB) && !defined(CONFIG_NETUTILS_NTPCLIENT_SERVERIP)
# error "CONFIG_NETUTILS_NTPCLIENT_SERVERIP must be provided"
#endif
#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 CONFIG_NETUTILS_NTPCLIENT_SERVER
# ifdef CONFIG_NETUTILS_NTPCLIENT_SERVERIP
/* Old config support */
# warning "NTP server hostname not defined, using deprecated server IP address setting"
# define CONFIG_NETUTILS_NTPCLIENT_SERVER \
inet_ntoa(CONFIG_NETUTILS_NTPCLIENT_SERVERIP)
# else
# error "NTP server hostname not defined"
# endif
#endif
/* NTP Time is seconds since 1900. Convert to Unix time which is seconds
* since 1970
*/
#define NTP2UNIX_TRANSLATION 2208988800u
#define NTP_VERSION_V3 3
#define NTP_VERSION_V4 4
#define NTP_VERSION NTP_VERSION_V4
#define MAX_SERVER_SELECTION_RETRIES 3
#ifndef ARRAY_SIZE
# define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
#ifndef STR
# define STR2(x) #x
# define STR(x) STR2(x)
#endif
/****************************************************************************
* Private Types
****************************************************************************/
/* This enumeration describes the state of the NTP daemon */
enum ntpc_daemon_e
{
NTP_NOT_RUNNING = 0,
NTP_STARTED,
NTP_RUNNING,
NTP_STOP_REQUESTED,
NTP_STOPPED
};
/* This type describes the state of the NTP client daemon. Only one
* instance of the NTP daemon is permitted in this implementation.
*/
struct ntpc_daemon_s
{
uint8_t state; /* See enum ntpc_daemon_e */
sem_t lock; /* Used to protect the whole structure */
sem_t sync; /* 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 */
int family; /* Allowed address family */
};
union ntp_addr_u
{
struct sockaddr sa;
#ifdef CONFIG_NET_IPv4
struct sockaddr_in in4;
#endif
#ifdef CONFIG_NET_IPv6
struct sockaddr_in6 in6;
#endif
struct sockaddr_storage ss;
};
/* NTP offset. */
struct ntp_sample_s
{
int64_t offset;
int64_t delay;
union ntp_addr_u srv_addr;
} packet_struct;
/* Server address list. */
struct ntp_servers_s
{
union ntp_addr_u list[CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES];
size_t num;
size_t pos;
FAR char *hostlist_str;
FAR char *hostlist_saveptr;
FAR char *hostnext;
FAR const char *ntp_servers;
};
/* KoD exclusion list. */
struct ntp_kod_exclude_s
{
sq_entry_t node;
union ntp_addr_u addr;
};
/****************************************************************************
* Private Data
****************************************************************************/
/* This type describes the state of the NTP client daemon. Only one
* instance of the NTP daemon is permitted in this implementation. This
* limitation is due only to this global data structure.
*/
static struct ntpc_daemon_s g_ntpc_daemon =
{
NTP_NOT_RUNNING,
SEM_INITIALIZER(1),
SEM_INITIALIZER(0),
-1,
{ NULL, NULL },
AF_UNSPEC, /* Default is both IPv4 and IPv6 */
};
static struct ntp_sample_s g_last_samples
[CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES];
unsigned int g_last_nsamples = 0;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: sample_cmp
****************************************************************************/
static int sample_cmp(FAR const void *_a, FAR const void *_b)
{
FAR const struct ntp_sample_s *a = _a;
FAR const struct ntp_sample_s *b = _b;
int64_t diff = a->offset - b->offset;
if (diff < 0)
{
return -1;
}
else if (diff > 0)
{
return 1;
}
else
{
return 0;
}
}
/****************************************************************************
* Name: int64abs
****************************************************************************/
static inline int64_t int64abs(int64_t value)
{
return value >= 0 ? value : -value;
}
/****************************************************************************
* Name: ntpc_get_compile_timestamp
****************************************************************************/
static time_t ntpc_get_compile_timestamp(void)
{
struct tm tm;
int year;
int day;
int month;
bool unknown = true;
time_t tim;
#ifdef __DATE__
const char *pmonth;
const char *pyear;
const char *pday;
/* Compile date. Format: "MMM DD YYYY", where MMM is month in three letter
* format, DD is day of month (left padded with space if less than ten) and
* YYYY is year. "??? ?? ????" if unknown.
*/
pmonth = __DATE__;
pday = pmonth + 4;
pyear = pmonth + 7;
year = (pyear[0] - '0') * 1000
+ (pyear[1] - '0') * 100
+ (pyear[2] - '0') * 10
+ (pyear[3] - '0') * 1;
day = (pday[1] - '0');
if (pday[0] != ' ')
{
day += (pday[0] - '0') * 10;
}
unknown = false;
switch (pmonth[0])
{
default:
unknown = true;
break;
case 'J':
if (pmonth[1] == 'a') /* Jan */
month = 1;
else if (pmonth[2] == 'n') /* Jun */
month = 6;
else /* Jul */
month = 7;
break;
case 'F': /* Feb */
month = 2;
break;
case 'M':
if (pmonth[2] == 'r') /* Mar */
month = 3;
else /* May */
month = 5;
break;
case 'A':
if (pmonth[1] == 'p') /* Apr */
month = 4;
else /* Aug */
month = 8;
break;
case 'S': /* Sep */
month = 9;
break;
case 'O': /* Oct */
month = 10;
break;
case 'N': /* Nov */
month = 11;
break;
case 'D': /* Dec */
month = 12;
break;
}
#endif
if (unknown)
{
month = 8;
day = 18;
year = 2015;
}
/* Convert date to timestamp. */
memset(&tm, 0, sizeof(tm));
tm.tm_hour = 0;
tm.tm_min = 0;
tm.tm_sec = 0;
tm.tm_mday = day;
tm.tm_mon = month - 1;
tm.tm_year = year - 1900;
tim = mktime(&tm);
/* Reduce by one day to discount timezones. */
tim -= 24 * 60 * 60;
return tim;
}
/****************************************************************************
* Name: ntpc_getuint32
*
* Description:
* Return the big-endian, 4-byte value in network (big-endian) order.
*
****************************************************************************/
static inline uint32_t ntpc_getuint32(FAR const uint8_t *ptr)
{
/* Network order is big-endian; host order is irrelevant */
return (uint32_t)ptr[3] | /* MS byte appears first in data stream */
((uint32_t)ptr[2] << 8) |
((uint32_t)ptr[1] << 16) |
((uint32_t)ptr[0] << 24);
}
/****************************************************************************
* Name: ntpc_getuint64
*
* Description:
* Return the big-endian, 8-byte value in network (big-endian) order.
*
****************************************************************************/
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
****************************************************************************/
static uint32_t ntp_secpart(uint64_t time)
{
/* NTP timestamps are represented as a 64-bit fixed-point number, in
* seconds relative to 0000 UT on 1 January 1900. The integer part is
* in the first 32 bits and the fraction part in the last 32 bits, as
* shown in the following diagram.
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Integer Part |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Fraction Part |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
/* Get seconds part. */
return time >> 32;
}
/****************************************************************************
* Name: ntp_nsecpart
****************************************************************************/
static uint32_t ntp_nsecpart(uint64_t time)
{
/* Get fraction part converted to nanoseconds. */
return ((time & 0xffffffffu) * NSEC_PER_SEC) >> 32;
}
/****************************************************************************
* Name: timespec2ntp
*
* Convert UNIX timespec timestamp to NTP 64-bit fixed point timestamp.
*
****************************************************************************/
static uint64_t timespec2ntp(FAR const struct timespec *ts)
{
uint64_t ntp_time;
/* Set fraction part. */
ntp_time = ((uint64_t)(ts->tv_nsec) << 32) / NSEC_PER_SEC;
DEBUGASSERT((ntp_time >> 32) == 0);
/* Set seconds part. */
ntp_time += (uint64_t)(ts->tv_sec) << 32;
return ntp_time;
}
/****************************************************************************
* Name: ntp_gettime
*
* Get time in NTP epoch, stored in fixed point uint64_t format (upper 32-bit
* seconds, lower 32-bit fraction)
****************************************************************************/
static uint64_t ntp_localtime(void)
{
int err = errno;
struct timespec currts;
/* Get current clock in NTP epoch. */
clock_gettime(CLOCK_REALTIME, &currts);
currts.tv_sec += NTP2UNIX_TRANSLATION;
/* Restore errno. */
errno = err;
return timespec2ntp(&currts);
}
/****************************************************************************
* Name: ntpc_calculate_offset
*
* Description:
* Calculate NTP time offset and round-trip delay
*
****************************************************************************/
static void ntpc_calculate_offset(FAR int64_t *offset, FAR int64_t *delay,
uint64_t local_xmittime,
uint64_t local_recvtime,
FAR const uint8_t *remote_recv,
FAR const uint8_t *remote_xmit)
{
uint64_t remote_recvtime;
uint64_t remote_xmittime;
/* Two timestamps from server, when request was received, and response
* send.
*/
remote_recvtime = ntpc_getuint64(remote_recv);
remote_xmittime = ntpc_getuint64(remote_xmit);
/* Calculate offset of local time compared to remote time.
* See: https://www.eecis.udel.edu/~mills/time.html
* http://nicolas.aimon.fr/2014/12/05/timesync/
*/
*offset = (int64_t)((remote_recvtime / 2 - local_xmittime / 2) +
(remote_xmittime / 2 - local_recvtime / 2));
/* Calculate roundtrip delay. */
*delay = (local_recvtime - local_xmittime) -
(remote_xmittime - remote_recvtime);
}
/****************************************************************************
* Name: ntpc_settime
*
* Description:
* Given the NTP time offset, adjust the system time
*
****************************************************************************/
static void ntpc_settime(int64_t offset, FAR struct timespec *start_realtime,
FAR struct timespec *start_monotonic)
{
struct timespec tp;
struct timespec curr_realtime;
struct timespec curr_monotonic;
int64_t diffms_real;
int64_t diffms_mono;
int64_t diff_diff_ms;
/* Get the system times */
clock_gettime(CLOCK_REALTIME, &curr_realtime);
clock_gettime(CLOCK_MONOTONIC, &curr_monotonic);
/* Check differences between monotonic and realtime. */
diffms_real = curr_realtime.tv_sec - start_realtime->tv_sec;
diffms_real *= 1000;
diffms_real += (int64_t)(curr_realtime.tv_nsec -
start_realtime->tv_nsec) / (1000 * 1000);
diffms_mono = curr_monotonic.tv_sec - start_monotonic->tv_sec;
diffms_mono *= 1000;
diffms_mono += (int64_t)(curr_monotonic.tv_nsec -
start_monotonic->tv_nsec) / (1000 * 1000);
/* Detect if real-time has been altered by other task. */
diff_diff_ms = diffms_real - diffms_mono;
if (diff_diff_ms < 0)
{
diff_diff_ms = -diff_diff_ms;
}
if (diff_diff_ms >= 1000)
{
nwarn("System time altered by other task by %ju msecs, "
"do not apply offset.\n", (intmax_t)diff_diff_ms);
return;
}
/* Apply offset */
tp = curr_realtime;
tp.tv_sec += ntp_secpart(offset);
tp.tv_nsec += ntp_nsecpart(offset);
while (tp.tv_nsec >= NSEC_PER_SEC)
{
tp.tv_nsec -= NSEC_PER_SEC;
tp.tv_sec++;
}
/* Set the system time */
clock_settime(CLOCK_REALTIME, &tp);
ninfo("Set time to %ju.%03ld seconds (offset: %s%lu.%03lu).\n",
(intmax_t)tp.tv_sec, tp.tv_nsec / NSEC_PER_MSEC,
offset < 0 ? "-" : "",
(unsigned long)ntp_secpart(int64abs(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(FAR const union ntp_addr_u *server_addr)
{
FAR struct ntp_kod_exclude_s *entry;
entry = (FAR void *)sq_peek(&g_ntpc_daemon.kod_list);
while (entry)
{
if (memcmp(&entry->addr, server_addr, sizeof(*server_addr)) == 0)
{
return true;
}
entry = (FAR 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(FAR const struct ntp_datagram_s *recv,
FAR const union ntp_addr_u *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(
FAR const struct ntp_datagram_s *xmit,
FAR const struct ntp_datagram_s *recv,
size_t nbytes,
FAR const union ntp_addr_u *xmitaddr,
FAR const union ntp_addr_u *recvaddr,
size_t recvaddrlen)
{
time_t buildtime;
time_t seconds;
if (recvaddr->sa.sa_family != xmitaddr->sa.sa_family)
{
ninfo("wrong address family\n");
return false;
}
#ifdef CONFIG_NET_IPv4
if (recvaddr->sa.sa_family == AF_INET)
{
if (recvaddrlen != sizeof(struct sockaddr_in) ||
xmitaddr->in4.sin_addr.s_addr != recvaddr->in4.sin_addr.s_addr ||
xmitaddr->in4.sin_port != recvaddr->in4.sin_port)
{
ninfo("response from wrong peer\n");
return false;
}
}
#endif
#ifdef CONFIG_NET_IPv6
if (recvaddr->sa.sa_family == AF_INET6)
{
if (recvaddrlen != sizeof(struct sockaddr_in6) ||
memcmp(&xmitaddr->in6.sin6_addr,
&recvaddr->in6.sin6_addr, sizeof(struct in6_addr)) != 0 ||
xmitaddr->in6.sin6_port != recvaddr->in6.sin6_port)
{
ninfo("response from wrong peer\n");
return false;
}
}
#endif /* CONFIG_NET_IPv6 */
if (nbytes < NTP_DATAGRAM_MINSIZE)
{
/* Too short. */
ninfo("too short response\n");
return false;
}
if (GETVN(recv->lvm) != NTP_VERSION_V3 &&
GETVN(recv->lvm) != NTP_VERSION_V4)
{
/* Wrong version. */
ninfo("wrong version: %d\n", GETVN(recv->lvm));
return false;
}
if (GETMODE(recv->lvm) != 4)
{
/* Response not in server mode. */
ninfo("wrong mode: %d\n", GETMODE(recv->lvm));
return false;
}
if (ntp_is_kiss_of_death(recv, xmitaddr))
{
/* KoD, Kiss-o'-Death. Ignore response. */
ninfo("kiss-of-death response\n");
return false;
}
if (GETLI(recv->lvm) == 3)
{
/* Clock not synchronized. */
ninfo("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."
*/
ninfo("xmittimestamp mismatch\n");
return false;
}
if (ntpc_getuint32(recv->reftimestamp) == 0)
{
/* Invalid timestamp. */
ninfo("invalid reftimestamp, 0x%08lx\n",
(unsigned long)ntpc_getuint32(recv->reftimestamp));
return false;
}
if (ntpc_getuint32(recv->recvtimestamp) == 0)
{
/* Invalid timestamp. */
ninfo("invalid recvtimestamp, 0x%08lx\n",
(unsigned long)ntpc_getuint32(recv->recvtimestamp));
return false;
}
if (ntpc_getuint32(recv->xmittimestamp) == 0)
{
/* Invalid timestamp. */
ninfo("invalid xmittimestamp, 0x%08lx\n",
(unsigned long)ntpc_getuint32(recv->xmittimestamp));
return false;
}
if ((int64_t)(ntpc_getuint64(recv->xmittimestamp) -
ntpc_getuint64(recv->recvtimestamp)) < 0)
{
/* Remote received our request after sending response? */
ninfo("invalid xmittimestamp & recvtimestamp pair\n");
return false;
}
buildtime = ntpc_get_compile_timestamp();
seconds = ntpc_getuint32(recv->recvtimestamp);
if (seconds > NTP2UNIX_TRANSLATION)
{
seconds -= NTP2UNIX_TRANSLATION;
}
if (seconds < buildtime)
{
/* Invalid timestamp. */
ninfo("invalid recvtimestamp, 0x%08lx\n",
(unsigned long)ntpc_getuint32(recv->recvtimestamp));
return false;
}
return true;
}
/****************************************************************************
* Name: ntpc_create_dgram_socket
****************************************************************************/
static int ntpc_create_dgram_socket(int domain)
{
struct timeval tv;
int ret;
int sd;
int err;
/* Create a datagram socket */
sd = socket(domain, SOCK_DGRAM, 0);
if (sd < 0)
{
err = errno;
nerr("ERROR: socket failed: %d\n", err);
errno = err;
return sd;
}
/* Setup a send timeout on the socket */
tv.tv_sec = CONFIG_NETUTILS_NTPCLIENT_TIMEOUT_MS / 1000;
tv.tv_usec = CONFIG_NETUTILS_NTPCLIENT_TIMEOUT_MS % 1000;
ret = setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(struct timeval));
if (ret < 0)
{
err = errno;
nerr("ERROR: setsockopt(SO_SNDTIMEO) failed: %d\n", errno);
goto err_close;
}
/* Setup a receive timeout on the socket */
ret = setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval));
if (ret < 0)
{
err = errno;
nerr("ERROR: setsockopt(SO_RCVTIMEO) failed: %d\n", errno);
goto err_close;
}
return sd;
err_close:
close(sd);
errno = err;
return ret;
}
/****************************************************************************
* Name: ntp_gethostip_multi
****************************************************************************/
static int ntp_gethostip_multi(FAR const char *hostname,
FAR union ntp_addr_u *ipaddr, size_t nipaddr)
{
struct addrinfo hints;
FAR struct addrinfo *info;
FAR struct addrinfo *next;
int ret;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = g_ntpc_daemon.family;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_NUMERICSERV;
ret = getaddrinfo(hostname, STR(CONFIG_NETUTILS_NTPCLIENT_PORTNO),
&hints, &info);
if (ret != OK)
{
nerr("ERROR: getaddrinfo(%s): %d: %s\n", hostname,
ret, gai_strerror(ret));
return ERROR;
}
ret = 0;
for (next = info; next != NULL; next = next->ai_next)
{
if (ret >= nipaddr)
{
break;
}
memcpy(ipaddr, next->ai_addr, next->ai_addrlen);
ret++;
ipaddr++;
}
freeaddrinfo(info);
return ret;
}
/****************************************************************************
* Name: ntp_get_next_hostip
****************************************************************************/
static int ntp_get_next_hostip(FAR struct ntp_servers_s *srvs,
FAR union ntp_addr_u *addr)
{
int ret;
if (srvs->pos >= srvs->num)
{
FAR char *hostname;
srvs->pos = 0;
srvs->num = 0;
/* Get next hostname. */
if (srvs->hostnext == NULL)
{
if (srvs->hostlist_str)
{
free(srvs->hostlist_str);
srvs->hostlist_str = NULL;
}
/* Allocate hostname list buffer */
srvs->hostlist_str = strdup(srvs->ntp_servers);
if (!srvs->hostlist_str)
{
return ERROR;
}
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 = ntp_gethostip_multi(hostname, srvs->list,
ARRAY_SIZE(srvs->list));
if (ret <= 0)
{
return ERROR;
}
srvs->num = ret;
}
*addr = srvs->list[srvs->pos++];
return OK;
}
/****************************************************************************
* Name: ntpc_get_ntp_sample
****************************************************************************/
static int ntpc_get_ntp_sample(FAR struct ntp_servers_s *srvs,
FAR struct ntp_sample_s *samples,
int curr_idx)
{
FAR struct ntp_sample_s *sample = &samples[curr_idx];
uint64_t xmit_time, recv_time;
union ntp_addr_u server;
union ntp_addr_u recvaddr;
struct ntp_datagram_s xmit;
struct ntp_datagram_s recv;
socklen_t socklen;
ssize_t nbytes;
int errval;
int retry = 0;
int nsamples = curr_idx;
bool addr_ok;
int ret;
int sd = -1;
int i;
/* Setup an ntp_addr_u with information about the server we are
* going to ask the time from.
*/
memset(&server, 0, sizeof(server));
do
{
addr_ok = true;
ret = ntp_get_next_hostip(srvs, &server);
if (ret < 0)
{
errval = errno;
nerr("ERROR: ntp_get_next_hostip() failed: %d\n", errval);
goto sock_error;
}
/* Make sure that server not in exclusion list. */
if (ntp_address_in_kod_list(&server))
{
if (retry < MAX_SERVER_SELECTION_RETRIES)
{
ninfo("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++)
{
if (memcmp(&server, &samples[i].srv_addr, sizeof(server)) == 0)
{
/* Already have sample from this server, retry DNS. */
ninfo("retry DNS\n");
if (retry < MAX_SERVER_SELECTION_RETRIES)
{
retry++;
addr_ok = false;
break;
}
else
{
/* Accept same server if cannot get DNS for other server. */
break;
}
}
}
}
while (!addr_ok);
/* Open socket. */
sd = ntpc_create_dgram_socket(server.sa.sa_family);
if (sd < 0)
{
errval = errno;
nerr("ERROR: ntpc_create_dgram_socket() failed: %d\n", errval);
goto sock_error;
}
/* Format the transmit datagram */
memset(&xmit, 0, sizeof(xmit));
xmit.lvm = MKLVM(0, NTP_VERSION, 3);
ninfo("Sending a NTPv%d packet\n", NTP_VERSION);
xmit_time = ntp_localtime();
ntpc_setuint64(xmit.xmittimestamp, xmit_time);
socklen = (server.sa.sa_family == AF_INET) ? sizeof(struct sockaddr_in)
: sizeof(struct sockaddr_in6);
ret = sendto(sd, &xmit, sizeof(struct ntp_datagram_s),
0, &server.sa, socklen);
if (ret < 0)
{
errval = errno;
nerr("ERROR: sendto() failed: %d\n", errval);
goto sock_error;
}
/* Attempt to receive a packet (with a timeout that was set up via
* setsockopt() above)
*/
nbytes = recvfrom(sd, &recv, sizeof(struct ntp_datagram_s),
0, &recvaddr.sa, &socklen);
recv_time = ntp_localtime();
/* Check if the received message was long enough to be a valid NTP
* datagram.
*/
if (nbytes > 0 && ntpc_verify_recvd_ntp_datagram(
&xmit, &recv, nbytes, &server, &recvaddr, socklen))
{
close(sd);
sd = -1;
ninfo("Calculate offset\n");
memset(sample, 0, sizeof(struct ntp_sample_s));
sample->srv_addr = server;
ntpc_calculate_offset(&sample->offset, &sample->delay,
xmit_time, recv_time, recv.recvtimestamp,
recv.xmittimestamp);
return OK;
}
else
{
/* Check for errors. Short datagrams are handled as error. */
errval = errno;
if (nbytes >= 0)
{
errval = EMSGSIZE;
}
else
{
nerr("ERROR: recvfrom() failed: %d\n", errval);
}
goto sock_error;
}
sock_error:
if (sd >= 0)
{
close(sd);
sd = -1;
}
errno = errval;
return ERROR;
}
/****************************************************************************
* Name: ntpc_daemon
*
* Description:
* This the the NTP client daemon. This is a *very* minimal
* implementation! An NTP request is and the system clock is set when the
* response is received
*
****************************************************************************/
static int ntpc_daemon(int argc, FAR char **argv)
{
FAR struct ntp_sample_s *samples;
FAR struct ntp_servers_s *srvs;
int exitcode = EXIT_SUCCESS;
int retries = 0;
int nsamples;
int ret;
/* We must always be invoked with a list of servers. */
DEBUGASSERT(argc > 1 && argv[1] != NULL && *argv[1] != '\0');
samples = malloc(sizeof(struct ntp_sample_s) *
CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES);
if (samples == NULL)
{
return EXIT_FAILURE;
}
srvs = calloc(1, sizeof(*srvs));
if (srvs == NULL)
{
free(samples);
return EXIT_FAILURE;
}
/* Indicate that we have started */
g_ntpc_daemon.state = NTP_RUNNING;
sem_post(&g_ntpc_daemon.sync);
/* Here we do the communication with the NTP server. We collect set of
* NTP samples (hopefully from different servers when using DNS) and
* select median time-offset of samples. This is to filter out
* misconfigured server giving wrong timestamps.
*
* NOTE that the scheduler is locked whenever this loop runs. That
* assures both: (1) that there are no asynchronous stop requests and
* (2) that we are not suspended while in critical moments when we about
* to set the new time. This sounds harsh, but this function is suspended
* most of the time either: (1) sending a datagram, (2) receiving a
* datagram, or (3) waiting for the next poll cycle.
*
* TODO: The first datagram that is sent is usually lost. That is because
* the MAC address of the NTP server is not in the ARP table. This is
* particularly bad here because the request will not be sent again until
* the long delay expires leaving the system with bad time for a long time
* initially. Solutions:
*
* 1. Fix send logic so that it assures that the ARP request has been
* sent and the entry is in the ARP table before sending the packet
* (best).
* 2. Add some ad hoc logic here so that there is no delay until at least
* one good time is received.
*/
sched_lock();
while (g_ntpc_daemon.state != NTP_STOP_REQUESTED)
{
struct timespec start_realtime;
struct timespec start_monotonic;
int errval = 0;
int i;
free(srvs->hostlist_str);
memset(srvs, 0, sizeof(*srvs));
srvs->ntp_servers = argv[1];
memset(samples, 0, sizeof(*samples) *
CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES);
clock_gettime(CLOCK_REALTIME, &start_realtime);
clock_gettime(CLOCK_MONOTONIC, &start_monotonic);
/* Collect samples. */
nsamples = 0;
for (i = 0; i < CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES; i++)
{
/* Get next sample. */
ret = ntpc_get_ntp_sample(srvs, samples, nsamples);
if (ret < 0)
{
errval = errno;
}
else
{
++nsamples;
}
}
/* Analyse samples. */
if (nsamples > 0)
{
int64_t offset;
/* Select median offset of samples. */
qsort(samples, nsamples, sizeof(*samples), sample_cmp);
for (i = 0; i < nsamples; i++)
{
ninfo("NTP sample[%d]: offset: %s%lu.%03lu sec, "
"round-trip delay: %s%lu.%03lu sec\n",
i,
samples[i].offset < 0 ? "-" : "",
(unsigned long)ntp_secpart(int64abs(samples[i].offset)),
ntp_nsecpart(int64abs(samples[i].offset))
/ NSEC_PER_MSEC,
samples[i].delay < 0 ? "-" : "",
(unsigned long)ntp_secpart(int64abs(samples[i].delay)),
ntp_nsecpart(int64abs(samples[i].delay))
/ NSEC_PER_MSEC);
}
if ((nsamples % 2) == 1)
{
offset = samples[nsamples / 2].offset;
}
else
{
int64_t offset1 = samples[nsamples / 2].offset;
int64_t offset2 = samples[nsamples / 2 - 1].offset;
/* Average of two middle offsets. */
if (offset1 > 0 && offset2 > 0)
{
offset = ((uint64_t)offset1 + (uint64_t)offset2) / 2;
}
else if (offset1 < 0 && offset2 < 0)
{
offset1 = -offset1;
offset2 = -offset2;
offset = ((uint64_t)offset1 + (uint64_t)offset2) / 2;
offset = -offset;
}
else
{
offset = (offset1 + offset2) / 2;
}
}
/* Adjust system time. */
ntpc_settime(offset, &start_realtime, &start_monotonic);
/* Save samples for ntpc_status() */
sem_wait(&g_ntpc_daemon.lock);
g_last_nsamples = nsamples;
memcpy(&g_last_samples, samples, nsamples * sizeof(*samples));
sem_post(&g_ntpc_daemon.lock);
#ifndef CONFIG_NETUTILS_NTPCLIENT_STAY_ON
/* Configured to exit at success. */
exitcode = EXIT_SUCCESS;
break;
#else
/* A full implementation of an NTP client would require much
* more. I think we can skip most of that here.
*/
if (g_ntpc_daemon.state == NTP_RUNNING)
{
ninfo("Waiting for %d seconds\n",
CONFIG_NETUTILS_NTPCLIENT_POLLDELAYSEC);
sleep(CONFIG_NETUTILS_NTPCLIENT_POLLDELAYSEC);
retries = 0;
}
continue;
#endif
}
/* Exceeded maximum retries? */
if (retries++ >= CONFIG_NETUTILS_NTPCLIENT_RETRIES)
{
nerr("ERROR: too many retries: %d\n", retries - 1);
exitcode = EXIT_FAILURE;
break;
}
/* Is this error a signal? If not, sleep before retry. */
if (errval != EINTR)
{
sleep(1);
}
/* Keep retrying. */
}
/* The NTP client is terminating */
sched_unlock();
g_ntpc_daemon.state = NTP_STOPPED;
sem_post(&g_ntpc_daemon.sync);
free(srvs->hostlist_str);
free(srvs);
free(samples);
return exitcode;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: ntpc_dualstack_family()
*
* Description:
* Set the protocol family used (AF_INET, AF_INET6 or AF_UNSPEC)
*
****************************************************************************/
void ntpc_dualstack_family(int family)
{
sem_wait(&g_ntpc_daemon.lock);
g_ntpc_daemon.family = family;
sem_post(&g_ntpc_daemon.lock);
}
/****************************************************************************
* Name: ntpc_start_with_list
*
* Description:
* Start the NTP daemon
*
* Returned Value:
* On success, the non-negative task ID of the NTPC daemon is returned;
* On failure, a negated errno value is returned.
*
****************************************************************************/
int ntpc_start_with_list(FAR const char *ntp_server_list)
{
FAR char *task_argv[] =
{
(FAR char *)ntp_server_list,
NULL
};
/* Is the NTP in a non-running state? */
sem_wait(&g_ntpc_daemon.lock);
if (g_ntpc_daemon.state == NTP_NOT_RUNNING ||
g_ntpc_daemon.state == NTP_STOPPED)
{
/* Start the NTP daemon */
g_ntpc_daemon.state = NTP_STARTED;
g_ntpc_daemon.pid =
task_create("NTP daemon", CONFIG_NETUTILS_NTPCLIENT_SERVERPRIO,
CONFIG_NETUTILS_NTPCLIENT_STACKSIZE, ntpc_daemon,
task_argv);
/* Handle failures to start the NTP daemon */
if (g_ntpc_daemon.pid < 0)
{
int errval = errno;
DEBUGASSERT(errval > 0);
g_ntpc_daemon.state = NTP_STOPPED;
nerr("ERROR: Failed to start the NTP daemon: %d\n", errval);
sem_post(&g_ntpc_daemon.lock);
return -errval;
}
/* Wait for any daemon state change */
do
{
sem_wait(&g_ntpc_daemon.sync);
}
while (g_ntpc_daemon.state == NTP_STARTED);
}
sem_post(&g_ntpc_daemon.lock);
return g_ntpc_daemon.pid;
}
/****************************************************************************
* Name: ntpc_start
*
* Description:
* Start the NTP daemon
*
* Returned Value:
* On success, the non-negative task ID of the NTPC daemon is returned;
* On failure, a negated errno value is returned.
*
****************************************************************************/
int ntpc_start(void)
{
return ntpc_start_with_list(CONFIG_NETUTILS_NTPCLIENT_SERVER);
}
/****************************************************************************
* Name: ntpc_stop
*
* Description:
* Stop the NTP daemon
*
* Returned Value:
* Zero on success; a negated errno value on failure. The current
* implementation only returns success.
*
****************************************************************************/
int ntpc_stop(void)
{
int ret;
/* Is the NTP in a running state? */
sem_wait(&g_ntpc_daemon.lock);
if (g_ntpc_daemon.state == NTP_STARTED ||
g_ntpc_daemon.state == NTP_RUNNING)
{
/* Yes.. request that the daemon stop. */
g_ntpc_daemon.state = NTP_STOP_REQUESTED;
/* Wait for any daemon state change */
do
{
/* Signal the NTP client */
ret = kill(g_ntpc_daemon.pid,
CONFIG_NETUTILS_NTPCLIENT_SIGWAKEUP);
if (ret < 0)
{
nerr("ERROR: kill pid %d failed: %d\n",
g_ntpc_daemon.pid, errno);
break;
}
/* Wait for the NTP client to respond to the stop request */
sem_wait(&g_ntpc_daemon.sync);
}
while (g_ntpc_daemon.state == NTP_STOP_REQUESTED);
}
sem_post(&g_ntpc_daemon.lock);
return OK;
}
int ntpc_status(struct ntpc_status_s *statusp)
{
unsigned int i;
sem_wait(&g_ntpc_daemon.lock);
statusp->nsamples = g_last_nsamples;
for (i = 0; i < g_last_nsamples; i++)
{
statusp->samples[i].offset = g_last_samples[i].offset;
statusp->samples[i].delay = g_last_samples[i].delay;
statusp->samples[i]._srv_addr_store = g_last_samples[i].srv_addr.ss;
statusp->samples[i].srv_addr = (FAR const struct sockaddr *)
&statusp->samples[i]._srv_addr_store;
}
sem_post(&g_ntpc_daemon.lock);
return OK;
}