ca6907ccbe
The current calculation easily overflows if the local time is around the unix epoch. I guess it isn't too unusual for devices without RTC. Or, the battery is dead. Or, whatever. This commit avoids the overflow by simply dividing everything by 2. While more sophisticated and precise solutions are possible, I feel that they are overkill for this simple implementation. For example, The unix epoch (1970) is 0x83aa7e8000000000 in 64-bit NTP timestamp. (1900-origin) The timestamp now, as of writing this, is 0xe3cda16b00000000. With the code before this commit, the offset will be: (lldb) p (long long)((0xe3cda16b00000000 - 0x83aa7e8000000000) + (0xe3cda16b00000000 - 0x83aa7e8000000000)) / 2 (long long) $16 = -2295952992316162048 (lldb) with the new code, it would be: (lldb) p (long long)((0xe3cda16b00000000 / 2 - 0x83aa7e8000000000 / 2) + (0xe3cda16b00000 / 2 - 0x83aa7e8000000000 / 2)) (long long) $17 = 6927419044538613760 (lldb) It's the correct offset from the unix epoch: (lldb) p 6927419044538613760 >> 32 (long) $0 = 1612915435 (lldb) spacetanuki% date -r 1612915435 Wed Feb 10 09:03:55 JST 2021 spacetanuki%
1574 lines
40 KiB
C
1574 lines
40 KiB
C
/****************************************************************************
|
|
* netutils/ntpclient/ntpclient.c
|
|
*
|
|
* Copyright (C) 2014, 2016, 2020 Gregory Nutt. All rights reserved.
|
|
* Author: Gregory Nutt <gnutt@nuttx.org>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name NuttX nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* 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 <errno.h>
|
|
#include <debug.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
|
|
};
|
|
|
|
/* 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 */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* 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;
|
|
#ifdef CONFIG_CLOCK_MONOTONIC
|
|
struct timespec curr_monotonic;
|
|
int64_t diffms_real;
|
|
int64_t diffms_mono;
|
|
int64_t diff_diff_ms;
|
|
#endif
|
|
|
|
/* Get the system times */
|
|
|
|
clock_gettime(CLOCK_REALTIME, &curr_realtime);
|
|
|
|
#ifdef CONFIG_CLOCK_MONOTONIC
|
|
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;
|
|
}
|
|
#else
|
|
UNUSED(start_monotonic);
|
|
#endif
|
|
|
|
/* 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, (void *)&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)
|
|
{
|
|
struct ntp_sample_s samples[CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES];
|
|
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');
|
|
|
|
memset(&srvs, 0, sizeof(srvs));
|
|
|
|
/* 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, 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));
|
|
|
|
clock_gettime(CLOCK_REALTIME, &start_realtime);
|
|
#ifdef CONFIG_CLOCK_MONOTONIC
|
|
clock_gettime(CLOCK_MONOTONIC, &start_monotonic);
|
|
#endif
|
|
|
|
/* 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);
|
|
|
|
#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);
|
|
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;
|
|
}
|