mirror of
https://github.com/apache/nuttx-apps.git
synced 2026-08-02 12:49:03 +00:00
Now that time_t is unconditionally 64-bit (signed int64_t) and the
struct timespec fields tv_sec / tv_nsec are wide enough on their own,
the explicit (uint64_t)/(int64_t)/(int) casts that used to guard the
multiplications and subtractions in *_us / *_ms / *_ns helpers are no
longer needed. Drop them to keep the timekeeping math readable.
In the same spirit, this commit also normalises the printf-style format
specifiers and casts used to print tv_sec / tv_nsec / tv_usec values.
The prior code was a mix of "%d"/"%u"/"%ld"/"%lu"/"%lld" with matching
(int)/(unsigned long)/(long long) casts; some formats truncated time_t
on 32-bit hosts, others mismatched signedness or width. Replace all
such cases with the portable POSIX-recommended forms:
- tv_sec (time_t, signed, impl-defined width) -> %jd + (intmax_t)
- tv_nsec (long, signed) -> %ld (no cast)
- tv_usec (suseconds_t / long) -> %ld (no cast)
Also drop two stale `(FAR const time_t *)&ts.tv_sec` casts that are
unnecessary now that ts.tv_sec is plain time_t.
Arithmetic-cleanup files (existing scope):
- benchmarks/cyclictest/cyclictest.c: timediff_us()
- benchmarks/sd_bench/sd_bench_main.c: get_time_delta_us()
- examples/oneshot/oneshot_main.c: maxus computation
- examples/watchdog/watchdog_main.c: current_time_ms (x2)
- industry/nxmodbus/nxmb_internal.h: nxmb_util_clock_ms()
- netutils/ntpclient/ntpclient.c: timespec2ntp()
- netutils/ptpd/ptpd.c: ptp_adjtime()
- system/dd/dd_main.c: elapsed accounting
- testing/drivers/drivertest/drivertest_posix_timer.c:
get_timestamp()
- testing/drivers/sd_stress/sd_stress_main.c:get_time_delta()
- testing/sched/getprime/getprime_main.c: elapsed accounting
- testing/sched/pthread_mutex_perf/pthread_mutex_perf.c:
timespec_avg()
Printf-format-fix files (new in this revision):
- examples/adjtime/adjtime_main.c
- examples/charger/charger_main.c
- examples/netpkt/netpkt_ethercat.c
- fsutils/mkfatfs/mkfatfs.c
- graphics/tiff/tiff_initialize.c
- netutils/ptpd/ptpd.c
- nshlib/nsh_timcmds.c
- system/coredump/coredump.c
- system/ptpd/ptpd_main.c
- testing/drivers/drivertest/drivertest_oneshot.c
- testing/mm/kasantest/kasantest.c
- testing/ostest/semtimed.c
- testing/sched/pthread_mutex_perf/pthread_mutex_perf.c
- testing/sched/timerjitter/timerjitter.c
- testing/testsuites/kernel/time/cases/clock_test_clock01.c
- testing/testsuites/kernel/time/cases/clock_test_smoke.c
No behavioural change.
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
1823 lines
46 KiB
C
1823 lines
46 KiB
C
/****************************************************************************
|
|
* apps/netutils/ntpclient/ntpclient.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* 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/param.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 <nuttx/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/netlib.h"
|
|
#include "netutils/ntpclient.h"
|
|
|
|
#include "ntpv3.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
/* Configuration ************************************************************/
|
|
|
|
#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
|
|
|
|
#define MAX_RETRY_INTERVAL 120
|
|
|
|
#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
|
|
};
|
|
|
|
enum ntpc_server_source_e
|
|
{
|
|
NTP_SERVER_SOURCE_NONE = 0,
|
|
NTP_SERVER_SOURCE_CONFIG,
|
|
NTP_SERVER_SOURCE_DHCP,
|
|
NTP_SERVER_SOURCE_EXPLICIT
|
|
};
|
|
|
|
/* 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 */
|
|
uint8_t source; /* See enum ntpc_server_source_e */
|
|
bool dhcp_registered;
|
|
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 */
|
|
|
|
/* DHCP-provided server list */
|
|
|
|
FAR char *dhcp_servers;
|
|
};
|
|
|
|
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,
|
|
NTP_SERVER_SOURCE_NONE,
|
|
false,
|
|
SEM_INITIALIZER(1),
|
|
SEM_INITIALIZER(0),
|
|
-1,
|
|
{ NULL, NULL },
|
|
AF_UNSPEC, /* Default is both IPv4 and IPv6 */
|
|
NULL,
|
|
};
|
|
|
|
static struct ntp_sample_s g_last_samples
|
|
[CONFIG_NETUTILS_NTPCLIENT_NUM_SAMPLES];
|
|
unsigned int g_last_nsamples = 0;
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
static int ntpc_daemon(int argc, FAR char **argv);
|
|
#ifdef CONFIG_NETUTILS_DHCPC
|
|
static int ntpc_set_servers_from_dhcp(FAR const char *ntp_server_list);
|
|
|
|
static FAR char *ntpc_dup_server_list(FAR const char *ntp_server_list)
|
|
{
|
|
if (ntp_server_list == NULL || ntp_server_list[0] == '\0')
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return strdup(ntp_server_list);
|
|
}
|
|
#endif
|
|
|
|
static FAR const char *ntpc_select_server_list(FAR uint8_t *source)
|
|
{
|
|
if (g_ntpc_daemon.dhcp_servers != NULL &&
|
|
g_ntpc_daemon.dhcp_servers[0] != '\0')
|
|
{
|
|
*source = NTP_SERVER_SOURCE_DHCP;
|
|
return g_ntpc_daemon.dhcp_servers;
|
|
}
|
|
|
|
if (CONFIG_NETUTILS_NTPCLIENT_SERVER[0] != '\0')
|
|
{
|
|
*source = NTP_SERVER_SOURCE_CONFIG;
|
|
return CONFIG_NETUTILS_NTPCLIENT_SERVER;
|
|
}
|
|
|
|
*source = NTP_SERVER_SOURCE_NONE;
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef CONFIG_NETUTILS_DHCPC
|
|
static void ntpc_dhcp_notify(FAR const char *ntp_server_list, FAR void *arg)
|
|
{
|
|
int ret;
|
|
|
|
UNUSED(arg);
|
|
|
|
ret = ntpc_set_servers_from_dhcp(ntp_server_list);
|
|
if (ret < 0)
|
|
{
|
|
nwarn("WARNING: failed to update DHCP NTP server list: %d\n", ret);
|
|
}
|
|
}
|
|
|
|
static int ntpc_register_dhcp(void)
|
|
{
|
|
int ret;
|
|
|
|
sem_wait(&g_ntpc_daemon.lock);
|
|
if (g_ntpc_daemon.dhcp_registered)
|
|
{
|
|
sem_post(&g_ntpc_daemon.lock);
|
|
return OK;
|
|
}
|
|
|
|
g_ntpc_daemon.dhcp_registered = true;
|
|
sem_post(&g_ntpc_daemon.lock);
|
|
|
|
ret = netlib_register_dhcp_ntp_callback(ntpc_dhcp_notify, NULL);
|
|
if (ret < 0)
|
|
{
|
|
sem_wait(&g_ntpc_daemon.lock);
|
|
g_ntpc_daemon.dhcp_registered = false;
|
|
sem_post(&g_ntpc_daemon.lock);
|
|
return ret;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
static int ntpc_start_selected(FAR const char *ntp_server_list,
|
|
uint8_t source)
|
|
{
|
|
FAR char *task_argv[] =
|
|
{
|
|
(FAR char *)ntp_server_list,
|
|
NULL
|
|
};
|
|
|
|
g_ntpc_daemon.state = NTP_STARTED;
|
|
g_ntpc_daemon.source = source;
|
|
g_ntpc_daemon.pid =
|
|
task_create("NTP daemon", CONFIG_NETUTILS_NTPCLIENT_SERVERPRIO,
|
|
CONFIG_NETUTILS_NTPCLIENT_STACKSIZE, ntpc_daemon,
|
|
task_argv);
|
|
|
|
if (g_ntpc_daemon.pid < 0)
|
|
{
|
|
int errval = errno;
|
|
DEBUGASSERT(errval > 0);
|
|
|
|
g_ntpc_daemon.state = NTP_STOPPED;
|
|
g_ntpc_daemon.source = NTP_SERVER_SOURCE_NONE;
|
|
nerr("ERROR: Failed to start the NTP daemon: %d\n", errval);
|
|
return -errval;
|
|
}
|
|
|
|
do
|
|
{
|
|
sem_wait(&g_ntpc_daemon.sync);
|
|
}
|
|
while (g_ntpc_daemon.state == NTP_STARTED);
|
|
|
|
return g_ntpc_daemon.pid;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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 int32_t ntp_secpart(int64_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 int32_t ntp_nsecpart(int64_t time)
|
|
{
|
|
/* Get fraction part converted to nanoseconds. */
|
|
|
|
return (((time << 32) >> 32) * 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 += 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 = (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_apply_offset
|
|
****************************************************************************/
|
|
|
|
static void ntpc_apply_offset(FAR struct timespec *tp,
|
|
FAR struct timespec *src,
|
|
int64_t offset)
|
|
{
|
|
tp->tv_sec = src->tv_sec + ntp_secpart(offset);
|
|
tp->tv_nsec = src->tv_nsec + ntp_nsecpart(offset);
|
|
|
|
while (tp->tv_nsec < 0)
|
|
{
|
|
tp->tv_nsec += NSEC_PER_SEC;
|
|
tp->tv_sec--;
|
|
}
|
|
|
|
while (tp->tv_nsec >= NSEC_PER_SEC)
|
|
{
|
|
tp->tv_nsec -= NSEC_PER_SEC;
|
|
tp->tv_sec++;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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 += (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 += (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 */
|
|
|
|
ntpc_apply_offset(&tp, &curr_realtime, offset);
|
|
|
|
/* 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) * 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,
|
|
nitems(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 retry_delay = 1;
|
|
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);
|
|
|
|
nsamples = 0;
|
|
if (netlib_check_ipconnectivity(NULL, 1, 1) > 0)
|
|
{
|
|
/* Collect samples. */
|
|
|
|
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 = (offset1 + offset2) / 2;
|
|
}
|
|
else if (offset1 < 0 && offset2 < 0)
|
|
{
|
|
offset1 = -offset1;
|
|
offset2 = -offset2;
|
|
|
|
offset = (offset1 + 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;
|
|
retry_delay = 1;
|
|
}
|
|
|
|
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)
|
|
{
|
|
ninfo("Retry %d in %d seconds...\n", retries, retry_delay);
|
|
sleep(retry_delay);
|
|
|
|
retry_delay *= 2;
|
|
if (retry_delay > MAX_RETRY_INTERVAL)
|
|
{
|
|
retry_delay = MAX_RETRY_INTERVAL;
|
|
}
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
int ret;
|
|
|
|
if (ntp_server_list == NULL || ntp_server_list[0] == '\0')
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
ret = ntpc_start_selected(ntp_server_list,
|
|
NTP_SERVER_SOURCE_EXPLICIT);
|
|
sem_post(&g_ntpc_daemon.lock);
|
|
return ret;
|
|
}
|
|
|
|
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)
|
|
{
|
|
FAR const char *ntp_server_list;
|
|
uint8_t source;
|
|
int ret;
|
|
|
|
#ifdef CONFIG_NETUTILS_DHCPC
|
|
ret = ntpc_register_dhcp();
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
sem_wait(&g_ntpc_daemon.lock);
|
|
if (g_ntpc_daemon.state != NTP_NOT_RUNNING &&
|
|
g_ntpc_daemon.state != NTP_STOPPED)
|
|
{
|
|
ret = g_ntpc_daemon.pid;
|
|
sem_post(&g_ntpc_daemon.lock);
|
|
return ret;
|
|
}
|
|
|
|
ntp_server_list = ntpc_select_server_list(&source);
|
|
if (ntp_server_list == NULL)
|
|
{
|
|
g_ntpc_daemon.source = NTP_SERVER_SOURCE_NONE;
|
|
sem_post(&g_ntpc_daemon.lock);
|
|
return -ENOENT;
|
|
}
|
|
|
|
ret = ntpc_start_selected(ntp_server_list, source);
|
|
sem_post(&g_ntpc_daemon.lock);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_NETUTILS_DHCPC
|
|
static int ntpc_set_servers_from_dhcp(FAR const char *ntp_server_list)
|
|
{
|
|
FAR char *new_servers;
|
|
FAR char *old_servers;
|
|
bool start = false;
|
|
bool restart = false;
|
|
int ret = OK;
|
|
|
|
new_servers = ntpc_dup_server_list(ntp_server_list);
|
|
if (ntp_server_list != NULL && ntp_server_list[0] != '\0' &&
|
|
new_servers == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
sem_wait(&g_ntpc_daemon.lock);
|
|
|
|
if ((g_ntpc_daemon.dhcp_servers == NULL && new_servers == NULL) ||
|
|
(g_ntpc_daemon.dhcp_servers != NULL && new_servers != NULL &&
|
|
strcmp(g_ntpc_daemon.dhcp_servers, new_servers) == 0))
|
|
{
|
|
sem_post(&g_ntpc_daemon.lock);
|
|
free(new_servers);
|
|
return OK;
|
|
}
|
|
|
|
old_servers = g_ntpc_daemon.dhcp_servers;
|
|
g_ntpc_daemon.dhcp_servers = new_servers;
|
|
|
|
if ((g_ntpc_daemon.source == NTP_SERVER_SOURCE_DHCP ||
|
|
g_ntpc_daemon.source == NTP_SERVER_SOURCE_CONFIG) &&
|
|
(g_ntpc_daemon.state == NTP_STARTED ||
|
|
g_ntpc_daemon.state == NTP_RUNNING))
|
|
{
|
|
restart = true;
|
|
}
|
|
else if (g_ntpc_daemon.source == NTP_SERVER_SOURCE_NONE &&
|
|
new_servers != NULL &&
|
|
(g_ntpc_daemon.state == NTP_NOT_RUNNING ||
|
|
g_ntpc_daemon.state == NTP_STOPPED))
|
|
{
|
|
start = true;
|
|
}
|
|
|
|
sem_post(&g_ntpc_daemon.lock);
|
|
free(old_servers);
|
|
|
|
if (restart)
|
|
{
|
|
ret = ntpc_stop();
|
|
if (ret >= 0 && new_servers != NULL)
|
|
{
|
|
ret = ntpc_start();
|
|
}
|
|
}
|
|
else if (start)
|
|
{
|
|
ret = ntpc_start();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* 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;
|
|
}
|