xref: /netbsd/usr.sbin/timed/timed/measure.c (revision bf9ec67e)
1 /*	$NetBSD: measure.c,v 1.8 2001/09/02 00:13:06 reinoud 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. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by the University of
18  *	California, Berkeley and its contributors.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #include <sys/cdefs.h>
37 #ifndef lint
38 #if 0
39 static char sccsid[] = "@(#)measure.c	8.2 (Berkeley) 3/26/95";
40 #else
41 __RCSID("$NetBSD: measure.c,v 1.8 2001/09/02 00:13:06 reinoud Exp $");
42 #endif
43 #endif /* not lint */
44 
45 #include "globals.h"
46 #include <netinet/in_systm.h>
47 #include <netinet/ip.h>
48 #include <netinet/ip_icmp.h>
49 
50 #define MSEC_DAY	(SECDAY*1000)
51 
52 #define PACKET_IN	1024
53 
54 #define MSGS		5		/* timestamps to average */
55 #define TRIALS		10		/* max # of timestamps sent */
56 
57 extern int sock_raw;
58 
59 int measure_delta;
60 
61 extern int in_cksum(u_short*, int);
62 
63 static n_short seqno = 0;
64 
65 /*
66  * Measures the differences between machines' clocks using
67  * ICMP timestamp messages.
68  */
69 int					/* status val defined in globals.h */
70 measure(u_long maxmsec,			/* wait this many msec at most */
71 	u_long wmsec,			/* msec to wait for an answer */
72 	char *hname,
73 	struct sockaddr_in *addr,
74 	int print)			/* print complaints on stderr */
75 {
76 	int length;
77 	int measure_status;
78 	int rcvcount, trials;
79 	int cc, count;
80 	fd_set ready;
81 	long sendtime, recvtime, histime1, histime2;
82 	long idelta, odelta, total;
83 	long min_idelta, min_odelta;
84 	struct timeval tdone, tcur, ttrans, twait, tout;
85 	u_char packet[PACKET_IN], opacket[64];
86 	register struct icmp *icp = (struct icmp *) packet;
87 	register struct icmp *oicp = (struct icmp *) opacket;
88 	struct ip *ip = (struct ip *) packet;
89 
90 	min_idelta = min_odelta = 0x7fffffff;
91 	measure_status = HOSTDOWN;
92 	measure_delta = HOSTDOWN;
93 	errno = 0;
94 	trials = 0;
95 
96 	/* open raw socket used to measure time differences */
97 	if (sock_raw < 0) {
98 		sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
99 		if (sock_raw < 0)  {
100 			syslog(LOG_ERR, "opening raw socket: %m");
101 			goto quit;
102 		}
103 	}
104 
105 
106 	/*
107 	 * empty the icmp input queue
108 	 */
109 	FD_ZERO(&ready);
110 	for (;;) {
111 		tout.tv_sec = tout.tv_usec = 0;
112 		FD_SET(sock_raw, &ready);
113 		if (select(sock_raw+1, &ready, 0,0, &tout)) {
114 			length = sizeof(struct sockaddr_in);
115 			cc = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0,
116 				      0,&length);
117 			if (cc < 0)
118 				goto quit;
119 			continue;
120 		}
121 		break;
122 	}
123 
124 	/*
125 	 * Choose the smallest transmission time in each of the two
126 	 * directions. Use these two latter quantities to compute the delta
127 	 * between the two clocks.
128 	 */
129 
130 	oicp->icmp_type = ICMP_TSTAMP;
131 	oicp->icmp_code = 0;
132 	oicp->icmp_id = getpid();
133 	oicp->icmp_rtime = 0;
134 	oicp->icmp_ttime = 0;
135 	oicp->icmp_seq = seqno;
136 
137 	FD_ZERO(&ready);
138 
139 	(void)gettimeofday(&tdone, 0);
140 	mstotvround(&tout, maxmsec);
141 	timeradd(&tdone, &tout, &tdone);	/* when we give up */
142 
143 	mstotvround(&twait, wmsec);
144 
145 	rcvcount = 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 			timeradd(&tcur, &twait, &ttrans);
171 		} else {
172 			ttrans = tdone;
173 		}
174 
175 		while (rcvcount < trials) {
176 			timersub(&ttrans, &tcur, &tout);
177 			if (tout.tv_sec < 0)
178 				tout.tv_sec = 0;
179 
180 			FD_SET(sock_raw, &ready);
181 			count = select(sock_raw+1, &ready, (fd_set *)0,
182 				       (fd_set *)0, &tout);
183 			(void)gettimeofday(&tcur, (struct timezone *)0);
184 			if (count <= 0)
185 				break;
186 
187 			length = sizeof(struct sockaddr_in);
188 			cc = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0,
189 				      0,&length);
190 			if (cc < 0)
191 				goto quit;
192 
193 			/*
194 			 * got something.  See if it is ours
195 			 */
196 			icp = (struct icmp *)(packet + (ip->ip_hl << 2));
197 			if (cc < sizeof(*ip)
198 			    || icp->icmp_type != ICMP_TSTAMPREPLY
199 			    || icp->icmp_id != oicp->icmp_id
200 			    || icp->icmp_seq < seqno
201 			    || icp->icmp_seq >= oicp->icmp_seq)
202 				continue;
203 
204 
205 			sendtime = ntohl(icp->icmp_otime);
206 			recvtime = ((tcur.tv_sec % SECDAY) * 1000 +
207 				    tcur.tv_usec / 1000);
208 
209 			total = recvtime-sendtime;
210 			if (total < 0)	/* do not hassle midnight */
211 				continue;
212 
213 			rcvcount++;
214 			histime1 = ntohl(icp->icmp_rtime);
215 			histime2 = ntohl(icp->icmp_ttime);
216 			/*
217 			 * a host using a time format different from
218 			 * msec. since midnight UT (as per RFC792) should
219 			 * set the high order bit of the 32-bit time
220 			 * value it transmits.
221 			 */
222 			if ((histime1 & 0x80000000) != 0) {
223 				measure_status = NONSTDTIME;
224 				goto quit;
225 			}
226 			measure_status = GOOD;
227 
228 			idelta = recvtime-histime2;
229 			odelta = histime1-sendtime;
230 
231 			/* do not be confused by midnight */
232 			if (idelta < -MSEC_DAY/2) idelta += MSEC_DAY;
233 			else if (idelta > MSEC_DAY/2) idelta -= MSEC_DAY;
234 
235 			if (odelta < -MSEC_DAY/2) odelta += MSEC_DAY;
236 			else if (odelta > MSEC_DAY/2) odelta -= MSEC_DAY;
237 
238 			/* save the quantization error so that we can get a
239 			 * measurement finer than our system clock.
240 			 */
241 			if (total < MIN_ROUND) {
242 				measure_delta = (odelta - idelta)/2;
243 				goto quit;
244 			}
245 
246 			if (idelta < min_idelta)
247 				min_idelta = idelta;
248 			if (odelta < min_odelta)
249 				min_odelta = odelta;
250 
251 			measure_delta = (min_odelta - min_idelta)/2;
252 		}
253 
254 		if (tcur.tv_sec > tdone.tv_sec
255 		    || (tcur.tv_sec == tdone.tv_sec
256 			&& tcur.tv_usec >= tdone.tv_usec))
257 			break;
258 	}
259 
260 quit:
261 	seqno += TRIALS;		/* allocate our sequence numbers */
262 
263 	/*
264 	 * If no answer is received for TRIALS consecutive times,
265 	 * the machine is assumed to be down
266 	 */
267 	if (measure_status == GOOD) {
268 		if (trace) {
269 			fprintf(fd,
270 				"measured delta %4d, %d trials to %-15s %s\n",
271 			   	measure_delta, trials,
272 				inet_ntoa(addr->sin_addr), hname);
273 		}
274 	} else if (print) {
275 		if (errno != 0)
276 			fprintf(stderr, "measure %s: %s\n", hname,
277 				strerror(errno));
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  */
302 void
303 mstotvround(struct timeval *res, long x)
304 {
305 	if (x < 0)
306 		x = -((-x + 3)/5);
307 	else
308 		x = (x+3)/5;
309 	x *= 5;
310 
311 	res->tv_sec = x/1000;
312 	res->tv_usec = (x-res->tv_sec*1000)*1000;
313 	if (res->tv_usec < 0) {
314 		res->tv_usec += 1000000;
315 		res->tv_sec--;
316 	}
317 }
318