blob: eb2d25bb3eda35769f5adb9a5fb70fb39899852c [file] [log] [blame]
/*
*
* Connection Manager
*
* Copyright (C) 2007-2014 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/timex.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <glib.h>
#include <gdhcp/timer.h>
#include "connman.h"
#undef DBG /* temp debug for SAPPHIRE-13875 */
#define DBG(msg, ...) connman_info("ntp: " msg, ##__VA_ARGS__)
// fail-safe for non-linux builds
#ifndef CLOCK_BOOTTIME
#define CLOCK_BOOTTIME CLOCK_REALTIME
#endif
struct ntp_short {
uint16_t seconds;
uint16_t fraction;
} __attribute__ ((packed));
struct ntp_time {
uint32_t seconds;
uint32_t fraction;
} __attribute__ ((packed));
struct ntp_msg {
uint8_t flags; /* Mode, version and leap indicator */
uint8_t stratum; /* Stratum details */
int8_t poll; /* Maximum interval in log2 seconds */
int8_t precision; /* Clock precision in log2 seconds */
struct ntp_short rootdelay; /* Root delay */
struct ntp_short rootdisp; /* Root dispersion */
uint32_t refid; /* Reference ID */
struct ntp_time reftime; /* Reference timestamp */
struct ntp_time orgtime; /* Origin timestamp */
struct ntp_time rectime; /* Receive timestamp */
struct ntp_time xmttime; /* Transmit timestamp */
} __attribute__ ((packed));
#define OFFSET_1900_1970 2208988800UL /* 1970 - 1900 in seconds */
#define STEPTIME_MIN_OFFSET 0.4
#define LOGTOD(a) ((a) < 0 ? 1. / (1L << -(a)) : 1L << (int)(a))
#define NSEC_PER_SEC ((uint64_t)1000000000ULL)
#ifndef ADJ_SETOFFSET
#define ADJ_SETOFFSET 0x0100 /* add 'time' to current time */
#endif
#define NTP_SEND_TIMEOUT 2
#define NTP_SEND_RETRIES 3
#define NTP_FLAG_LI_SHIFT 6
#define NTP_FLAG_LI_MASK 0x3
#define NTP_FLAG_LI_NOWARNING 0x0
#define NTP_FLAG_LI_ADDSECOND 0x1
#define NTP_FLAG_LI_DELSECOND 0x2
#define NTP_FLAG_LI_NOTINSYNC 0x3
#define NTP_FLAG_VN_SHIFT 3
#define NTP_FLAG_VN_MASK 0x7
#define NTP_FLAG_MD_SHIFT 0
#define NTP_FLAG_MD_MASK 0x7
#define NTP_FLAG_MD_UNSPEC 0
#define NTP_FLAG_MD_ACTIVE 1
#define NTP_FLAG_MD_PASSIVE 2
#define NTP_FLAG_MD_CLIENT 3
#define NTP_FLAG_MD_SERVER 4
#define NTP_FLAG_MD_BROADCAST 5
#define NTP_FLAG_MD_CONTROL 6
#define NTP_FLAG_MD_PRIVATE 7
#define NTP_FLAG_VN_VER3 3
#define NTP_FLAG_VN_VER4 4
#define NTP_FLAGS_ENCODE(li, vn, md) ((uint8_t)( \
(((li) & NTP_FLAG_LI_MASK) << NTP_FLAG_LI_SHIFT) | \
(((vn) & NTP_FLAG_VN_MASK) << NTP_FLAG_VN_SHIFT) | \
(((md) & NTP_FLAG_MD_MASK) << NTP_FLAG_MD_SHIFT)))
#define NTP_FLAGS_LI_DECODE(flags) ((uint8_t)(((flags) >> NTP_FLAG_LI_SHIFT) & NTP_FLAG_LI_MASK))
#define NTP_FLAGS_VN_DECODE(flags) ((uint8_t)(((flags) >> NTP_FLAG_VN_SHIFT) & NTP_FLAG_VN_MASK))
#define NTP_FLAGS_MD_DECODE(flags) ((uint8_t)(((flags) >> NTP_FLAG_MD_SHIFT) & NTP_FLAG_MD_MASK))
#define NTP_PRECISION_S 0
#define NTP_PRECISION_DS -3
#define NTP_PRECISION_CS -6
#define NTP_PRECISION_MS -9
#define NTP_PRECISION_US -19
#define NTP_PRECISION_NS -29
#define NTP_MINPOLL 4 /* 2^4 = 16 seconds */
#define NTP_MAXPOLL 17 /* 2^17 = 36.4 hours */
#define NTP_MINPOLL_DEFAULT 6 /* 2^6 = 64 seconds */
#define NTP_MAXPOLL_DEFAULT 10 /* 2^10 = 1024 seconds */
static guint channel_watch = 0;
static struct timespec mtx_time;
static int transmit_fd = 0;
static char *timeserver = NULL;
static struct sockaddr_storage timeserver_addr;
static gint poll_id = 0;
static gint timeout_id = 0;
static guint retries = 0;
static bool ntp_can_sleep = true;
static void send_packet(int fd, const char *server, uint32_t timeout, int family);
static void next_server(void)
{
if (timeserver) {
g_free(timeserver);
timeserver = NULL;
}
__connman_timeserver_sync_next();
}
static gboolean send_timeout(gpointer user_data)
{
uint32_t timeout = GPOINTER_TO_UINT(user_data);
DBG("send timeout %u (retries %d)", timeout, retries);
if (retries++ == NTP_SEND_RETRIES)
next_server();
else
send_packet(transmit_fd, timeserver, timeout << 1, timeserver_addr.ss_family);
return FALSE;
}
static void send_packet(int fd, const char *server, uint32_t timeout, int family)
{
struct ntp_msg msg;
struct sockaddr_in in4addr;
struct sockaddr_in6 in6addr;
struct sockaddr * addr;
struct timeval transmit_timeval;
ssize_t len;
int size;
unsigned char * addrptr;
/*
* At some point, we could specify the actual system precision with:
*
* clock_getres(CLOCK_REALTIME, &ts);
* msg.precision = (int)log2(ts.tv_sec + (ts.tv_nsec * 1.0e-9));
*/
memset(&msg, 0, sizeof(msg));
msg.flags = NTP_FLAGS_ENCODE(NTP_FLAG_LI_NOTINSYNC, NTP_FLAG_VN_VER4,
NTP_FLAG_MD_CLIENT);
msg.poll = NTP_MAXPOLL_DEFAULT;
msg.precision = NTP_PRECISION_S;
if (family == AF_INET) {
memset(&in4addr, 0, sizeof(in4addr));
in4addr.sin_family = AF_INET;
in4addr.sin_port = htons(123);
size = sizeof(in4addr);
addrptr = (unsigned char *)&in4addr.sin_addr.s_addr;
addr = (struct sockaddr *)&in4addr;
} else if (family == AF_INET6){
memset(&in6addr, 0, sizeof(in6addr));
in6addr.sin6_family = AF_INET6;
in6addr.sin6_port = htons(123);
size = sizeof(in6addr);
addrptr = in6addr.sin6_addr.__in6_u.__u6_addr8;
addr = (struct sockaddr *)&in6addr;
} else {
DBG("wrong family type");
return;
}
if (inet_pton(family, server, addrptr) == 0)
{
DBG("cannot convert ip address string");
return;
}
if (ntp_can_sleep) {
ntp_can_sleep = false;
__connman_notifier_sleep_event(&ntp_can_sleep, ntp_can_sleep, time(0) + 6 * NTP_SEND_TIMEOUT);
}
gettimeofday(&transmit_timeval, NULL);
clock_gettime(CLOCK_MONOTONIC, &mtx_time);
msg.xmttime.seconds = htonl(transmit_timeval.tv_sec + OFFSET_1900_1970);
msg.xmttime.fraction = htonl(transmit_timeval.tv_usec * 1000);
len = sendto(fd, &msg, sizeof(msg), MSG_DONTWAIT,
addr, size);
if (len < 0) {
connman_error("ntp: Time request for server %s failed (%d/%s)",
server, errno, strerror(errno));
if (errno == ENETUNREACH)
__connman_timeserver_sync_next();
return;
}
if (len != sizeof(msg)) {
connman_error("ntp: Broken time request for server %s", server);
return;
}
/*
* Add an exponential retry timeout to retry the existing
* request. After a set number of retries, we'll fallback to
* trying another server.
*/
timeout_id = g_timeout_add_seconds(timeout, send_timeout,
GUINT_TO_POINTER(timeout));
}
static gboolean next_poll(gpointer user_data)
{
poll_id = 0;
if (!timeserver || transmit_fd == 0)
return FALSE;
send_packet(transmit_fd, timeserver, NTP_SEND_TIMEOUT, timeserver_addr.ss_family);
return FALSE;
}
static void reset_timeout(void)
{
if (timeout_id > 0) {
g_source_remove(timeout_id);
timeout_id = 0;
}
retries = 0;
}
static void decode_msg(void *base, size_t len, struct timeval *tv,
struct timespec *mrx_time)
{
struct ntp_msg *msg = base;
double m_delta, org, rec, xmt, dst;
double delay, offset;
static guint transmit_delay;
struct timex tmx = {};
if (len < sizeof(*msg)) {
connman_error("ntp: Invalid response from time server");
return;
}
if (!tv) {
connman_error("ntp: Invalid packet timestamp from time server");
return;
}
DBG("flags : 0x%02x", msg->flags);
DBG("stratum : %u", msg->stratum);
DBG("poll : %f seconds (%d)",
LOGTOD(msg->poll), msg->poll);
DBG("precision : %f seconds (%d)",
LOGTOD(msg->precision), msg->precision);
DBG("root delay : %u seconds (fraction %u)",
msg->rootdelay.seconds, msg->rootdelay.fraction);
DBG("root disp. : %u seconds (fraction %u)",
msg->rootdisp.seconds, msg->rootdisp.fraction);
DBG("reference : 0x%04x", msg->refid);
if (!msg->stratum) {
/* RFC 4330 ch 8 Kiss-of-Death packet */
uint32_t code = ntohl(msg->refid);
connman_info("ntp: Skipping server %s KoD code %c%c%c%c",
timeserver, code >> 24, code >> 16 & 0xff,
code >> 8 & 0xff, code & 0xff);
next_server();
return;
}
if (msg->poll < NTP_MINPOLL || msg->poll > NTP_MAXPOLL) {
DBG("fix poll : %f seconds (%d)",
LOGTOD(NTP_MAXPOLL_DEFAULT), NTP_MAXPOLL_DEFAULT);
transmit_delay = LOGTOD(NTP_MAXPOLL_DEFAULT);
} else {
transmit_delay = LOGTOD(msg->poll);
}
if (NTP_FLAGS_LI_DECODE(msg->flags) == NTP_FLAG_LI_NOTINSYNC) {
DBG("ignoring unsynchronized peer");
return;
}
if (NTP_FLAGS_VN_DECODE(msg->flags) != NTP_FLAG_VN_VER4) {
if (NTP_FLAGS_VN_DECODE(msg->flags) == NTP_FLAG_VN_VER3) {
DBG("requested version %d, accepting version %d",
NTP_FLAG_VN_VER4, NTP_FLAGS_VN_DECODE(msg->flags));
} else {
DBG("unsupported version %d", NTP_FLAGS_VN_DECODE(msg->flags));
return;
}
}
if (NTP_FLAGS_MD_DECODE(msg->flags) != NTP_FLAG_MD_SERVER) {
DBG("unsupported mode %d", NTP_FLAGS_MD_DECODE(msg->flags));
return;
}
m_delta = mrx_time->tv_sec - mtx_time.tv_sec +
1.0e-9 * (mrx_time->tv_nsec - mtx_time.tv_nsec);
org = tv->tv_sec + (1.0e-6 * tv->tv_usec) - m_delta + OFFSET_1900_1970;
rec = ntohl(msg->rectime.seconds) +
((double) ntohl(msg->rectime.fraction) / UINT_MAX);
xmt = ntohl(msg->xmttime.seconds) +
((double) ntohl(msg->xmttime.fraction) / UINT_MAX);
dst = tv->tv_sec + (1.0e-6 * tv->tv_usec) + OFFSET_1900_1970;
DBG("org=%f rec=%f xmt=%f dst=%f", org, rec, xmt, dst);
offset = ((rec - org) + (xmt - dst)) / 2;
delay = (dst - org) - (xmt - rec);
DBG("offset=%f delay=%f", offset, delay);
/* Remove the timeout, as timeserver has responded */
reset_timeout();
/*
* Now poll the server every transmit_delay seconds
* for time correction.
*/
if (poll_id > 0)
g_rttimeout_source_remove(poll_id);
DBG("Timeserver %s, next sync in %d seconds", timeserver, transmit_delay);
poll_id = g_rttimeout_add_seconds_full(CLOCK_BOOTTIME, G_PRIORITY_DEFAULT, transmit_delay,
next_poll, NULL, NULL);
if (offset < STEPTIME_MIN_OFFSET && offset > -STEPTIME_MIN_OFFSET) {
tmx.modes = ADJ_STATUS | ADJ_NANO | ADJ_OFFSET | ADJ_TIMECONST | ADJ_MAXERROR | ADJ_ESTERROR;
tmx.status = STA_PLL;
tmx.offset = offset * NSEC_PER_SEC;
tmx.constant = msg->poll - 4;
tmx.maxerror = 0;
tmx.esterror = 0;
connman_info("ntp: adjust (slew): %+.6f sec", offset);
} else {
tmx.modes = ADJ_STATUS | ADJ_NANO | ADJ_SETOFFSET | ADJ_MAXERROR | ADJ_ESTERROR;
/* ADJ_NANO uses nanoseconds in the microseconds field */
tmx.time.tv_sec = (long)offset;
tmx.time.tv_usec = (offset - tmx.time.tv_sec) * NSEC_PER_SEC;
tmx.maxerror = 0;
tmx.esterror = 0;
/* the kernel expects -0.3s as {-1, 7000.000.000} */
if (tmx.time.tv_usec < 0) {
tmx.time.tv_sec -= 1;
tmx.time.tv_usec += NSEC_PER_SEC;
}
connman_info("ntp: adjust (jump): %+.6f sec", offset);
}
if (NTP_FLAGS_LI_DECODE(msg->flags) & NTP_FLAG_LI_ADDSECOND)
tmx.status |= STA_INS;
else if (NTP_FLAGS_LI_DECODE(msg->flags) & NTP_FLAG_LI_DELSECOND)
tmx.status |= STA_DEL;
if (adjtimex(&tmx) < 0) {
connman_error("ntp: Failed to adjust time");
} else {
DBG("interval/delta/delay/drift %fs/%+.3fs/%.3fs/%+ldppm",
LOGTOD(msg->poll), offset, delay, tmx.freq / 65536);
}
if (!ntp_can_sleep) {
ntp_can_sleep = true;
__connman_notifier_sleep_event(&ntp_can_sleep, ntp_can_sleep, time(0) + 2 * NTP_SEND_TIMEOUT);
}
}
static gboolean received_data(GIOChannel *channel, GIOCondition condition,
gpointer user_data)
{
unsigned char buf[128];
struct sockaddr_storage sender_addr;
struct msghdr msg;
struct iovec iov;
struct cmsghdr *cmsg;
struct timeval *tv;
struct timespec mrx_time;
char aux[128];
ssize_t len;
int fd;
if (condition & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
connman_error("ntp: Problem with timer server channel");
channel_watch = 0;
return FALSE;
}
fd = g_io_channel_unix_get_fd(channel);
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = aux;
msg.msg_controllen = sizeof(aux);
msg.msg_name = &sender_addr;
msg.msg_namelen = sizeof(sender_addr);
len = recvmsg(fd, &msg, MSG_DONTWAIT);
if (len < 0)
return TRUE;
if (sender_addr.ss_family == AF_INET) {
if (((struct sockaddr_in *)&timeserver_addr)->sin_addr.s_addr != ((struct sockaddr_in *)&sender_addr)->sin_addr.s_addr)
return TRUE;
} else if(sender_addr.ss_family == AF_INET6) {
if (memcmp(((struct sockaddr_in6 *)&timeserver_addr)->sin6_addr.__in6_u.__u6_addr8,
((struct sockaddr_in6 *)&sender_addr)->sin6_addr.__in6_u.__u6_addr8,
16) != 0)
return TRUE;
} else {
connman_error("ntp: Not a valid family type");
return TRUE;
}
tv = NULL;
clock_gettime(CLOCK_MONOTONIC, &mrx_time);
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level != SOL_SOCKET)
continue;
switch (cmsg->cmsg_type) {
case SCM_TIMESTAMP:
tv = (struct timeval *) CMSG_DATA(cmsg);
break;
}
}
decode_msg(iov.iov_base, iov.iov_len, tv, &mrx_time);
return TRUE;
}
static void start_ntp(char *server, int family)
{
GIOChannel *channel;
struct sockaddr * addr;
struct sockaddr_in in4addr;
struct sockaddr_in6 in6addr;
int tos = IPTOS_LOWDELAY, timestamp = 1;
int size;
if (!server)
return;
DBG("server %s family %d", server, family);
if (channel_watch > 0)
goto send;
transmit_fd = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (transmit_fd <= 0) {
connman_error("ntp: Failed to open time server socket");
return;
}
if (family == AF_INET) {
memset(&in4addr, 0, sizeof(in4addr));
in4addr.sin_family = family;
addr = (struct sockaddr *)&in4addr;
size = sizeof(in4addr);
} else if (family == AF_INET6) {
memset(&in6addr, 0, sizeof(in6addr));
in6addr.sin6_family = family;
addr = (struct sockaddr *)&in6addr;
size = sizeof(in6addr);
} else {
connman_error("ntp: Family not correct");
return;
}
if (bind(transmit_fd, (struct sockaddr *) addr, size) < 0) {
connman_error("ntp: Failed to bind time server socket");
close(transmit_fd);
return;
}
if (family == AF_INET) {
if (setsockopt(transmit_fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) {
connman_error("ntp: Failed to set type of service option");
close(transmit_fd);
return;
}
} else if (family == AF_INET6) {
//TO Do
}
if (setsockopt(transmit_fd, SOL_SOCKET, SO_TIMESTAMP, &timestamp,
sizeof(timestamp)) < 0) {
connman_error("ntp: Failed to enable timestamp support");
close(transmit_fd);
return;
}
channel = g_io_channel_unix_new(transmit_fd);
if (!channel) {
close(transmit_fd);
return;
}
g_io_channel_set_encoding(channel, NULL, NULL);
g_io_channel_set_buffered(channel, FALSE);
g_io_channel_set_close_on_unref(channel, TRUE);
channel_watch = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT,
G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
received_data, NULL, NULL);
g_io_channel_unref(channel);
send:
send_packet(transmit_fd, server, NTP_SEND_TIMEOUT, family);
}
int __connman_ntp_start(char *server)
{
DBG("%s", server);
struct addrinfo hint;
struct addrinfo *info;
int family;
int ret;
if (!server)
return -EINVAL;
if (timeserver)
g_free(timeserver);
memset(&hint, 0, sizeof(hint));
hint.ai_family = AF_UNSPEC;
hint.ai_flags = AI_NUMERICHOST;
ret = getaddrinfo(server, NULL, &hint, &info);
if (ret) {
connman_error("ntp: cannot get the server info");
return -1;
}
family = info->ai_family;
if (family == AF_INET) {
if (inet_pton(family, server, &(((struct sockaddr_in *)&timeserver_addr)->sin_addr.s_addr)) == 0) {
connman_error("ntp: cannot convert ip address string");
return -1;
}
} else if (family == AF_INET6) {
if (inet_pton(family, server, ((struct sockaddr_in6 *)&timeserver_addr)->sin6_addr.__in6_u.__u6_addr8) == 0) {
connman_error("ntp: cannot convert ipv6 address string");
return -1;
}
} else {
connman_error("ntp: Neither IPv4 nor IPv6 type");
return -1;
}
timeserver_addr.ss_family = family;
timeserver = g_strdup(server);
freeaddrinfo(info);
start_ntp(timeserver, family);
return 0;
}
void __connman_ntp_stop()
{
DBG("stop");
if (poll_id > 0) {
g_rttimeout_source_remove(poll_id);
poll_id = 0;
}
reset_timeout();
if (channel_watch > 0) {
g_source_remove(channel_watch);
channel_watch = 0;
transmit_fd = 0;
}
if (timeserver) {
g_free(timeserver);
timeserver = NULL;
}
if (!ntp_can_sleep) {
ntp_can_sleep = true;
__connman_notifier_sleep_event(&ntp_can_sleep, ntp_can_sleep, time(0) + 6 * NTP_SEND_TIMEOUT);
}
}