1 /*-
2  * Copyright (c) 2017 Maksym Sobolyev <sobomax@FreeBSD.org>
3  * 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  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 /*
28  * The test that setups two processes A and B and make A sending
29  * B UDP packet(s) and B send it back. The time of sending is recorded
30  * in the payload and time of the arrival is either determined by
31  * reading clock after recv() completes or using kernel-supplied
32  * via recvmsg(). End-to-end time t(A->B->A) is then calculated
33  * and compared against time for both t(A->B) + t(B->A) to make
34  * sure it makes sense.
35  */
36 
37 #include <sys/cdefs.h>
38 __FBSDID("$FreeBSD$");
39 
40 #include <sys/types.h>
41 #include <sys/socket.h>
42 #include <sys/wait.h>
43 #include <sys/time.h>
44 #include <netinet/in.h>
45 #include <arpa/inet.h>
46 #include <err.h>
47 #include <poll.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <strings.h>
52 #include <time.h>
53 #include <unistd.h>
54 
55 #define	NPKTS		1000
56 #define	PKT_SIZE	128
57 /* Timeout to receive pong on the side A, 100ms */
58 #define SRECV_TIMEOUT	(1 * 100)
59 /*
60  * Timeout to receive ping on the side B. 4x as large as on the side A,
61  * so that in the case of packet loss the side A will have a chance to
62  * realize that and send few more before B bails out.
63  */
64 #define RRECV_TIMEOUT	(SRECV_TIMEOUT * 4)
65 #define MIN_NRECV	((NPKTS * 99) / 100) /* 99% */
66 
67 //#define	SIMULATE_PLOSS
68 
69 struct trip_ts {
70     struct timespec sent;
71     struct timespec recvd;
72 };
73 
74 struct test_pkt {
75     int pnum;
76     struct trip_ts tss[2];
77     int lost;
78     unsigned char data[PKT_SIZE];
79 };
80 
81 struct test_ctx {
82     const char *name;
83     int fds[2];
84     struct pollfd pfds[2];
85     union {
86         struct sockaddr_in v4;
87         struct sockaddr_in6 v6;
88     } sin[2];
89     struct test_pkt test_pkts[NPKTS];
90     int nsent;
91     int nrecvd;
92     clockid_t clock;
93     int use_recvmsg;
94     int ts_type;
95 };
96 
97 struct rtt {
98     struct timespec a2b;
99     struct timespec b2a;
100     struct timespec e2e;
101     struct timespec a2b_b2a;
102 };
103 
104 #define SEC(x)		((x)->tv_sec)
105 #define NSEC(x)		((x)->tv_nsec)
106 #define NSEC_MAX	1000000000L
107 #define NSEC_IN_USEC	1000L
108 
109 #define timeval2timespec(tv, ts)                                   \
110     do {                                                           \
111         SEC(ts) = (tv)->tv_sec;                                    \
112         NSEC(ts) = (tv)->tv_usec * NSEC_IN_USEC;                   \
113     } while (0);
114 
115 static const struct timespec zero_ts;
116 /* 0.01s, should be more than enough for the loopback communication  */
117 static const struct timespec max_ts = {.tv_nsec = (NSEC_MAX / 100)};
118 
119 enum ts_types {TT_TIMESTAMP = -2, TT_BINTIME = -1,
120   TT_REALTIME_MICRO = SO_TS_REALTIME_MICRO, TT_TS_BINTIME = SO_TS_BINTIME,
121   TT_REALTIME = SO_TS_REALTIME, TT_MONOTONIC = SO_TS_MONOTONIC};
122 
123 static clockid_t
124 get_clock_type(struct test_ctx *tcp)
125 {
126     switch (tcp->ts_type) {
127     case TT_TIMESTAMP:
128     case TT_BINTIME:
129     case TT_REALTIME_MICRO:
130     case TT_TS_BINTIME:
131     case TT_REALTIME:
132         return (CLOCK_REALTIME);
133 
134     case TT_MONOTONIC:
135         return (CLOCK_MONOTONIC);
136     }
137     abort();
138 }
139 
140 static int
141 get_scm_type(struct test_ctx *tcp)
142 {
143     switch (tcp->ts_type) {
144     case TT_TIMESTAMP:
145     case TT_REALTIME_MICRO:
146         return (SCM_TIMESTAMP);
147 
148     case TT_BINTIME:
149     case TT_TS_BINTIME:
150         return (SCM_BINTIME);
151 
152     case TT_REALTIME:
153         return (SCM_REALTIME);
154 
155     case TT_MONOTONIC:
156         return (SCM_MONOTONIC);
157     }
158     abort();
159 }
160 
161 static size_t
162 get_scm_size(struct test_ctx *tcp)
163 {
164     switch (tcp->ts_type) {
165     case TT_TIMESTAMP:
166     case TT_REALTIME_MICRO:
167         return (sizeof(struct timeval));
168 
169     case TT_BINTIME:
170     case TT_TS_BINTIME:
171         return (sizeof(struct bintime));
172 
173     case TT_REALTIME:
174     case TT_MONOTONIC:
175         return (sizeof(struct timespec));
176     }
177     abort();
178 }
179 
180 static void
181 setup_ts_sockopt(struct test_ctx *tcp, int fd)
182 {
183     int rval, oname1, oname2, sval1, sval2;
184 
185     oname1 = SO_TIMESTAMP;
186     oname2 = -1;
187     sval2 = -1;
188 
189     switch (tcp->ts_type) {
190     case TT_REALTIME_MICRO:
191     case TT_TS_BINTIME:
192     case TT_REALTIME:
193     case TT_MONOTONIC:
194         oname2 = SO_TS_CLOCK;
195         sval2 = tcp->ts_type;
196         break;
197 
198     case TT_TIMESTAMP:
199         break;
200 
201     case TT_BINTIME:
202         oname1 = SO_BINTIME;
203         break;
204 
205     default:
206         abort();
207     }
208 
209     sval1 = 1;
210     rval = setsockopt(fd, SOL_SOCKET, oname1, &sval1,
211       sizeof(sval1));
212     if (rval != 0) {
213         err(1, "%s: setup_udp: setsockopt(%d, %d, 1)", tcp->name,
214           fd, oname1);
215     }
216     if (oname2 == -1)
217         return;
218     rval = setsockopt(fd, SOL_SOCKET, oname2, &sval2,
219       sizeof(sval2));
220     if (rval != 0) {
221         err(1, "%s: setup_udp: setsockopt(%d, %d, %d)",
222           tcp->name, fd, oname2, sval2);
223     }
224 }
225 
226 
227 static void
228 setup_udp(struct test_ctx *tcp)
229 {
230     int i;
231     socklen_t sin_len, af_len;
232 
233     af_len = sizeof(tcp->sin[0].v4);
234     for (i = 0; i < 2; i++) {
235         tcp->sin[i].v4.sin_len = af_len;
236         tcp->sin[i].v4.sin_family = AF_INET;
237         tcp->sin[i].v4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
238         tcp->fds[i] = socket(PF_INET, SOCK_DGRAM, 0);
239         if (tcp->fds[i] < 0)
240             err(1, "%s: setup_udp: socket", tcp->name);
241         if (bind(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], af_len) < 0)
242             err(1, "%s: setup_udp: bind(%s, %d)", tcp->name,
243               inet_ntoa(tcp->sin[i].v4.sin_addr), 0);
244         sin_len = af_len;
245         if (getsockname(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], &sin_len) < 0)
246             err(1, "%s: setup_udp: getsockname(%d)", tcp->name, tcp->fds[i]);
247         if (tcp->use_recvmsg != 0) {
248             setup_ts_sockopt(tcp, tcp->fds[i]);
249         }
250 
251         tcp->pfds[i].fd = tcp->fds[i];
252         tcp->pfds[i].events = POLLIN;
253     }
254 
255     if (connect(tcp->fds[0], (struct sockaddr *)&tcp->sin[1], af_len) < 0)
256         err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,
257           inet_ntoa(tcp->sin[1].v4.sin_addr), ntohs(tcp->sin[1].v4.sin_port));
258     if (connect(tcp->fds[1], (struct sockaddr *)&tcp->sin[0], af_len) < 0)
259         err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,
260           inet_ntoa(tcp->sin[0].v4.sin_addr), ntohs(tcp->sin[0].v4.sin_port));
261 }
262 
263 static char *
264 inet_ntoa6(const void *sin6_addr)
265 {
266     static char straddr[INET6_ADDRSTRLEN];
267 
268     inet_ntop(AF_INET6, sin6_addr, straddr, sizeof(straddr));
269     return (straddr);
270 }
271 
272 static void
273 setup_udp6(struct test_ctx *tcp)
274 {
275     int i;
276     socklen_t sin_len, af_len;
277 
278     af_len = sizeof(tcp->sin[0].v6);
279     for (i = 0; i < 2; i++) {
280         tcp->sin[i].v6.sin6_len = af_len;
281         tcp->sin[i].v6.sin6_family = AF_INET6;
282         tcp->sin[i].v6.sin6_addr = in6addr_loopback;
283         tcp->fds[i] = socket(PF_INET6, SOCK_DGRAM, 0);
284         if (tcp->fds[i] < 0)
285             err(1, "%s: setup_udp: socket", tcp->name);
286         if (bind(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], af_len) < 0)
287             err(1, "%s: setup_udp: bind(%s, %d)", tcp->name,
288               inet_ntoa6(&tcp->sin[i].v6.sin6_addr), 0);
289         sin_len = af_len;
290         if (getsockname(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], &sin_len) < 0)
291             err(1, "%s: setup_udp: getsockname(%d)", tcp->name, tcp->fds[i]);
292         if (tcp->use_recvmsg != 0) {
293             setup_ts_sockopt(tcp, tcp->fds[i]);
294         }
295 
296         tcp->pfds[i].fd = tcp->fds[i];
297         tcp->pfds[i].events = POLLIN;
298     }
299 
300     if (connect(tcp->fds[0], (struct sockaddr *)&tcp->sin[1], af_len) < 0)
301         err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,
302           inet_ntoa6(&tcp->sin[1].v6.sin6_addr),
303           ntohs(tcp->sin[1].v6.sin6_port));
304     if (connect(tcp->fds[1], (struct sockaddr *)&tcp->sin[0], af_len) < 0)
305         err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,
306           inet_ntoa6(&tcp->sin[0].v6.sin6_addr),
307           ntohs(tcp->sin[0].v6.sin6_port));
308 }
309 
310 static void
311 teardown_udp(struct test_ctx *tcp)
312 {
313 
314     close(tcp->fds[0]);
315     close(tcp->fds[1]);
316 }
317 
318 static void
319 send_pkt(struct test_ctx *tcp, int pnum, int fdidx, const char *face)
320 {
321     ssize_t r;
322     size_t slen;
323 
324     slen = sizeof(tcp->test_pkts[pnum]);
325     clock_gettime(get_clock_type(tcp), &tcp->test_pkts[pnum].tss[fdidx].sent);
326     r = send(tcp->fds[fdidx], &tcp->test_pkts[pnum], slen, 0);
327     if (r < 0) {
328         err(1, "%s: %s: send(%d)", tcp->name, face, tcp->fds[fdidx]);
329     }
330     if (r < (ssize_t)slen) {
331         errx(1, "%s: %s: send(%d): short send", tcp->name, face,
332           tcp->fds[fdidx]);
333     }
334     tcp->nsent += 1;
335 }
336 
337 #define PDATA(tcp, i) ((tcp)->test_pkts[(i)].data)
338 
339 static void
340 hdr_extract_ts(struct test_ctx *tcp, struct msghdr *mhp, struct timespec *tp)
341 {
342     int scm_type;
343     size_t scm_size;
344     union {
345         struct timespec ts;
346         struct bintime bt;
347         struct timeval tv;
348     } tdata;
349     struct cmsghdr *cmsg;
350 
351     scm_type = get_scm_type(tcp);
352     scm_size = get_scm_size(tcp);
353     for (cmsg = CMSG_FIRSTHDR(mhp); cmsg != NULL;
354       cmsg = CMSG_NXTHDR(mhp, cmsg)) {
355         if ((cmsg->cmsg_level == SOL_SOCKET) &&
356           (cmsg->cmsg_type == scm_type)) {
357             memcpy(&tdata, CMSG_DATA(cmsg), scm_size);
358             break;
359         }
360     }
361     if (cmsg == NULL) {
362         abort();
363     }
364     switch (tcp->ts_type) {
365     case TT_REALTIME:
366     case TT_MONOTONIC:
367         *tp = tdata.ts;
368         break;
369 
370     case TT_TIMESTAMP:
371     case TT_REALTIME_MICRO:
372         timeval2timespec(&tdata.tv, tp);
373         break;
374 
375     case TT_BINTIME:
376     case TT_TS_BINTIME:
377         bintime2timespec(&tdata.bt, tp);
378         break;
379 
380     default:
381         abort();
382     }
383 }
384 
385 static void
386 recv_pkt_recvmsg(struct test_ctx *tcp, int fdidx, const char *face, void *buf,
387   size_t rlen, struct timespec *tp)
388 {
389     /* We use a union to make sure hdr is aligned */
390     union {
391         struct cmsghdr hdr;
392         unsigned char buf[CMSG_SPACE(1024)];
393     } cmsgbuf;
394     struct msghdr msg;
395     struct iovec iov;
396     ssize_t rval;
397 
398     memset(&msg, '\0', sizeof(msg));
399     iov.iov_base = buf;
400     iov.iov_len = rlen;
401     msg.msg_iov = &iov;
402     msg.msg_iovlen = 1;
403     msg.msg_control = cmsgbuf.buf;
404     msg.msg_controllen = sizeof(cmsgbuf.buf);
405 
406     rval = recvmsg(tcp->fds[fdidx], &msg, 0);
407     if (rval < 0) {
408         err(1, "%s: %s: recvmsg(%d)", tcp->name, face, tcp->fds[fdidx]);
409     }
410     if (rval < (ssize_t)rlen) {
411         errx(1, "%s: %s: recvmsg(%d): short recv", tcp->name, face,
412           tcp->fds[fdidx]);
413     }
414 
415     hdr_extract_ts(tcp, &msg, tp);
416 }
417 
418 static void
419 recv_pkt_recv(struct test_ctx *tcp, int fdidx, const char *face, void *buf,
420   size_t rlen, struct timespec *tp)
421 {
422     ssize_t rval;
423 
424     rval = recv(tcp->fds[fdidx], buf, rlen, 0);
425     clock_gettime(get_clock_type(tcp), tp);
426     if (rval < 0) {
427         err(1, "%s: %s: recv(%d)", tcp->name, face, tcp->fds[fdidx]);
428     }
429     if (rval < (ssize_t)rlen) {
430         errx(1, "%s: %s: recv(%d): short recv", tcp->name, face,
431             tcp->fds[fdidx]);
432     }
433 }
434 
435 static int
436 recv_pkt(struct test_ctx *tcp, int fdidx, const char *face, int tout)
437 {
438     int pr;
439     struct test_pkt recv_buf;
440     size_t rlen;
441 
442     pr = poll(&tcp->pfds[fdidx], 1, tout);
443     if (pr < 0) {
444         err(1, "%s: %s: poll(%d)", tcp->name, face, tcp->fds[fdidx]);
445     }
446     if (pr == 0) {
447         return (-1);
448     }
449     if(tcp->pfds[fdidx].revents != POLLIN) {
450         errx(1, "%s: %s: poll(%d): unexpected result", tcp->name, face,
451           tcp->fds[fdidx]);
452     }
453     rlen = sizeof(recv_buf);
454     if (tcp->use_recvmsg == 0) {
455         recv_pkt_recv(tcp, fdidx, face, &recv_buf, rlen,
456           &recv_buf.tss[fdidx].recvd);
457     } else {
458         recv_pkt_recvmsg(tcp, fdidx, face, &recv_buf, rlen,
459           &recv_buf.tss[fdidx].recvd);
460     }
461     if (recv_buf.pnum < 0 || recv_buf.pnum >= NPKTS ||
462       memcmp(recv_buf.data, PDATA(tcp, recv_buf.pnum), PKT_SIZE) != 0) {
463         errx(1, "%s: %s: recv(%d): corrupted data, packet %d", tcp->name,
464           face, tcp->fds[fdidx], recv_buf.pnum);
465     }
466     tcp->nrecvd += 1;
467     memcpy(tcp->test_pkts[recv_buf.pnum].tss, recv_buf.tss,
468       sizeof(recv_buf.tss));
469     tcp->test_pkts[recv_buf.pnum].lost = 0;
470     return (recv_buf.pnum);
471 }
472 
473 static void
474 test_server(struct test_ctx *tcp)
475 {
476     int i, j;
477 
478     for (i = 0; i < NPKTS; i++) {
479         send_pkt(tcp, i, 0, __FUNCTION__);
480         j = recv_pkt(tcp, 0, __FUNCTION__, SRECV_TIMEOUT);
481         if (j < 0) {
482             warnx("packet %d is lost", i);
483             /* timeout */
484             continue;
485         }
486     }
487 }
488 
489 static void
490 test_client(struct test_ctx *tcp)
491 {
492     int i, j;
493 
494     for (i = 0; i < NPKTS; i++) {
495         j = recv_pkt(tcp, 1, __FUNCTION__, RRECV_TIMEOUT);
496         if (j < 0) {
497             /* timeout */
498             return;
499         }
500 #if defined(SIMULATE_PLOSS)
501         if ((i % 99) == 0) {
502             warnx("dropping packet %d", i);
503             continue;
504         }
505 #endif
506         send_pkt(tcp, j, 1, __FUNCTION__);
507     }
508 }
509 
510 static void
511 calc_rtt(struct test_pkt *tpp, struct rtt *rttp)
512 {
513 
514     timespecsub(&tpp->tss[1].recvd, &tpp->tss[0].sent, &rttp->a2b);
515     timespecsub(&tpp->tss[0].recvd, &tpp->tss[1].sent, &rttp->b2a);
516     timespecadd(&rttp->a2b, &rttp->b2a, &rttp->a2b_b2a);
517     timespecsub(&tpp->tss[0].recvd, &tpp->tss[0].sent, &rttp->e2e);
518 }
519 
520 static void
521 test_run(int ts_type, int use_ipv6, int use_recvmsg, const char *name)
522 {
523     struct test_ctx test_ctx;
524     pid_t pid, cpid;
525     int i, j, status;
526 
527     printf("Testing %s via %s: ", name, (use_ipv6 == 0) ? "IPv4" : "IPv6");
528     fflush(stdout);
529     bzero(&test_ctx, sizeof(test_ctx));
530     test_ctx.name = name;
531     test_ctx.use_recvmsg = use_recvmsg;
532     test_ctx.ts_type = ts_type;
533     if (use_ipv6 == 0) {
534         setup_udp(&test_ctx);
535     } else {
536         setup_udp6(&test_ctx);
537     }
538     for (i = 0; i < NPKTS; i++) {
539         test_ctx.test_pkts[i].pnum = i;
540         test_ctx.test_pkts[i].lost = 1;
541         for (j = 0; j < PKT_SIZE; j++) {
542             test_ctx.test_pkts[i].data[j] = (unsigned char)random();
543         }
544     }
545     cpid = fork();
546     if (cpid < 0) {
547         err(1, "%s: fork()", test_ctx.name);
548     }
549     if (cpid == 0) {
550         test_client(&test_ctx);
551         exit(0);
552     }
553     test_server(&test_ctx);
554     pid = waitpid(cpid, &status, 0);
555     if (pid == (pid_t)-1) {
556         err(1, "%s: waitpid(%d)", test_ctx.name, cpid);
557     }
558 
559     if (WIFEXITED(status)) {
560         if (WEXITSTATUS(status) != EXIT_SUCCESS) {
561             errx(1, "client exit status is %d",
562               WEXITSTATUS(status));
563         }
564     } else {
565         if (WIFSIGNALED(status))
566             errx(1, "abnormal termination of client, signal %d%s",
567               WTERMSIG(status), WCOREDUMP(status) ?
568               " (core file generated)" : "");
569         else
570             errx(1, "termination of client, unknown status");
571     }
572     if (test_ctx.nrecvd < MIN_NRECV) {
573         errx(1, "packet loss is too high %d received out of %d, min %d",
574           test_ctx.nrecvd, test_ctx.nsent, MIN_NRECV);
575     }
576     for (i = 0; i < NPKTS; i++) {
577         struct rtt rtt;
578         if (test_ctx.test_pkts[i].lost != 0) {
579             continue;
580         }
581         calc_rtt(&test_ctx.test_pkts[i], &rtt);
582         if (!timespeccmp(&rtt.e2e, &rtt.a2b_b2a, >))
583             errx(1, "end-to-end trip time is too small");
584         if (!timespeccmp(&rtt.e2e, &max_ts, <))
585             errx(1, "end-to-end trip time is too large");
586         if (!timespeccmp(&rtt.a2b, &zero_ts, >))
587             errx(1, "A2B trip time is not positive");
588         if (!timespeccmp(&rtt.b2a, &zero_ts, >))
589             errx(1, "B2A trip time is not positive");
590     }
591     teardown_udp(&test_ctx);
592 }
593 
594 int
595 main(void)
596 {
597     int i;
598 
599     srandomdev();
600 
601     for (i = 0; i < 2; i++) {
602         test_run(0, i, 0, "send()/recv()");
603         printf("OK\n");
604         test_run(TT_TIMESTAMP, i, 1,
605           "send()/recvmsg(), setsockopt(SO_TIMESTAMP, 1)");
606         printf("OK\n");
607         if (i == 0) {
608             test_run(TT_BINTIME, i, 1,
609               "send()/recvmsg(), setsockopt(SO_BINTIME, 1)");
610             printf("OK\n");
611         }
612         test_run(TT_REALTIME_MICRO, i, 1,
613           "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME_MICRO)");
614         printf("OK\n");
615         test_run(TT_TS_BINTIME, i, 1,
616           "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_BINTIME)");
617         printf("OK\n");
618         test_run(TT_REALTIME, i, 1,
619           "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME)");
620         printf("OK\n");
621         test_run(TT_MONOTONIC, i, 1,
622           "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_MONOTONIC)");
623         printf("OK\n");
624     }
625     exit(0);
626 }
627