measure.c revision 209344
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  "$FreeBSD: head/usr.sbin/timed/timed/measure.c 209344 2010-06-19 17:04:01Z gavin $";
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	trials = 0;
90	errno = 0;
91
92	/* open raw socket used to measure time differences */
93	if (sock_raw < 0) {
94		sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
95		if (sock_raw < 0)  {
96			syslog(LOG_ERR, "opening raw socket: %m");
97			goto quit;
98		}
99	}
100
101
102	/*
103	 * empty the icmp input queue
104	 */
105	FD_ZERO(&ready);
106	for (;;) {
107		tout.tv_sec = tout.tv_usec = 0;
108		FD_SET(sock_raw, &ready);
109		if (select(sock_raw+1, &ready, 0,0, &tout)) {
110			length = sizeof(struct sockaddr_in);
111			cc = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0,
112				      0,&length);
113			if (cc < 0)
114				goto quit;
115			continue;
116		}
117		break;
118	}
119
120	/*
121	 * Choose the smallest transmission time in each of the two
122	 * directions. Use these two latter quantities to compute the delta
123	 * between the two clocks.
124	 */
125
126	oicp->icmp_type = ICMP_TSTAMP;
127	oicp->icmp_code = 0;
128	oicp->icmp_id = getpid();
129	oicp->icmp_rtime = 0;
130	oicp->icmp_ttime = 0;
131	oicp->icmp_seq = seqno;
132
133	FD_ZERO(&ready);
134
135	(void)gettimeofday(&tdone, 0);
136	mstotvround(&tout, maxmsec);
137	timevaladd(&tdone, &tout);		/* when we give up */
138
139	mstotvround(&twait, wmsec);
140
141	rcvcount = 0;
142	while (rcvcount < MSGS) {
143		(void)gettimeofday(&tcur, 0);
144
145		/*
146		 * keep sending until we have sent the max
147		 */
148		if (trials < TRIALS) {
149			trials++;
150			oicp->icmp_otime = htonl((tcur.tv_sec % SECDAY) * 1000
151						 + tcur.tv_usec / 1000);
152			oicp->icmp_cksum = 0;
153			oicp->icmp_cksum = in_cksum((u_short*)oicp,
154						    sizeof(*oicp));
155
156			count = sendto(sock_raw, opacket, sizeof(*oicp), 0,
157				       (struct sockaddr*)addr,
158				       sizeof(struct sockaddr));
159			if (count < 0) {
160				if (measure_status == HOSTDOWN)
161					measure_status = UNREACHABLE;
162				goto quit;
163			}
164			++oicp->icmp_seq;
165
166			ttrans = tcur;
167			timevaladd(&ttrans, &twait);
168		} else {
169			ttrans = tdone;
170		}
171
172		while (rcvcount < trials) {
173			timevalsub(&tout, &ttrans, &tcur);
174			if (tout.tv_sec < 0)
175				tout.tv_sec = 0;
176
177			FD_SET(sock_raw, &ready);
178			count = select(sock_raw+1, &ready, (fd_set *)0,
179				       (fd_set *)0, &tout);
180			(void)gettimeofday(&tcur, (struct timezone *)0);
181			if (count <= 0)
182				break;
183
184			length = sizeof(struct sockaddr_in);
185			cc = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0,
186				      0,&length);
187			if (cc < 0)
188				goto quit;
189
190			/*
191			 * got something.  See if it is ours
192			 */
193			icp = (struct icmp *)(packet + (ip->ip_hl << 2));
194			if (cc < sizeof(*ip)
195			    || icp->icmp_type != ICMP_TSTAMPREPLY
196			    || icp->icmp_id != oicp->icmp_id
197			    || icp->icmp_seq < seqno
198			    || icp->icmp_seq >= oicp->icmp_seq)
199				continue;
200
201
202			sendtime = ntohl(icp->icmp_otime);
203			recvtime = ((tcur.tv_sec % SECDAY) * 1000 +
204				    tcur.tv_usec / 1000);
205
206			total = recvtime-sendtime;
207			if (total < 0)	/* do not hassle midnight */
208				continue;
209
210			rcvcount++;
211			histime1 = ntohl(icp->icmp_rtime);
212			histime2 = ntohl(icp->icmp_ttime);
213			/*
214			 * a host using a time format different from
215			 * msec. since midnight UT (as per RFC792) should
216			 * set the high order bit of the 32-bit time
217			 * value it transmits.
218			 */
219			if ((histime1 & 0x80000000) != 0) {
220				measure_status = NONSTDTIME;
221				goto quit;
222			}
223			measure_status = GOOD;
224
225			idelta = recvtime-histime2;
226			odelta = histime1-sendtime;
227
228			/* do not be confused by midnight */
229			if (idelta < -MSEC_DAY/2) idelta += MSEC_DAY;
230			else if (idelta > MSEC_DAY/2) idelta -= MSEC_DAY;
231
232			if (odelta < -MSEC_DAY/2) odelta += MSEC_DAY;
233			else if (odelta > MSEC_DAY/2) odelta -= MSEC_DAY;
234
235			/* save the quantization error so that we can get a
236			 * measurement finer than our system clock.
237			 */
238			if (total < MIN_ROUND) {
239				measure_delta = (odelta - idelta)/2;
240				goto quit;
241			}
242
243			if (idelta < min_idelta)
244				min_idelta = idelta;
245			if (odelta < min_odelta)
246				min_odelta = odelta;
247
248			measure_delta = (min_odelta - min_idelta)/2;
249		}
250
251		if (tcur.tv_sec > tdone.tv_sec
252		    || (tcur.tv_sec == tdone.tv_sec
253			&& tcur.tv_usec >= tdone.tv_usec))
254			break;
255	}
256
257quit:
258	seqno += TRIALS;		/* allocate our sequence numbers */
259
260	/*
261	 * If no answer is received for TRIALS consecutive times,
262	 * the machine is assumed to be down
263	 */
264	if (measure_status == GOOD) {
265		if (trace) {
266			fprintf(fd,
267				"measured delta %4d, %d trials to %-15s %s\n",
268			   	measure_delta, trials,
269				inet_ntoa(addr->sin_addr), hname);
270		}
271	} else if (print) {
272		if (errno != 0)
273			warn("measure %s", hname);
274	} else {
275		if (errno != 0) {
276			syslog(LOG_ERR, "measure %s: %m", hname);
277		} else {
278			syslog(LOG_ERR, "measure: %s did not respond", hname);
279		}
280		if (trace) {
281			fprintf(fd,
282				"measure: %s failed after %d trials\n",
283				hname, trials);
284			(void)fflush(fd);
285		}
286	}
287
288	return(measure_status);
289}
290
291
292
293
294
295/*
296 * round a number of milliseconds into a struct timeval
297 */
298void
299mstotvround(res, x)
300	struct timeval *res;
301	long x;
302{
303	if (x < 0)
304		x = -((-x + 3)/5);
305	else
306		x = (x+3)/5;
307	x *= 5;
308	res->tv_sec = x/1000;
309	res->tv_usec = (x-res->tv_sec*1000)*1000;
310	if (res->tv_usec < 0) {
311		res->tv_usec += 1000000;
312		res->tv_sec--;
313	}
314}
315
316void
317timevaladd(tv1, tv2)
318	struct timeval *tv1, *tv2;
319{
320	tv1->tv_sec += tv2->tv_sec;
321	tv1->tv_usec += tv2->tv_usec;
322	if (tv1->tv_usec >= 1000000) {
323		tv1->tv_sec++;
324		tv1->tv_usec -= 1000000;
325	}
326	if (tv1->tv_usec < 0) {
327		tv1->tv_sec--;
328		tv1->tv_usec += 1000000;
329	}
330}
331
332void
333timevalsub(res, tv1, tv2)
334	struct timeval *res, *tv1, *tv2;
335{
336	res->tv_sec = tv1->tv_sec - tv2->tv_sec;
337	res->tv_usec = tv1->tv_usec - tv2->tv_usec;
338	if (res->tv_usec >= 1000000) {
339		res->tv_sec++;
340		res->tv_usec -= 1000000;
341	}
342	if (res->tv_usec < 0) {
343		res->tv_sec--;
344		res->tv_usec += 1000000;
345	}
346}
347