diff options
Diffstat (limited to 'client.c')
-rw-r--r-- | client.c | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/client.c b/client.c new file mode 100644 index 0000000..0eae0c5 --- /dev/null +++ b/client.c @@ -0,0 +1,359 @@ +/* $OpenBSD: client.c,v 1.79 2008/01/28 11:45:59 mpf Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2004 Alexander Guy <alexander.guy@andern.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "ntpd.h" + +int client_update(struct ntp_peer *); +void set_deadline(struct ntp_peer *, time_t); + +void +set_next(struct ntp_peer *p, time_t t) +{ + p->next = getmonotime() + t; + p->deadline = 0; +} + +void +set_deadline(struct ntp_peer *p, time_t t) +{ + p->deadline = getmonotime() + t; + p->next = 0; +} + +int +client_peer_init(struct ntp_peer *p) +{ + if ((p->query = calloc(1, sizeof(struct ntp_query))) == NULL) + fatal("client_peer_init calloc"); + p->query->fd = -1; + p->query->msg.status = MODE_CLIENT | (NTP_VERSION << 3); + p->state = STATE_NONE; + p->shift = 0; + p->trustlevel = TRUSTLEVEL_PATHETIC; + p->lasterror = 0; + p->senderrors = 0; + + return (client_addr_init(p)); +} + +int +client_addr_init(struct ntp_peer *p) +{ + struct sockaddr_in *sa_in; + struct sockaddr_in6 *sa_in6; + struct ntp_addr *h; + + for (h = p->addr; h != NULL; h = h->next) { + switch (h->ss.ss_family) { + case AF_INET: + sa_in = (struct sockaddr_in *)&h->ss; + if (ntohs(sa_in->sin_port) == 0) + sa_in->sin_port = htons(123); + p->state = STATE_DNS_DONE; + break; + case AF_INET6: + sa_in6 = (struct sockaddr_in6 *)&h->ss; + if (ntohs(sa_in6->sin6_port) == 0) + sa_in6->sin6_port = htons(123); + p->state = STATE_DNS_DONE; + break; + default: + fatalx("king bula sez: wrong AF in client_addr_init"); + /* not reached */ + } + } + + p->query->fd = -1; + set_next(p, 0); + + return (0); +} + +int +client_nextaddr(struct ntp_peer *p) +{ + if (p->query->fd != -1) { + close(p->query->fd); + p->query->fd = -1; + } + + if (p->state == STATE_DNS_INPROGRESS) + return (-1); + + if (p->addr_head.a == NULL) { + priv_host_dns(p->addr_head.name, p->id); + p->state = STATE_DNS_INPROGRESS; + return (-1); + } + + if ((p->addr = p->addr->next) == NULL) + p->addr = p->addr_head.a; + + p->shift = 0; + p->trustlevel = TRUSTLEVEL_PATHETIC; + + return (0); +} + +int +client_query(struct ntp_peer *p) +{ + int tos = IPTOS_LOWDELAY; + + if (p->addr == NULL && client_nextaddr(p) == -1) { + set_next(p, MAX(SETTIME_TIMEOUT, + scale_interval(INTERVAL_QUERY_AGGRESSIVE))); + return (0); + } + + if (p->state < STATE_DNS_DONE || p->addr == NULL) + return (-1); + + if (p->query->fd == -1) { + struct sockaddr *sa = (struct sockaddr *)&p->addr->ss; + + if ((p->query->fd = socket(p->addr->ss.ss_family, SOCK_DGRAM, + 0)) == -1) + fatal("client_query socket"); + if (connect(p->query->fd, sa, SA_LEN(sa)) == -1) { + if (errno == ECONNREFUSED || errno == ENETUNREACH || + errno == EHOSTUNREACH || errno == EADDRNOTAVAIL) { + client_nextaddr(p); + set_next(p, MAX(SETTIME_TIMEOUT, + scale_interval(INTERVAL_QUERY_AGGRESSIVE))); + return (-1); + } else + fatal("client_query connect"); + } + if (p->addr->ss.ss_family == AF_INET && setsockopt(p->query->fd, + IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1) + log_warn("setsockopt IPTOS_LOWDELAY"); + } + + /* + * Send out a random 64-bit number as our transmit time. The NTP + * server will copy said number into the originate field on the + * response that it sends us. This is totally legal per the SNTP spec. + * + * The impact of this is two fold: we no longer send out the current + * system time for the world to see (which may aid an attacker), and + * it gives us a (not very secure) way of knowing that we're not + * getting spoofed by an attacker that can't capture our traffic + * but can spoof packets from the NTP server we're communicating with. + * + * Save the real transmit timestamp locally. + */ + + p->query->msg.xmttime.int_partl = arc4random(); + p->query->msg.xmttime.fractionl = arc4random(); + p->query->xmttime = gettime_corrected(); + + if (ntp_sendmsg(p->query->fd, NULL, &p->query->msg, + NTP_MSGSIZE_NOAUTH, 0) == -1) { + p->senderrors++; + set_next(p, INTERVAL_QUERY_PATHETIC); + p->trustlevel = TRUSTLEVEL_PATHETIC; + return (-1); + } + + p->senderrors = 0; + p->state = STATE_QUERY_SENT; + set_deadline(p, QUERYTIME_MAX); + + return (0); +} + +int +client_dispatch(struct ntp_peer *p, u_int8_t settime) +{ + char buf[NTP_MSGSIZE]; + ssize_t size; + struct ntp_msg msg; + double T1, T2, T3, T4; + time_t interval; + + if ((size = recvfrom(p->query->fd, &buf, sizeof(buf), 0, + NULL, NULL)) == -1) { + if (errno == EHOSTUNREACH || errno == EHOSTDOWN || + errno == ENETUNREACH || errno == ENETDOWN || + errno == ECONNREFUSED || errno == EADDRNOTAVAIL) { + client_log_error(p, "recvfrom", errno); + set_next(p, error_interval()); + return (0); + } else + fatal("recvfrom"); + } + + T4 = gettime_corrected(); + + ntp_getmsg((struct sockaddr *)&p->addr->ss, buf, size, &msg); + + if (msg.orgtime.int_partl != p->query->msg.xmttime.int_partl || + msg.orgtime.fractionl != p->query->msg.xmttime.fractionl) + return (0); + + if ((msg.status & LI_ALARM) == LI_ALARM || msg.stratum == 0 || + msg.stratum > NTP_MAXSTRATUM) { + interval = error_interval(); + set_next(p, interval); + log_info("reply from %s: not synced, next query %ds", + log_sockaddr((struct sockaddr *)&p->addr->ss), interval); + return (0); + } + + /* + * From RFC 2030 (with a correction to the delay math): + * + * Timestamp Name ID When Generated + * ------------------------------------------------------------ + * Originate Timestamp T1 time request sent by client + * Receive Timestamp T2 time request received by server + * Transmit Timestamp T3 time reply sent by server + * Destination Timestamp T4 time reply received by client + * + * The roundtrip delay d and local clock offset t are defined as + * + * d = (T4 - T1) - (T3 - T2) t = ((T2 - T1) + (T3 - T4)) / 2. + */ + + T1 = p->query->xmttime; + T2 = lfp_to_d(msg.rectime); + T3 = lfp_to_d(msg.xmttime); + + p->reply[p->shift].offset = ((T2 - T1) + (T3 - T4)) / 2; + p->reply[p->shift].delay = (T4 - T1) - (T3 - T2); + if (p->reply[p->shift].delay < 0) { + interval = error_interval(); + set_next(p, interval); + log_info("reply from %s: negative delay %fs, " + "next query %ds", + log_sockaddr((struct sockaddr *)&p->addr->ss), + p->reply[p->shift].delay, interval); + return (0); + } + p->reply[p->shift].error = (T2 - T1) - (T3 - T4); + p->reply[p->shift].rcvd = getmonotime(); + p->reply[p->shift].good = 1; + + p->reply[p->shift].status.leap = (msg.status & LIMASK); + p->reply[p->shift].status.precision = msg.precision; + p->reply[p->shift].status.rootdelay = sfp_to_d(msg.rootdelay); + p->reply[p->shift].status.rootdispersion = sfp_to_d(msg.dispersion); + p->reply[p->shift].status.refid = ntohl(msg.refid); + p->reply[p->shift].status.refid4 = msg.xmttime.fractionl; + p->reply[p->shift].status.reftime = lfp_to_d(msg.reftime); + p->reply[p->shift].status.poll = msg.ppoll; + p->reply[p->shift].status.stratum = msg.stratum; + + if (p->addr->ss.ss_family == AF_INET) + p->reply[p->shift].status.send_refid = + ((struct sockaddr_in *)&p->addr->ss)->sin_addr.s_addr; + else + p->reply[p->shift].status.send_refid = msg.xmttime.fractionl; + + if (p->trustlevel < TRUSTLEVEL_PATHETIC) + interval = scale_interval(INTERVAL_QUERY_PATHETIC); + else if (p->trustlevel < TRUSTLEVEL_AGGRESSIVE) + interval = scale_interval(INTERVAL_QUERY_AGGRESSIVE); + else + interval = scale_interval(INTERVAL_QUERY_NORMAL); + + set_next(p, interval); + p->state = STATE_REPLY_RECEIVED; + + /* every received reply which we do not discard increases trust */ + if (p->trustlevel < TRUSTLEVEL_MAX) { + if (p->trustlevel < TRUSTLEVEL_BADPEER && + p->trustlevel + 1 >= TRUSTLEVEL_BADPEER) + log_info("peer %s now valid", + log_sockaddr((struct sockaddr *)&p->addr->ss)); + p->trustlevel++; + } + + log_debug("reply from %s: offset %f delay %f, " + "next query %ds", log_sockaddr((struct sockaddr *)&p->addr->ss), + p->reply[p->shift].offset, p->reply[p->shift].delay, interval); + + client_update(p); + if (settime) + priv_settime(p->reply[p->shift].offset); + + if (++p->shift >= OFFSET_ARRAY_SIZE) + p->shift = 0; + + return (0); +} + +int +client_update(struct ntp_peer *p) +{ + int i, best = 0, good = 0; + + /* + * clock filter + * find the offset which arrived with the lowest delay + * use that as the peer update + * invalidate it and all older ones + */ + + for (i = 0; good == 0 && i < OFFSET_ARRAY_SIZE; i++) + if (p->reply[i].good) { + good++; + best = i; + } + + for (; i < OFFSET_ARRAY_SIZE; i++) + if (p->reply[i].good) { + good++; + if (p->reply[i].delay < p->reply[best].delay) + best = i; + } + + if (good < 8) + return (-1); + + memcpy(&p->update, &p->reply[best], sizeof(p->update)); + if (priv_adjtime() == 0) { + for (i = 0; i < OFFSET_ARRAY_SIZE; i++) + if (p->reply[i].rcvd <= p->reply[best].rcvd) + p->reply[i].good = 0; + } + return (0); +} + +void +client_log_error(struct ntp_peer *peer, const char *operation, int error) +{ + const char *address; + + address = log_sockaddr((struct sockaddr *)&peer->addr->ss); + if (peer->lasterror == error) { + log_debug("%s %s: %s", operation, address, strerror(error)); + return; + } + peer->lasterror = error; + log_warn("%s %s", operation, address); +} |