/*- * Copyright (c) 2007 TANDBERG Telecom AS * Copyright (c) 2008-2009 Dag-Erling Smørgrav * Copyright (c) 2017-2021 Carsten Sonne Larsen * 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); }