1/*	$NetBSD: measure.c,v 1.15 2007/01/25 23:51:11 christos Exp $	*/
2
3/*-
4 * Copyright (c) 1985, 1993 The Regents of the University of California.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33#ifndef lint
34#if 0
35static char sccsid[] = "@(#)measure.c	8.2 (Berkeley) 3/26/95";
36#else
37__RCSID("$NetBSD: measure.c,v 1.15 2007/01/25 23:51:11 christos Exp $");
38#endif
39#endif /* not lint */
40
41#include "globals.h"
42#include <netinet/in_systm.h>
43#include <netinet/ip.h>
44#include <netinet/ip_icmp.h>
45#include <util.h>
46
47#define MSEC_DAY	(SECDAY*1000)
48
49#define PACKET_IN	1024
50
51#define MSGS		5		/* timestamps to average */
52#define TRIALS		10		/* max # of timestamps sent */
53
54extern int sock_raw;
55
56int measure_delta;
57
58extern int in_cksum(const void *, int);
59
60static n_short seqno = 0;
61
62/*
63 * Measures the differences between machines' clocks using
64 * ICMP timestamp messages.
65 */
66int					/* status val defined in globals.h */
67measure(u_long maxmsec,			/* wait this many msec at most */
68	u_long wmsec,			/* msec to wait for an answer */
69	const char *hname,
70	const struct sockaddr_in *addr,
71	int printerr)			/* print complaints on stderr */
72{
73	socklen_t length;
74	int measure_status;
75	int rcvcount, trials;
76	int count;
77	struct pollfd set[1];
78	long sendtime, recvtime, histime1, histime2;
79	long idelta, odelta, total;
80	long min_idelta, min_odelta;
81	struct timeval tdone, tcur, ttrans, twait, tout;
82	u_char packet[PACKET_IN];
83	struct icmp icp;
84	struct icmp oicp;
85	struct ip ip;
86
87	min_idelta = min_odelta = 0x7fffffff;
88	measure_status = HOSTDOWN;
89	measure_delta = HOSTDOWN;
90	errno = 0;
91	trials = 0;
92
93	/* open raw socket used to measure time differences */
94	if (sock_raw < 0) {
95		sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
96		if (sock_raw < 0)  {
97			syslog(LOG_ERR, "opening raw socket: %m");
98			goto quit;
99		}
100	}
101
102	set[0].fd = sock_raw;
103	set[0].events = POLLIN;
104
105	/*
106	 * empty the icmp input queue
107	 */
108	for (;;) {
109		if (poll(set, 1, 0)) {
110			ssize_t ret;
111			length = sizeof(struct sockaddr_in);
112			ret = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0,
113				      0,&length);
114			if (ret < 0)
115				goto quit;
116			continue;
117		}
118		break;
119	}
120
121	/*
122	 * Choose the smallest transmission time in each of the two
123	 * directions. Use these two latter quantities to compute the delta
124	 * between the two clocks.
125	 */
126
127	oicp.icmp_type = ICMP_TSTAMP;
128	oicp.icmp_code = 0;
129	oicp.icmp_id = getpid();
130	oicp.icmp_rtime = 0;
131	oicp.icmp_ttime = 0;
132	oicp.icmp_seq = seqno;
133
134	(void)gettimeofday(&tdone, 0);
135	mstotvround(&tout, maxmsec);
136	timeradd(&tdone, &tout, &tdone);	/* when we give up */
137
138	mstotvround(&twait, wmsec);
139
140	tout.tv_sec = 0;
141	tout.tv_usec = 0;
142	rcvcount = 0;
143	while (rcvcount < MSGS) {
144		(void)gettimeofday(&tcur, 0);
145
146		/*
147		 * keep sending until we have sent the max
148		 */
149		if (trials < TRIALS) {
150			uint32_t otime;
151
152			trials++;
153			otime = (tcur.tv_sec % SECDAY) * 1000
154                                            + tcur.tv_usec / 1000;
155			oicp.icmp_otime = htonl(otime);
156			oicp.icmp_cksum = 0;
157			oicp.icmp_cksum = in_cksum(&oicp, sizeof(oicp));
158
159			count = sendto(sock_raw, &oicp, sizeof(oicp), 0,
160				       (const struct sockaddr*)addr,
161				       sizeof(struct sockaddr));
162			if (count < 0) {
163				if (measure_status == HOSTDOWN)
164					measure_status = UNREACHABLE;
165				goto quit;
166			}
167			oicp.icmp_seq++;
168
169			timeradd(&tcur, &twait, &ttrans);
170		} else {
171			ttrans = tdone;
172		}
173
174		while (rcvcount < trials) {
175			ssize_t ret;
176
177			timersub(&ttrans, &tcur, &tout);
178			if (tout.tv_sec < 0)
179				tout.tv_sec = 0;
180
181			count = poll(set, 1, tout.tv_sec * 1000 + tout.tv_usec / 1000);
182			(void)gettimeofday(&tcur, (struct timezone *)0);
183			if (count <= 0)
184				break;
185
186			length = sizeof(struct sockaddr_in);
187			ret = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0,
188				      0,&length);
189			if (ret < 0)
190				goto quit;
191
192			/*
193			 * got something.  See if it is ours
194			 */
195
196			if ((size_t)ret < sizeof(ip))
197				continue;
198			memcpy(&ip, packet, sizeof(ip));
199			if ((size_t)ret < (size_t)ip.ip_hl << 2)
200				continue;
201			ret -= ip.ip_hl << 2;
202
203			memset(&icp, 0, sizeof(icp));
204			memcpy(&icp, &packet[ip.ip_hl << 2],
205				MIN((size_t)ret, sizeof(icp)));
206
207			if (icp.icmp_type != ICMP_TSTAMPREPLY
208			    || icp.icmp_id != oicp.icmp_id
209			    || icp.icmp_seq < seqno
210			    || icp.icmp_seq >= oicp.icmp_seq)
211				continue;
212
213			sendtime = ntohl(icp.icmp_otime);
214			recvtime = ((tcur.tv_sec % SECDAY) * 1000 +
215				    tcur.tv_usec / 1000);
216
217			total = recvtime - sendtime;
218			if (total < 0)	/* do not hassle midnight */
219				continue;
220
221			rcvcount++;
222			histime1 = ntohl(icp.icmp_rtime);
223			histime2 = ntohl(icp.icmp_ttime);
224			/*
225			 * a host using a time format different from
226			 * msec. since midnight UT (as per RFC792) should
227			 * set the high order bit of the 32-bit time
228			 * value it transmits.
229			 */
230			if ((histime1 & 0x80000000) != 0) {
231				measure_status = NONSTDTIME;
232				goto quit;
233			}
234			measure_status = GOOD;
235
236			idelta = recvtime-histime2;
237			odelta = histime1-sendtime;
238
239			/* do not be confused by midnight */
240			if (idelta < -MSEC_DAY/2) idelta += MSEC_DAY;
241			else if (idelta > MSEC_DAY/2) idelta -= MSEC_DAY;
242
243			if (odelta < -MSEC_DAY/2) odelta += MSEC_DAY;
244			else if (odelta > MSEC_DAY/2) odelta -= MSEC_DAY;
245
246			/* save the quantization error so that we can get a
247			 * measurement finer than our system clock.
248			 */
249			if (total < MIN_ROUND) {
250				measure_delta = (odelta - idelta)/2;
251				goto quit;
252			}
253
254			if (idelta < min_idelta)
255				min_idelta = idelta;
256			if (odelta < min_odelta)
257				min_odelta = odelta;
258
259			measure_delta = (min_odelta - min_idelta)/2;
260		}
261
262		if (tcur.tv_sec > tdone.tv_sec
263		    || (tcur.tv_sec == tdone.tv_sec
264			&& tcur.tv_usec >= tdone.tv_usec))
265			break;
266	}
267
268quit:
269	seqno += TRIALS;		/* allocate our sequence numbers */
270
271	/*
272	 * If no answer is received for TRIALS consecutive times,
273	 * the machine is assumed to be down
274	 */
275	if (measure_status == GOOD) {
276		if (trace) {
277			fprintf(fd,
278				"measured delta %4d, %d trials to %-15s %s\n",
279			   	measure_delta, trials,
280				inet_ntoa(addr->sin_addr), hname);
281		}
282	} else if (printerr) {
283		if (errno != 0)
284			fprintf(stderr, "measure %s: %s\n", hname,
285				strerror(errno));
286	} else {
287		if (errno != 0) {
288			syslog(LOG_ERR, "measure %s: %m", hname);
289		} else {
290			syslog(LOG_ERR, "measure: %s did not respond", hname);
291		}
292		if (trace) {
293			fprintf(fd,
294				"measure: %s failed after %d trials\n",
295				hname, trials);
296			(void)fflush(fd);
297		}
298	}
299
300	return(measure_status);
301}
302
303
304
305
306
307/*
308 * round a number of milliseconds into a struct timeval
309 */
310void
311mstotvround(struct timeval *res, long x)
312{
313	if (x < 0)
314		x = -((-x + 3)/5);
315	else
316		x = (x+3)/5;
317	x *= 5;
318
319	res->tv_sec = x/1000;
320	res->tv_usec = (x-res->tv_sec*1000)*1000;
321	if (res->tv_usec < 0) {
322		res->tv_usec += 1000000;
323		res->tv_sec--;
324	}
325}
326
327void
328update_time(struct timeval *tv, const struct tsp *msg)
329{
330#ifdef SUPPORT_UTMP
331	logwtmp("|", "date", "");
332#endif
333#ifdef SUPPORT_UTMPX
334	logwtmpx("|", "date", "", 0, OLD_TIME);
335#endif
336	tv->tv_sec = msg->tsp_time.tv_sec;
337	tv->tv_usec = msg->tsp_time.tv_usec;
338	(void)settimeofday(tv, 0);
339#ifdef SUPPORT_UTMP
340	logwtmp("}", "date", "");
341#endif
342#ifdef SUPPORT_UTMPX
343	logwtmpx("}", "date", "", 0, NEW_TIME);
344#endif
345}
346