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