diff options
author | Adam Tkac <vonsch@gmail.com> | 2009-11-22 03:43:55 +0100 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2009-11-22 03:43:55 +0100 |
commit | b1585064fdac138b3bd14c2b64f64c67ec08b654 (patch) | |
tree | b632b12a9aea4953751bb1569bf9290db42c5c4d | |
parent | d095fd4d95fd6241847f01e4fd674bc177310c33 (diff) | |
download | busybox-b1585064fdac138b3bd14c2b64f64c67ec08b654.tar.gz |
ntpd: new applet by Adam Tkac. +5k.
Signed-off-by: Adam Tkac <vonsch@gmail.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r-- | include/applets.h | 1 | ||||
-rw-r--r-- | include/usage.h | 11 | ||||
-rw-r--r-- | networking/Config.in | 14 | ||||
-rw-r--r-- | networking/Kbuild | 1 | ||||
-rw-r--r-- | networking/ntpd.c | 999 |
5 files changed, 1026 insertions, 0 deletions
diff --git a/include/applets.h b/include/applets.h index 15ccf9cea..a2d1e95a5 100644 --- a/include/applets.h +++ b/include/applets.h @@ -285,6 +285,7 @@ IF_NICE(APPLET(nice, _BB_DIR_BIN, _BB_SUID_DROP)) IF_NMETER(APPLET(nmeter, _BB_DIR_USR_BIN, _BB_SUID_DROP)) IF_NOHUP(APPLET(nohup, _BB_DIR_USR_BIN, _BB_SUID_DROP)) IF_NSLOOKUP(APPLET(nslookup, _BB_DIR_USR_BIN, _BB_SUID_DROP)) +IF_NTPD(APPLET(ntpd, _BB_DIR_USR_SBIN, _BB_SUID_DROP)) IF_OD(APPLET(od, _BB_DIR_USR_BIN, _BB_SUID_DROP)) IF_OPENVT(APPLET(openvt, _BB_DIR_USR_BIN, _BB_SUID_DROP)) //IF_PARSE(APPLET(parse, _BB_DIR_USR_BIN, _BB_SUID_DROP)) diff --git a/include/usage.h b/include/usage.h index eae9650ca..3ccf160bd 100644 --- a/include/usage.h +++ b/include/usage.h @@ -3210,6 +3210,17 @@ "Name: debian\n" \ "Address: 127.0.0.1\n" +#define ntpd_trivial_usage \ + "[-dngl] [-p PEER]..." +#define ntpd_full_usage "\n\n" \ + "NTP client/server\n" \ + "\nOptions:" \ + "\n -d Verbose" \ + "\n -n Do not daemonize" \ + "\n -g Set system time even if offset is > 1000 sec" \ + "\n -l Run as server on port 123" \ + "\n -p PEER Obtain time from PEER (may be repeated)" \ + #define od_trivial_usage \ "[-aBbcDdeFfHhIiLlOovXx] " IF_DESKTOP("[-t TYPE] ") "[FILE]" #define od_full_usage "\n\n" \ diff --git a/networking/Config.in b/networking/Config.in index 5f1b6f636..3d29622da 100644 --- a/networking/Config.in +++ b/networking/Config.in @@ -661,6 +661,20 @@ config NSLOOKUP help nslookup is a tool to query Internet name servers. +config NTPD + bool "ntpd" + default n + help + The NTP client/server daemon. + +config FEATURE_NTPD_SERVER + bool "Make ntpd usable as a NTP server" + default y + depends on NTPD + help + Make ntpd usable as a NTP server. If you disable this option + ntpd will be usable only as a NTP client. + config PING bool "ping" default n diff --git a/networking/Kbuild b/networking/Kbuild index 866d42f6b..b0765bcf6 100644 --- a/networking/Kbuild +++ b/networking/Kbuild @@ -27,6 +27,7 @@ lib-$(CONFIG_NAMEIF) += nameif.o lib-$(CONFIG_NC) += nc.o lib-$(CONFIG_NETSTAT) += netstat.o lib-$(CONFIG_NSLOOKUP) += nslookup.o +lib-$(CONFIG_NTPD) += ntpd.o lib-$(CONFIG_PING) += ping.o lib-$(CONFIG_PING6) += ping.o lib-$(CONFIG_PSCAN) += pscan.o diff --git a/networking/ntpd.c b/networking/ntpd.c new file mode 100644 index 000000000..62a00a5bf --- /dev/null +++ b/networking/ntpd.c @@ -0,0 +1,999 @@ +/* + * NTP client/server, based on OpenNTPD 3.9p1 + * + * Author: Adam Tkac <vonsch@gmail.com> + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include <netinet/ip.h> /* For IPTOS_LOWDELAY definition */ + +#ifndef IP_PKTINFO +# error "Sorry, your kernel has to support IP_PKTINFO" +#endif + +#define INTERVAL_QUERY_NORMAL 30 /* sync to peers every n secs */ +#define INTERVAL_QUERY_PATHETIC 60 +#define INTERVAL_QUERY_AGRESSIVE 5 + +#define TRUSTLEVEL_BADPEER 6 +#define TRUSTLEVEL_PATHETIC 2 +#define TRUSTLEVEL_AGRESSIVE 8 +#define TRUSTLEVEL_MAX 10 + +#define QSCALE_OFF_MIN 0.05 +#define QSCALE_OFF_MAX 0.50 + +#define QUERYTIME_MAX 15 /* single query might take n secs max */ +#define OFFSET_ARRAY_SIZE 8 +#define SETTIME_MIN_OFFSET 180 /* min offset for settime at start */ +#define SETTIME_TIMEOUT 15 /* max seconds to wait with -s */ + +/* Style borrowed from NTP ref/tcpdump and updated for SNTPv4 (RFC2030). */ + +/* + * RFC Section 3 + * + * 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 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * 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 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +typedef struct { + uint32_t int_partl; + uint32_t fractionl; +} l_fixedpt_t; + +typedef struct { + uint16_t int_parts; + uint16_t fractions; +} s_fixedpt_t; + +#define NTP_DIGESTSIZE 16 +#define NTP_MSGSIZE_NOAUTH 48 +#define NTP_MSGSIZE (NTP_MSGSIZE_NOAUTH + 4 + NTP_DIGESTSIZE) + +typedef struct { + uint8_t status; /* status of local clock and leap info */ + uint8_t stratum; /* Stratum level */ + uint8_t ppoll; /* poll value */ + int8_t precision; + s_fixedpt_t rootdelay; + s_fixedpt_t dispersion; + uint32_t refid; + l_fixedpt_t reftime; + l_fixedpt_t orgtime; + l_fixedpt_t rectime; + l_fixedpt_t xmttime; + uint32_t keyid; + uint8_t digest[NTP_DIGESTSIZE]; +} ntp_msg_t; + +typedef struct { + int fd; + ntp_msg_t msg; + double xmttime; +} ntp_query_t; + +enum { + NTP_VERSION = 4, + NTP_MAXSTRATUM = 15, + /* Leap Second Codes (high order two bits) */ + LI_NOWARNING = (0 << 6), /* no warning */ + LI_PLUSSEC = (1 << 6), /* add a second (61 seconds) */ + LI_MINUSSEC = (2 << 6), /* minus a second (59 seconds) */ + LI_ALARM = (3 << 6), /* alarm condition */ + + /* Status Masks */ + MODE_MASK = (7 << 0), + VERSION_MASK = (7 << 3), + LI_MASK = (3 << 6), + + /* Mode values */ + MODE_RES0 = 0, /* reserved */ + MODE_SYM_ACT = 1, /* symmetric active */ + MODE_SYM_PAS = 2, /* symmetric passive */ + MODE_CLIENT = 3, /* client */ + MODE_SERVER = 4, /* server */ + MODE_BROADCAST = 5, /* broadcast */ + MODE_RES1 = 6, /* reserved for NTP control message */ + MODE_RES2 = 7 /* reserved for private use */ +}; + +#define JAN_1970 2208988800UL /* 1970 - 1900 in seconds */ + +enum client_state { + STATE_NONE, + STATE_QUERY_SENT, + STATE_REPLY_RECEIVED +}; + +typedef struct { + double rootdelay; + double rootdispersion; + double reftime; + uint32_t refid; + uint32_t refid4; + uint8_t synced; + uint8_t leap; + int8_t precision; + uint8_t poll; + uint8_t stratum; +} ntp_status_t; + +typedef struct { + ntp_status_t status; + double offset; + double delay; + double error; + time_t rcvd; + uint8_t good; +} ntp_offset_t; + +typedef struct { + len_and_sockaddr *lsa; + ntp_query_t query; + ntp_offset_t reply[OFFSET_ARRAY_SIZE]; + ntp_offset_t update; + enum client_state state; + time_t next; + time_t deadline; + uint8_t shift; + uint8_t trustlevel; +} ntp_peer_t; + + +struct globals { + unsigned verbose; +#if ENABLE_FEATURE_NTPD_SERVER + int listen_fd; +#endif + llist_t *ntp_peers; + ntp_status_t status; + uint32_t scale; + uint8_t settime; + uint8_t firstadj; + smallint peer_cnt; + +}; +#define G (*ptr_to_globals) + + +static const int const_IPTOS_LOWDELAY = IPTOS_LOWDELAY; + + +static void +set_next(ntp_peer_t *p, time_t t) +{ + p->next = time(NULL) + t; + p->deadline = 0; +} + +static void +add_peers(const char *s) +{ + ntp_peer_t *p; + + p = xzalloc(sizeof(*p)); +//TODO: big ntpd uses all IPs, not just 1st, do we need to mimic that? + p->lsa = xhost2sockaddr(s, 123); + p->query.fd = -1; + p->query.msg.status = MODE_CLIENT | (NTP_VERSION << 3); + if (STATE_NONE != 0) + p->state = STATE_NONE; + p->trustlevel = TRUSTLEVEL_PATHETIC; + p->query.fd = -1; + set_next(p, 0); + + llist_add_to(&G.ntp_peers, p); + G.peer_cnt++; +} + +static double +gettime(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); /* never fails */ + return (tv.tv_sec + JAN_1970 + 1.0e-6 * tv.tv_usec); +} + + +static void +d_to_tv(double d, struct timeval *tv) +{ + tv->tv_sec = (long)d; + tv->tv_usec = (d - tv->tv_sec) * 1000000; +} + +static double +lfp_to_d(l_fixedpt_t lfp) +{ + double ret; + + lfp.int_partl = ntohl(lfp.int_partl); + lfp.fractionl = ntohl(lfp.fractionl); + ret = (double)(lfp.int_partl) + ((double)lfp.fractionl / UINT_MAX); + return ret; +} + +#if ENABLE_FEATURE_NTPD_SERVER +static l_fixedpt_t +d_to_lfp(double d) +{ + l_fixedpt_t lfp; + + lfp.int_partl = htonl((uint32_t)d); + lfp.fractionl = htonl((uint32_t)((d - (u_int32_t)d) * UINT_MAX)); + return lfp; +} +#endif + +static double +sfp_to_d(s_fixedpt_t sfp) +{ + double ret; + + sfp.int_parts = ntohs(sfp.int_parts); + sfp.fractions = ntohs(sfp.fractions); + ret = (double)(sfp.int_parts) + ((double)sfp.fractions / USHRT_MAX); + return ret; +} + +#if ENABLE_FEATURE_NTPD_SERVER +static s_fixedpt_t +d_to_sfp(double d) +{ + s_fixedpt_t sfp; + + sfp.int_parts = htons((uint16_t)d); + sfp.fractions = htons((uint16_t)((d - (u_int16_t)d) * USHRT_MAX)); + return sfp; +} +#endif + +static void +set_deadline(ntp_peer_t *p, time_t t) +{ + p->deadline = time(NULL) + t; + p->next = 0; +} + +static time_t +error_interval(void) +{ + time_t interval, r; + + interval = INTERVAL_QUERY_PATHETIC * QSCALE_OFF_MAX / QSCALE_OFF_MIN; + r = random() % (interval / 10); + return (interval + r); +} + +static int +sendmsg_wrap(int fd, + const struct sockaddr *from, const struct sockaddr *to, socklen_t addrlen, + ntp_msg_t *msg, ssize_t len) +{ + ssize_t ret; + + errno = 0; + if (!from) { + ret = sendto(fd, msg, len, 0, to, addrlen); + } else { + ret = send_to_from(fd, msg, len, 0, to, from, addrlen); + } + if (ret != len) { + bb_perror_msg("send failed"); + return -1; + } + return 0; +} + +static int +client_query(ntp_peer_t *p) +{ + if (p->query.fd == -1) { + p->query.fd = xsocket(p->lsa->u.sa.sa_family, SOCK_DGRAM, 0); +#if ENABLE_FEATURE_IPV6 + if (p->lsa->u.sa.sa_family == AF_INET) +#endif + setsockopt(p->query.fd, IPPROTO_IP, IP_TOS, &const_IPTOS_LOWDELAY, sizeof(const_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 = random(); + p->query.msg.xmttime.fractionl = random(); + p->query.xmttime = gettime(); + + if (sendmsg_wrap(p->query.fd, /*from:*/ NULL, /*to:*/ &p->lsa->u.sa, /*addrlen:*/ p->lsa->len, + &p->query.msg, NTP_MSGSIZE_NOAUTH) == -1) { + set_next(p, INTERVAL_QUERY_PATHETIC); + return -1; + } + + p->state = STATE_QUERY_SENT; + set_deadline(p, QUERYTIME_MAX); + + return 0; +} + +static int +offset_compare(const void *aa, const void *bb) +{ + const ntp_peer_t * const *a; + const ntp_peer_t * const *b; + + a = aa; + b = bb; + + if ((*a)->update.offset < (*b)->update.offset) + return -1; + return ((*a)->update.offset > (*b)->update.offset); +} + +static uint32_t +updated_scale(double offset) +{ + if (offset < 0) + offset = -offset; + + if (offset > QSCALE_OFF_MAX) + return 1; + if (offset < QSCALE_OFF_MIN) + return QSCALE_OFF_MAX / QSCALE_OFF_MIN; + return QSCALE_OFF_MAX / offset; +} + +static void +adjtime_wrap(void) +{ + ntp_peer_t *p; + unsigned offset_cnt; + int i = 0; + ntp_peer_t **peers; + double offset_median; + llist_t *item; + len_and_sockaddr *lsa; + struct timeval tv, olddelta; + + offset_cnt = 0; + for (item = G.ntp_peers; item != NULL; item = item->link) { + p = (ntp_peer_t *) item->data; + if (p->trustlevel < TRUSTLEVEL_BADPEER) + continue; + if (!p->update.good) + return; + offset_cnt++; + } + + peers = xzalloc(sizeof(ntp_peer_t *) * offset_cnt); + for (item = G.ntp_peers; item != NULL; item = item->link) { + p = (ntp_peer_t *) item->data; + if (p->trustlevel < TRUSTLEVEL_BADPEER) + continue; + peers[i++] = p; + } + + qsort(peers, offset_cnt, sizeof(ntp_peer_t *), offset_compare); + + if (offset_cnt != 0) { + if ((offset_cnt & 1) == 0) { +//TODO: try offset_cnt /= 2... + offset_median = + (peers[offset_cnt / 2 - 1]->update.offset + + peers[offset_cnt / 2]->update.offset) / 2; + G.status.rootdelay = + (peers[offset_cnt / 2 - 1]->update.delay + + peers[offset_cnt / 2]->update.delay) / 2; + G.status.stratum = MAX( + peers[offset_cnt / 2 - 1]->update.status.stratum, + peers[offset_cnt / 2]->update.status.stratum); + } else { + offset_median = peers[offset_cnt / 2]->update.offset; + G.status.rootdelay = peers[offset_cnt / 2]->update.delay; + G.status.stratum = peers[offset_cnt / 2]->update.status.stratum; + } + G.status.leap = peers[offset_cnt / 2]->update.status.leap; + + bb_info_msg("adjusting local clock by %fs", offset_median); + + d_to_tv(offset_median, &tv); + if (adjtime(&tv, &olddelta) == -1) + bb_error_msg("adjtime failed"); + else if (!G.firstadj + && olddelta.tv_sec == 0 + && olddelta.tv_usec == 0 + && !G.status.synced + ) { + bb_info_msg("clock synced"); + G.status.synced = 1; + } else if (G.status.synced) { + bb_info_msg("clock unsynced"); + G.status.synced = 0; + } + + G.firstadj = 0; + G.status.reftime = gettime(); + G.status.stratum++; /* one more than selected peer */ + G.scale = updated_scale(offset_median); + + G.status.refid4 = peers[offset_cnt / 2]->update.status.refid4; + + lsa = peers[offset_cnt / 2]->lsa; + G.status.refid = +#if ENABLE_FEATURE_IPV6 + lsa->u.sa.sa_family != AF_INET ? + G.status.refid4 : +#endif + lsa->u.sin.sin_addr.s_addr; + } + + free(peers); + + for (item = G.ntp_peers; item != NULL; item = item->link) { + p = (ntp_peer_t *) item->data; + p->update.good = 0; + } +} + +static void +settime(double offset) +{ + ntp_peer_t *p; + llist_t *item; + struct timeval tv, curtime; + char buf[80]; + time_t tval; + +#if 0 + if (!G.settime) + return; +#endif + + /* if the offset is small, don't call settimeofday */ + if (offset < SETTIME_MIN_OFFSET && offset > -SETTIME_MIN_OFFSET) + return; + + gettimeofday(&curtime, NULL); /* never fails */ + + d_to_tv(offset, &tv); + curtime.tv_usec += tv.tv_usec + 1000000; + curtime.tv_sec += tv.tv_sec - 1 + (curtime.tv_usec / 1000000); + curtime.tv_usec %= 1000000; + + if (settimeofday(&curtime, NULL) == -1) { + bb_error_msg("settimeofday"); + return; + } + + G.settime = 0; + + tval = curtime.tv_sec; + strftime(buf, sizeof(buf), "%a %b %e %H:%M:%S %Z %Y", localtime(&tval)); + + /* Do we want to print message below to system log when daemonized? */ + bb_info_msg("set local clock to %s (offset %fs)", buf, offset); + + for (item = G.ntp_peers; item != NULL; item = item->link) { + p = (ntp_peer_t *) item->data; + if (p->next) + p->next -= offset; + if (p->deadline) + p->deadline -= offset; + } +} + +static void +client_update(ntp_peer_t *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; + + memcpy(&p->update, &p->reply[best], sizeof(p->update)); + adjtime_wrap(); + + for (i = 0; i < OFFSET_ARRAY_SIZE; i++) + if (p->reply[i].rcvd <= p->reply[best].rcvd) + p->reply[i].good = 0; +} + +static time_t +scale_interval(time_t requested) +{ + time_t interval, r; + + interval = requested * G.scale; + r = random() % MAX(5, interval / 10); + return (interval + r); +} + +static void +client_dispatch(ntp_peer_t *p) +{ + char *addr; + ssize_t size; + ntp_msg_t msg; + double T1, T2, T3, T4; + time_t interval; + ntp_offset_t *offset; + + addr = xmalloc_sockaddr2dotted_noport(&p->lsa->u.sa); + + size = recvfrom(p->query.fd, &msg, sizeof(msg), 0, NULL, NULL); + if (size == -1) { + bb_perror_msg("recvfrom(%s) error", addr); + if (errno == EHOSTUNREACH || errno == EHOSTDOWN + || errno == ENETUNREACH || errno == ENETDOWN + || errno == ECONNREFUSED || errno == EADDRNOTAVAIL + ) { +//TODO: always do this? + set_next(p, error_interval()); + goto bail; + } + xfunc_die(); + } + + T4 = gettime(); + + if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE) { + bb_error_msg("malformed packet received from %s", addr); + goto bail; + } + + if (msg.orgtime.int_partl != p->query.msg.xmttime.int_partl + || msg.orgtime.fractionl != p->query.msg.xmttime.fractionl + ) { + goto bail; + } + + if ((msg.status & LI_ALARM) == LI_ALARM + || msg.stratum == 0 + || msg.stratum > NTP_MAXSTRATUM + ) { + interval = error_interval(); + bb_info_msg("reply from %s: not synced, next query %ds", addr, (int) interval); + goto bail; + } + + /* + * 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); + + offset = &p->reply[p->shift]; + + offset->offset = ((T2 - T1) + (T3 - T4)) / 2; + offset->delay = (T4 - T1) - (T3 - T2); + if (offset->delay < 0) { + interval = error_interval(); + set_next(p, interval); + bb_info_msg("reply from %s: negative delay %f", addr, p->reply[p->shift].delay); + goto bail; + } + offset->error = (T2 - T1) - (T3 - T4); + offset->rcvd = time(NULL); + offset->good = 1; + + offset->status.leap = (msg.status & LI_MASK); + offset->status.precision = msg.precision; + offset->status.rootdelay = sfp_to_d(msg.rootdelay); + offset->status.rootdispersion = sfp_to_d(msg.dispersion); + offset->status.refid = ntohl(msg.refid); + offset->status.refid4 = msg.xmttime.fractionl; + offset->status.reftime = lfp_to_d(msg.reftime); + offset->status.poll = msg.ppoll; + offset->status.stratum = msg.stratum; + + if (p->trustlevel < TRUSTLEVEL_PATHETIC) + interval = scale_interval(INTERVAL_QUERY_PATHETIC); + else if (p->trustlevel < TRUSTLEVEL_AGRESSIVE) + interval = scale_interval(INTERVAL_QUERY_AGRESSIVE); + 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 + ) { + bb_info_msg("peer %s now valid", addr); + } + p->trustlevel++; + } + + bb_info_msg("reply from %s: offset %f delay %f, next query %ds", addr, + offset->offset, offset->delay, (int) interval); + + client_update(p); + if (!G.settime) + settime(offset->offset); + + if (++p->shift >= OFFSET_ARRAY_SIZE) + p->shift = 0; + + bail: + free(addr); +} + +#if ENABLE_FEATURE_NTPD_SERVER +static void +server_dispatch(int fd) +{ + ssize_t size; + uint8_t version; + double rectime; + len_and_sockaddr *to; + struct sockaddr *from; + ntp_msg_t query, reply; + + to = get_sock_lsa(G.listen_fd); + from = xzalloc(to->len); + + size = recv_from_to(fd, &query, sizeof(query), 0, from, &to->u.sa, to->len); + if (size == -1) + bb_error_msg_and_die("recv_from_to"); + if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE) { + char *addr = xmalloc_sockaddr2dotted_noport(from); + bb_error_msg("malformed packet received from %s", addr); + free(addr); + goto bail; + } + + rectime = gettime(); + version = (query.status & VERSION_MASK) >> 3; + + memset(&reply, 0, sizeof(reply)); + reply.status = G.status.synced ? G.status.leap : LI_ALARM; + reply.status |= (query.status & VERSION_MASK); + reply.status |= ((query.status & MODE_MASK) == MODE_CLIENT) ? + MODE_SERVER : MODE_SYM_PAS; + reply.stratum = G.status.stratum; + reply.ppoll = query.ppoll; + reply.precision = G.status.precision; + reply.rectime = d_to_lfp(rectime); + reply.reftime = d_to_lfp(G.status.reftime); + reply.xmttime = d_to_lfp(gettime()); + reply.orgtime = query.xmttime; + reply.rootdelay = d_to_sfp(G.status.rootdelay); + reply.refid = (version > 3) ? G.status.refid4 : G.status.refid; + + /* We reply from the address packet was sent to, + * this makes to/from look swapped here: */ + sendmsg_wrap(fd, /*from:*/ &to->u.sa, /*to:*/ from, /*addrlen:*/ to->len, + &reply, size); + + bail: + free(to); + free(from); +} +#endif + +/* Upstream ntpd's options: + * + * -4 Force DNS resolution of host names to the IPv4 namespace. + * -6 Force DNS resolution of host names to the IPv6 namespace. + * -a Require cryptographic authentication for broadcast client, + * multicast client and symmetric passive associations. + * This is the default. + * -A Do not require cryptographic authentication for broadcast client, + * multicast client and symmetric passive associations. + * This is almost never a good idea. + * -b Enable the client to synchronize to broadcast servers. + * -c conffile + * Specify the name and path of the configuration file, + * default /etc/ntp.conf + * -d Specify debugging mode. This option may occur more than once, + * with each occurrence indicating greater detail of display. + * -D level + * Specify debugging level directly. + * -f driftfile + * Specify the name and path of the frequency file. + * This is the same operation as the "driftfile FILE" + * configuration command. + * -g Normally, ntpd exits with a message to the system log + * if the offset exceeds the panic threshold, which is 1000 s + * by default. This option allows the time to be set to any value + * without restriction; however, this can happen only once. + * If the threshold is exceeded after that, ntpd will exit + * with a message to the system log. This option can be used + * with the -q and -x options. See the tinker command for other options. + * -i jaildir + * Chroot the server to the directory jaildir. This option also implies + * that the server attempts to drop root privileges at startup + * (otherwise, chroot gives very little additional security). + * You may need to also specify a -u option. + * -k keyfile + * Specify the name and path of the symmetric key file, + * default /etc/ntp/keys. This is the same operation + * as the "keys FILE" configuration command. + * -l logfile + * Specify the name and path of the log file. The default + * is the system log file. This is the same operation as + * the "logfile FILE" configuration command. + * -L Do not listen to virtual IPs. The default is to listen. + * -n Don't fork. + * -N To the extent permitted by the operating system, + * run the ntpd at the highest priority. + * -p pidfile + * Specify the name and path of the file used to record the ntpd + * process ID. This is the same operation as the "pidfile FILE" + * configuration command. + * -P priority + * To the extent permitted by the operating system, + * run the ntpd at the specified priority. + * -q Exit the ntpd just after the first time the clock is set. + * This behavior mimics that of the ntpdate program, which is + * to be retired. The -g and -x options can be used with this option. + * Note: The kernel time discipline is disabled with this option. + * -r broadcastdelay + * Specify the default propagation delay from the broadcast/multicast + * server to this client. This is necessary only if the delay + * cannot be computed automatically by the protocol. + * -s statsdir + * Specify the directory path for files created by the statistics + * facility. This is the same operation as the "statsdir DIR" + * configuration command. + * -t key + * Add a key number to the trusted key list. This option can occur + * more than once. + * -u user[:group] + * Specify a user, and optionally a group, to switch to. + * -v variable + * -V variable + * Add a system variable listed by default. + * -x Normally, the time is slewed if the offset is less than the step + * threshold, which is 128 ms by default, and stepped if above + * the threshold. This option sets the threshold to 600 s, which is + * well within the accuracy window to set the clock manually. + * Note: since the slew rate of typical Unix kernels is limited + * to 0.5 ms/s, each second of adjustment requires an amortization + * interval of 2000 s. Thus, an adjustment as much as 600 s + * will take almost 14 days to complete. This option can be used + * with the -g and -q options. See the tinker command for other options. + * Note: The kernel time discipline is disabled with this option. + */ + +enum { + OPT_n = (1 << 0), + OPT_g = (1 << 1), + OPT_p = (1 << 2), + OPT_l = (1 << 3), +}; + +/* By doing init in a separate function we decrease stack usage + * in main loop. + */ +static NOINLINE void ntp_init(char **argv) +{ + unsigned opts; + llist_t *peers; + + tzset(); + + if (getuid()) + bb_error_msg_and_die("need root privileges"); + + peers = NULL; + opt_complementary = "dd:p::"; /* d: counter, p: list */ + opts = getopt32(argv, + "ng" /* compat */ + "p:"IF_FEATURE_NTPD_SERVER("l") /* NOT compat */ + "d" /* compat */ + "46aAbLNx", /* compat, ignored */ + &peers, &G.verbose); +#if ENABLE_FEATURE_NTPD_SERVER + G.listen_fd = -1; + if (opts & OPT_l) { + G.listen_fd = create_and_bind_dgram_or_die(NULL, 123); + socket_want_pktinfo(G.listen_fd); + setsockopt(G.listen_fd, IPPROTO_IP, IP_TOS, &const_IPTOS_LOWDELAY, sizeof(const_IPTOS_LOWDELAY)); + } +#endif + if (opts & OPT_g) + G.settime = 1; + while (peers) + add_peers(llist_pop(&peers)); + if (!(opts & OPT_n)) { + logmode = LOGMODE_NONE; + bb_daemonize(DAEMON_DEVNULL_STDIO); + } + + /* Set some globals */ + { + int prec = 0; + int b; +#if 0 + struct timespec tp; + /* We can use sys_clock_getres but assuming 10ms tick should be fine */ + clock_getres(CLOCK_REALTIME, &tp); + tp.tv_sec = 0; + tp.tv_nsec = 10000000; + b = 1000000000 / tp.tv_nsec; /* convert to Hz */ +#else + b = 100; /* b = 1000000000/10000000 = 100 */ +#endif + while (b > 1) + prec--, b >>= 1; + G.status.precision = prec; + } + G.scale = 1; + G.firstadj = 1; + + bb_signals((1 << SIGTERM) | (1 << SIGINT), record_signo); + bb_signals((1 << SIGPIPE) | (1 << SIGHUP), SIG_IGN); +} + +int ntpd_main(int argc UNUSED_PARAM, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ntpd_main(int argc UNUSED_PARAM, char **argv) +{ + struct globals g; + unsigned new_cnt; + struct pollfd *pfd; + ntp_peer_t **idx2peer; + + memset(&g, 0, sizeof(g)); + SET_PTR_TO_GLOBALS(&g); + + ntp_init(argv); + + new_cnt = g.peer_cnt; + idx2peer = xzalloc(sizeof(void *) * new_cnt); +#if ENABLE_FEATURE_NTPD_SERVER + if (g.listen_fd != -1) + new_cnt++; +#endif + pfd = xzalloc(sizeof(pfd[0]) * new_cnt); + + while (!bb_got_signal) { + llist_t *item; + unsigned i, j, idx_peers; + unsigned sent_cnt, trial_cnt; + int nfds, timeout; + time_t nextaction; + + nextaction = time(NULL) + 3600; + + i = 0; +#if ENABLE_FEATURE_NTPD_SERVER + if (g.listen_fd != -1) { + pfd[0].fd = g.listen_fd; + pfd[0].events = POLLIN; + i++; + } +#endif + idx_peers = i; + sent_cnt = trial_cnt = 0; + for (item = g.ntp_peers; item != NULL; item = item->link) { + ntp_peer_t *p = (ntp_peer_t *) item->data; + + if (p->next > 0 && p->next <= time(NULL)) { + trial_cnt++; + if (client_query(p) == 0) + sent_cnt++; + } + if (p->next > 0 && p->next < nextaction) + nextaction = p->next; + if (p->deadline > 0 && p->deadline < nextaction) + nextaction = p->deadline; + + if (p->deadline > 0 && p->deadline <= time(NULL)) { + char *addr = xmalloc_sockaddr2dotted_noport(&p->lsa->u.sa); + + timeout = error_interval(); + bb_info_msg("no reply from %s received in time, " + "next query %ds", addr, timeout); + if (p->trustlevel >= TRUSTLEVEL_BADPEER) { + p->trustlevel /= 2; + if (p->trustlevel < TRUSTLEVEL_BADPEER) + bb_info_msg("peer %s now invalid", addr); + } + free(addr); + + set_next(p, timeout); + } + + if (p->state == STATE_QUERY_SENT) { + pfd[i].fd = p->query.fd; + pfd[i].events = POLLIN; + idx2peer[i - idx_peers] = p; + i++; + } + } + + if (g.settime + && ((trial_cnt > 0 && sent_cnt == 0) || g.peer_cnt == 0) + ) { + settime(0); /* no good peers, don't wait */ + } + + timeout = nextaction - time(NULL); + if (timeout < 0) + timeout = 0; + + if (g.verbose) + bb_error_msg("entering poll %u secs", timeout); + nfds = poll(pfd, i, timeout * 1000); + + j = 0; +#if ENABLE_FEATURE_NTPD_SERVER + for (; nfds > 0 && j < idx_peers; j++) { + if (pfd[j].revents & (POLLIN|POLLERR)) { + nfds--; + server_dispatch(pfd[j].fd); + } + } +#endif + for (; nfds > 0 && j < i; j++) { + if (pfd[j].revents & (POLLIN|POLLERR)) { + nfds--; + client_dispatch(idx2peer[j - idx_peers]); + } + } + } /* while (!bb_got_signal) */ + + kill_myself_with_sig(bb_got_signal); +} |