Initial import from FreeBSD RELENG_4:
[dragonfly.git] / usr.sbin / timed / timed / measure.c
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
36 static char sccsid[] = "@(#)measure.c   8.1 (Berkeley) 6/6/93";
37 #endif
38 static const char rcsid[] =
39   "$FreeBSD: src/usr.sbin/timed/timed/measure.c,v 1.6 1999/08/28 01:20:17 peter 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
54 extern int sock_raw;
55
56 int measure_delta;
57
58 static n_short seqno = 0;
59
60 /*
61  * Measures the differences between machines' clocks using
62  * ICMP timestamp messages.
63  */
64 int                                     /* status val defined in globals.h */
65 measure(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
261 quit:
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  */
302 void
303 mstotvround(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
322 void
323 timevaladd(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
338 void
339 timevalsub(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 }