AmiTimeKeeper/sntp.c

469 lines
10 KiB
C

/*-
* Copyright (c) 2007 TANDBERG Telecom AS
* Copyright (c) 2008-2009 Dag-Erling Smørgrav <des@des.no>
* Copyright (c) 2017-2021 Carsten Sonne Larsen <cs@innolan.net>
* All rights reserved.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
*
*/
#include "config.h"
#include "timer.h"
#include "sntp.h"
#include "mem.h"
#include "net_getaddrinfo.h"
#include "message.h"
#include "logmod.h"
#define MODULENAME "SNTP"
/*
* Convert a struct timeval to an NTP timestamp
*/
void tv2nt(struct timeval *tv, struct ntptime *nt)
{
uint64_t frac;
nt->sec = tv->tv_secs + UNIX_EPOCH;
frac = tv->tv_micro;
frac <<= 32;
frac /= 1000 * 1000;
nt->frac = frac;
}
/*
* Convert an NTP timestamp to a struct timeval
*/
void nt2tv(struct ntptime *nt, struct timeval *tv)
{
uint64_t frac;
tv->tv_sec = nt->sec - UNIX_EPOCH;
frac = nt->frac;
frac *= 1000 * 1000;
frac >>= 32;
tv->tv_usec = frac;
}
/*
* Convert struct ntptime in-place from network to host order
*/
void n2h_ntp(struct ntptime *nt)
{
nt->sec = ntohl(nt->sec);
nt->frac = ntohl(nt->frac);
}
/*
* Convert struct ntptime in-place from host to network order
*/
void h2n_ntp(struct ntptime *nt)
{
nt->sec = htonl(nt->sec);
nt->frac = htonl(nt->frac);
}
/*
* SNTP client state
*/
struct sntp
{
/* parameters from sntp_create() */
STRPTR srcaddr;
STRPTR srcport;
STRPTR dstaddr;
STRPTR dstport;
/* DNS data */
int family;
int socktype;
int protocol;
struct sockaddr *laddr;
socklen_t laddrlen;
struct sockaddr *raddr;
socklen_t raddrlen;
/* socket and poll structure */
int sd;
struct pollfd pfd;
/* protocol state */
struct ntptime last_send;
struct ntptime last_recv;
};
/*
* Initialize an SNTP client context
*
* Multiple contexts can coexist as long as they do not use the same
* source port.
*
* NOTICE: Consider adding support for binding to a specific source address.
*/
struct sntp *
sntp_create(const char *dstaddr, const char *dstport,
const char *srcaddr, const char *srcport)
{
struct sntp *sntp;
sntp = AllocStructSafe(struct sntp);
sntp->sd = -1;
sntp->srcaddr = (STRPTR)((srcaddr != NULL) ? StrDupSafe(srcaddr) : NULL);
sntp->srcport = (STRPTR)StrDupSafe((srcport != NULL) ? srcport : "123");
sntp->dstaddr = (STRPTR)StrDupSafe(dstaddr);
sntp->dstport = (STRPTR)StrDupSafe((dstport != NULL) ? dstport : "123");
/* good to go */
return (sntp);
}
/*
* Look up local and remote addresses and set up the socket
*/
int sntp_open(struct sntp *sntp)
{
struct addrinfo hints;
struct addrinfo *aiv, *ai;
int ret;
if (sntp->sd != -1)
return (SNTP_OK);
/* resolve the server address */
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
if ((ret = getaddrinfo(sntp->dstaddr, sntp->dstport, &hints, &aiv)) != 0)
return (SNTP_DNSERR);
/* Something went wrong */
if (aiv == NULL)
return (SNTP_SYSERR);
/*
* Iterate over the results until we find one we can use. This is
* sometimes necessary on systems with partial IPv6 support, where
* the resolver may return IPv6 addresses which the network stack
* can't handle.
*/
for (ai = aiv; ai; ai = ai->ai_next)
{
sntp->sd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sntp->sd >= 0)
break;
}
if (sntp->sd == -1)
{
LogWarn("Could not get socket descriptor");
freeaddrinfo(aiv);
return (SNTP_SYSERR);
}
sntp->raddr = MemDupSafe(ai->ai_addr, ai->ai_addrlen);
sntp->raddrlen = ai->ai_addrlen;
sntp->family = ai->ai_family;
sntp->socktype = ai->ai_socktype;
sntp->protocol = ai->ai_protocol;
freeaddrinfo(aiv);
/* get a matching local address */
memset(&hints, 0, sizeof hints);
hints.ai_family = sntp->family;
hints.ai_socktype = sntp->socktype;
hints.ai_protocol = sntp->protocol;
hints.ai_flags = AI_PASSIVE;
if ((ret = getaddrinfo(sntp->srcaddr, sntp->srcport, &hints, &aiv)) != 0)
{
sntp_close(sntp);
return (SNTP_DNSERR);
}
/* Something went wrong */
if (aiv == NULL)
return (SNTP_SYSERR);
/* NOTICE: Consider asserting that results match expectations */
sntp->laddr = MemDupSafe(aiv->ai_addr, aiv->ai_addrlen);
sntp->laddrlen = aiv->ai_addrlen;
freeaddrinfo(aiv);
/* prepare our socket */
/* NOTICE: Sometimes AmigaOS do not like bindings. Just leave it for now.
if (bind(sntp->sd, sntp->laddr, sntp->laddrlen) != 0) {
LogError("Could not bind socket");
sntp_close(sntp);
return (SNTP_SYSERR);
}
*/
if (connect(sntp->sd, sntp->raddr, sntp->raddrlen) != 0)
{
LogWarn("Could not connect socket");
sntp_close(sntp);
return (SNTP_SYSERR);
}
/* prepare our pollfd */
sntp->pfd.fd = sntp->sd;
sntp->pfd.events = POLLIN;
sntp->pfd.revents = 0;
return (SNTP_OK);
}
/*
* Destroy an SNTP client context
*
* This is called several times during error handling in other parts of
* the code, so we should save and restore errno
*/
void sntp_close(struct sntp *sntp)
{
int serrno;
serrno = errno;
sntp->family = 0;
sntp->socktype = 0;
sntp->protocol = 0;
if (sntp->laddr)
FreeMemSafe(sntp->laddr);
sntp->laddrlen = 0;
if (sntp->raddr)
FreeMemSafe(sntp->raddr);
sntp->raddrlen = 0;
if (sntp->sd != -1)
CloseSocket(sntp->sd);
memset(&sntp->pfd, 0, sizeof sntp->pfd);
nt_zero(sntp->last_send);
nt_zero(sntp->last_recv);
errno = serrno;
}
/*
* Destroy an SNTP client context
*/
void sntp_destroy(struct sntp *sntp)
{
(void)sntp_close(sntp);
if (sntp->srcaddr)
FreeMemSafe(sntp->srcaddr);
sntp->srcaddr = 0;
if (sntp->srcport)
FreeMemSafe(sntp->srcport);
sntp->srcport = 0;
if (sntp->dstaddr)
FreeMemSafe(sntp->dstaddr);
sntp->dstaddr = 0;
if (sntp->dstport)
FreeMemSafe(sntp->dstport);
sntp->dstport = 0;
FreeMemSafe(sntp);
}
/*
* Structure of an NTP message without authenticator
*/
struct ntp_msg
{
uint8_t flags;
uint8_t stratum;
uint8_t poll;
uint8_t precision;
uint32_t root_delay;
uint32_t root_dispersion;
uint8_t reference_id[4];
struct ntptime reference;
struct ntptime originate;
struct ntptime receive;
struct ntptime transmit;
};
/*
* Send an SNTP request
*/
sntp_err_t
sntp_send(struct sntp *sntp)
{
struct timeval tv;
struct ntp_msg msg;
ssize_t ret;
sntp_err_t se;
if ((se = sntp_open(sntp)) != SNTP_OK)
return (se);
memset(&msg, 0, sizeof msg);
msg.flags = 0x23; /* version 4, client */
GetTimeOfDay(&tv);
tv2nt(&tv, &msg.transmit);
h2n_ntp(&msg.transmit);
ret = send(sntp->sd, (void *)&msg, sizeof(struct ntp_msg), 0);
if (ret < 0)
return (SNTP_SYSERR);
tv2nt(&tv, &sntp->last_send);
return (SNTP_OK);
}
/*
* Have we sent a request to which we're still expecting a response?
*/
sntp_err_t
sntp_pending(struct sntp *sntp)
{
/* not currently open */
if (sntp->sd == -1)
return (SNTP_NOREQ);
/* last request predates last response */
if (nt_lt(sntp->last_send, sntp->last_recv))
return (SNTP_NOREQ);
return (SNTP_OK);
}
/*
* Poll for the arrival of an SNTP reply
*/
sntp_err_t
sntp_poll(struct sntp *sntp, int timeout)
{
sntp_err_t se;
if ((se = sntp_pending(sntp)) != SNTP_OK)
return (se);
switch (poll(&sntp->pfd, 1, timeout))
{
case -1:
sntp_close(sntp);
return (SNTP_SYSERR);
case 0:
return (SNTP_NORESP);
case 1:
return (SNTP_OK);
}
return 0;
}
/*
* Receive and process an SNTP reply
*/
sntp_err_t
sntp_recv(struct sntp *sntp, struct ntptime *nt, int *statum)
{
struct timeval tv;
struct ntp_msg msg;
sntp_err_t se;
int res;
*statum = -1;
if ((se = sntp_pending(sntp)) != SNTP_OK)
return (se);
/* NOTICE: Consider using recvmsg() instead */
res = recv(sntp->sd, (void *)&msg, sizeof(struct ntp_msg), MSG_DONTWAIT);
switch (res)
{
case -1:
if (errno == EAGAIN)
return (SNTP_NORESP);
sntp_close(sntp);
return (SNTP_SYSERR);
case 0:
/* can this actually occur? */
return (SNTP_NORESP);
case sizeof msg:
/* good! */
break;
default:
/* we got something, but bob knows what */
return (SNTP_BADRESP);
}
/* record time of arrival */
GetTimeOfDay(&tv);
/* convert to host order */
n2h_ntp(&msg.originate);
n2h_ntp(&msg.receive);
n2h_ntp(&msg.transmit);
/* look for kiss packet */
if (msg.flags == 0xe4 && msg.stratum == 0)
{
LogWarn("KoD: %.4s", msg.reference_id);
/* NOTICE: Consider taking a closer look at the kiss code */
return (SNTP_BACKOFF);
}
/* check validity: synchronized NTPv4 server */
switch (msg.flags)
{
case 0x23: /* version 4 client */
/* we're probably accidentally querying ourselves */
return (SNTP_BADRESP);
case 0x24: /* no warning, version 4, server */
case 0x64: /* subtract leap second, version 4, server */
case 0xa4: /* add leap second, version 4, server */
/* these are the normal, useful cases */
break;
case 0xe4: /* unsynchronized, version 4, server */
/* server not usable (yet?) */
return (SNTP_LAME);
default:
return (SNTP_BADRESP);
}
/* check if this is the response we were expecting */
if (!nt_eq(msg.originate, sntp->last_send))
/* probably delayed response to old request */
return (SNTP_NORESP);
tv2nt(&tv, &sntp->last_recv);
*statum = msg.stratum;
*nt = msg.transmit;
return (SNTP_OK);
}