a29d9ea9da
to get the prototypes or macros are defined in it Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
1599 lines
41 KiB
C
1599 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 = 5;
|
|
tv.tv_usec = 0;
|
|
|
|
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 */
|
|
|
|
tv.tv_sec = 5;
|
|
tv.tv_usec = 0;
|
|
|
|
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;
|
|
}
|