1 /*
2  * testcode/perf.c - debug program to estimate name server performance.
3  *
4  * Copyright (c) 2008, NLnet Labs. All rights reserved.
5  *
6  * This software is open source.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * Redistributions of source code must retain the above copyright notice,
13  * this list of conditions and the following disclaimer.
14  *
15  * Redistributions in binary form must reproduce the above copyright notice,
16  * this list of conditions and the following disclaimer in the documentation
17  * and/or other materials provided with the distribution.
18  *
19  * Neither the name of the NLNET LABS nor the names of its contributors may
20  * be used to endorse or promote products derived from this software without
21  * specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34  */
35 
36 /**
37  * \file
38  *
39  * This program estimates DNS name server performance.
40  */
41 
42 #include "config.h"
43 #ifdef HAVE_GETOPT_H
44 #include <getopt.h>
45 #endif
46 #include <signal.h>
47 #include "util/log.h"
48 #include "util/locks.h"
49 #include "util/net_help.h"
50 #include "util/data/msgencode.h"
51 #include "util/data/msgreply.h"
52 #include "util/data/msgparse.h"
53 #include "sldns/sbuffer.h"
54 #include "sldns/wire2str.h"
55 #include "sldns/str2wire.h"
56 #include <sys/time.h>
57 
58 /** usage information for perf */
usage(char * nm)59 static void usage(char* nm)
60 {
61 	printf("usage: %s [options] server\n", nm);
62 	printf("server: ip address of server, IP4 or IP6.\n");
63 	printf("	If not on port %d add @port.\n", UNBOUND_DNS_PORT);
64 	printf("-d sec	duration of test in whole seconds (0: wait for ^C)\n");
65 	printf("-a str	query to ask, interpreted as a line from qfile\n");
66 	printf("-f fnm	query list to read from file\n");
67 	printf("	every line has format: qname qclass qtype [+-]{E}\n");
68 	printf("	where + means RD set, E means EDNS enabled\n");
69 	printf("-q 	quiet mode, print only final qps\n");
70 	exit(1);
71 }
72 
73 struct perfinfo;
74 struct perfio;
75 
76 /** Global info for perf */
77 struct perfinfo {
78 	/** need to exit */
79 	volatile int exit;
80 	/** all purpose buffer (for UDP send and receive) */
81 	sldns_buffer* buf;
82 
83 	/** destination */
84 	struct sockaddr_storage dest;
85 	/** length of dest socket addr */
86 	socklen_t destlen;
87 
88 	/** when did this time slice start */
89 	struct timeval since;
90 	/** number of queries received in that time */
91 	size_t numrecv;
92 	/** number of queries sent out in that time */
93 	size_t numsent;
94 
95 	/** duration of test in seconds */
96 	int duration;
97 	/** quiet mode? */
98 	int quiet;
99 
100 	/** when did the total test start */
101 	struct timeval start;
102 	/** total number recvd */
103 	size_t total_recv;
104 	/** total number sent */
105 	size_t total_sent;
106 	/** numbers by rcode */
107 	size_t by_rcode[32];
108 
109 	/** number of I/O ports */
110 	size_t io_num;
111 	/** I/O ports array */
112 	struct perfio* io;
113 	/** max fd value in io ports */
114 	int maxfd;
115 	/** readset */
116 	fd_set rset;
117 
118 	/** size of querylist */
119 	size_t qlist_size;
120 	/** allocated size of qlist array */
121 	size_t qlist_capacity;
122 	/** list of query packets (data) */
123 	uint8_t** qlist_data;
124 	/** list of query packets (length of a packet) */
125 	size_t* qlist_len;
126 	/** index into querylist, for walking the list */
127 	size_t qlist_idx;
128 };
129 
130 /** I/O port for perf */
131 struct perfio {
132 	/** id number */
133 	size_t id;
134 	/** file descriptor of socket */
135 	int fd;
136 	/** timeout value */
137 	struct timeval timeout;
138 	/** ptr back to perfinfo */
139 	struct perfinfo* info;
140 };
141 
142 /** number of msec between starting io ports */
143 #define START_IO_INTERVAL 10
144 /** number of msec timeout on io ports */
145 #define IO_TIMEOUT 10
146 
147 /** signal handler global info */
148 static struct perfinfo* sig_info;
149 
150 /** signal handler for user quit */
perf_sigh(int sig)151 static RETSIGTYPE perf_sigh(int sig)
152 {
153 	log_assert(sig_info);
154 	if(!sig_info->quiet)
155 		printf("exit on signal %d\n", sig);
156 	sig_info->exit = 1;
157 }
158 
159 /** timeval compare, t1 < t2 */
160 static int
perf_tv_smaller(struct timeval * t1,struct timeval * t2)161 perf_tv_smaller(struct timeval* t1, struct timeval* t2)
162 {
163 #ifndef S_SPLINT_S
164 	if(t1->tv_sec < t2->tv_sec)
165 		return 1;
166 	if(t1->tv_sec == t2->tv_sec &&
167 		t1->tv_usec < t2->tv_usec)
168 		return 1;
169 #endif
170 	return 0;
171 }
172 
173 /** timeval add, t1 += t2 */
174 static void
perf_tv_add(struct timeval * t1,struct timeval * t2)175 perf_tv_add(struct timeval* t1, struct timeval* t2)
176 {
177 #ifndef S_SPLINT_S
178 	t1->tv_sec += t2->tv_sec;
179 	t1->tv_usec += t2->tv_usec;
180 	while(t1->tv_usec >= 1000000) {
181 		t1->tv_usec -= 1000000;
182 		t1->tv_sec++;
183 	}
184 #endif
185 }
186 
187 /** timeval subtract, t1 -= t2 */
188 static void
perf_tv_subtract(struct timeval * t1,struct timeval * t2)189 perf_tv_subtract(struct timeval* t1, struct timeval* t2)
190 {
191 #ifndef S_SPLINT_S
192 	t1->tv_sec -= t2->tv_sec;
193 	if(t1->tv_usec >= t2->tv_usec) {
194 		t1->tv_usec -= t2->tv_usec;
195 	} else {
196 		t1->tv_sec--;
197 		t1->tv_usec = 1000000-(t2->tv_usec-t1->tv_usec);
198 	}
199 #endif
200 }
201 
202 
203 /** setup perf test environment */
204 static void
perfsetup(struct perfinfo * info)205 perfsetup(struct perfinfo* info)
206 {
207 	size_t i;
208 	if(gettimeofday(&info->start, NULL) < 0)
209 		fatal_exit("gettimeofday: %s", strerror(errno));
210 	sig_info = info;
211 	if( signal(SIGINT, perf_sigh) == SIG_ERR ||
212 #ifdef SIGQUIT
213 		signal(SIGQUIT, perf_sigh) == SIG_ERR ||
214 #endif
215 #ifdef SIGHUP
216 		signal(SIGHUP, perf_sigh) == SIG_ERR ||
217 #endif
218 #ifdef SIGBREAK
219 		signal(SIGBREAK, perf_sigh) == SIG_ERR ||
220 #endif
221 		signal(SIGTERM, perf_sigh) == SIG_ERR)
222 		fatal_exit("could not bind to signal");
223 	info->io = (struct perfio*)calloc(sizeof(struct perfio), info->io_num);
224 	if(!info->io) fatal_exit("out of memory");
225 #ifndef S_SPLINT_S
226 	FD_ZERO(&info->rset);
227 #endif
228 	info->since = info->start;
229 	for(i=0; i<info->io_num; i++) {
230 		info->io[i].id = i;
231 		info->io[i].info = info;
232 		info->io[i].fd = socket(
233 			addr_is_ip6(&info->dest, info->destlen)?
234 			AF_INET6:AF_INET, SOCK_DGRAM, 0);
235 		if(info->io[i].fd == -1) {
236 			fatal_exit("socket: %s", sock_strerror(errno));
237 		}
238 		if(info->io[i].fd > info->maxfd)
239 			info->maxfd = info->io[i].fd;
240 #ifndef S_SPLINT_S
241 		FD_SET(FD_SET_T info->io[i].fd, &info->rset);
242 		info->io[i].timeout.tv_usec = ((START_IO_INTERVAL*i)%1000)
243 						*1000;
244 		info->io[i].timeout.tv_sec = (START_IO_INTERVAL*i)/1000;
245 		perf_tv_add(&info->io[i].timeout, &info->since);
246 #endif
247 	}
248 }
249 
250 /** cleanup perf test environment */
251 static void
perffree(struct perfinfo * info)252 perffree(struct perfinfo* info)
253 {
254 	size_t i;
255 	if(!info) return;
256 	if(info->io) {
257 		for(i=0; i<info->io_num; i++) {
258 			sock_close(info->io[i].fd);
259 		}
260 		free(info->io);
261 	}
262 	for(i=0; i<info->qlist_size; i++)
263 		free(info->qlist_data[i]);
264 	free(info->qlist_data);
265 	free(info->qlist_len);
266 }
267 
268 /** send new query for io */
269 static void
perfsend(struct perfinfo * info,size_t n,struct timeval * now)270 perfsend(struct perfinfo* info, size_t n, struct timeval* now)
271 {
272 	ssize_t r;
273 	r = sendto(info->io[n].fd, (void*)info->qlist_data[info->qlist_idx],
274 		info->qlist_len[info->qlist_idx], 0,
275 		(struct sockaddr*)&info->dest, info->destlen);
276 	/*log_hex("send", info->qlist_data[info->qlist_idx],
277 		info->qlist_len[info->qlist_idx]);*/
278 	if(r == -1) {
279 		log_err("sendto: %s", sock_strerror(errno));
280 	} else if(r != (ssize_t)info->qlist_len[info->qlist_idx]) {
281 		log_err("partial sendto");
282 	}
283 	info->qlist_idx = (info->qlist_idx+1) % info->qlist_size;
284 	info->numsent++;
285 
286 	info->io[n].timeout.tv_sec = IO_TIMEOUT/1000;
287 	info->io[n].timeout.tv_usec = (IO_TIMEOUT%1000)*1000;
288 	perf_tv_add(&info->io[n].timeout, now);
289 }
290 
291 /** got reply for io */
292 static void
perfreply(struct perfinfo * info,size_t n,struct timeval * now)293 perfreply(struct perfinfo* info, size_t n, struct timeval* now)
294 {
295 	ssize_t r;
296 	r = recv(info->io[n].fd, (void*)sldns_buffer_begin(info->buf),
297 		sldns_buffer_capacity(info->buf), 0);
298 	if(r == -1) {
299 		log_err("recv: %s", sock_strerror(errno));
300 	} else {
301 		info->by_rcode[LDNS_RCODE_WIRE(sldns_buffer_begin(
302 			info->buf))]++;
303 		info->numrecv++;
304 	}
305 	/*sldns_buffer_set_limit(info->buf, r);
306 	log_buf(0, "reply", info->buf);*/
307 	perfsend(info, n, now);
308 }
309 
310 /** got timeout for io */
311 static void
perftimeout(struct perfinfo * info,size_t n,struct timeval * now)312 perftimeout(struct perfinfo* info, size_t n, struct timeval* now)
313 {
314 	/* may not be a dropped packet, this is also used to start
315 	 * up the sending IOs */
316 	perfsend(info, n, now);
317 }
318 
319 /** print nice stats about qps */
320 static void
stat_printout(struct perfinfo * info,struct timeval * now,struct timeval * elapsed)321 stat_printout(struct perfinfo* info, struct timeval* now,
322 	struct timeval* elapsed)
323 {
324 	/* calculate qps */
325 	double dt, qps = 0;
326 #ifndef S_SPLINT_S
327 	dt = (double)(elapsed->tv_sec*1000000 + elapsed->tv_usec) / 1000000;
328 #endif
329 	if(dt > 0.001)
330 		qps = (double)(info->numrecv) / dt;
331 	if(!info->quiet)
332 		printf("qps: %g\n", qps);
333 	/* setup next slice */
334 	info->since = *now;
335 	info->total_sent += info->numsent;
336 	info->total_recv += info->numrecv;
337 	info->numrecv = 0;
338 	info->numsent = 0;
339 }
340 
341 /** wait for new events for performance test */
342 static void
perfselect(struct perfinfo * info)343 perfselect(struct perfinfo* info)
344 {
345 	fd_set rset = info->rset;
346 	struct timeval timeout, now;
347 	int num;
348 	size_t i;
349 	if(gettimeofday(&now, NULL) < 0)
350 		fatal_exit("gettimeofday: %s", strerror(errno));
351 	/* time to exit? */
352 	if(info->duration > 0) {
353 		timeout = now;
354 		perf_tv_subtract(&timeout, &info->start);
355 		if((int)timeout.tv_sec >= info->duration) {
356 			info->exit = 1;
357 			return;
358 		}
359 	}
360 	/* time for stats printout? */
361 	timeout = now;
362 	perf_tv_subtract(&timeout, &info->since);
363 	if(timeout.tv_sec > 0) {
364 		stat_printout(info, &now, &timeout);
365 	}
366 	/* see what is closest port to timeout; or if there is a timeout */
367 	timeout = info->io[0].timeout;
368 	for(i=0; i<info->io_num; i++) {
369 		if(perf_tv_smaller(&info->io[i].timeout, &now)) {
370 			perftimeout(info, i, &now);
371 			return;
372 		}
373 		if(perf_tv_smaller(&info->io[i].timeout, &timeout)) {
374 			timeout = info->io[i].timeout;
375 		}
376 	}
377 	perf_tv_subtract(&timeout, &now);
378 
379 	num = select(info->maxfd+1, &rset, NULL, NULL, &timeout);
380 	if(num == -1) {
381 		if(errno == EAGAIN || errno == EINTR)
382 			return;
383 		log_err("select: %s", strerror(errno));
384 	}
385 
386 	/* handle new events */
387 	for(i=0; num && i<info->io_num; i++) {
388 		if(FD_ISSET(info->io[i].fd, &rset)) {
389 			perfreply(info, i, &now);
390 			num--;
391 		}
392 	}
393 }
394 
395 /** show end stats */
396 static void
perfendstats(struct perfinfo * info)397 perfendstats(struct perfinfo* info)
398 {
399 	double dt, qps;
400 	struct timeval timeout, now;
401 	int i, lost;
402 	if(gettimeofday(&now, NULL) < 0)
403 		fatal_exit("gettimeofday: %s", strerror(errno));
404 	timeout = now;
405 	perf_tv_subtract(&timeout, &info->since);
406 	stat_printout(info, &now, &timeout);
407 
408 	timeout = now;
409 	perf_tv_subtract(&timeout, &info->start);
410 	dt = (double)(timeout.tv_sec*1000000 + timeout.tv_usec) / 1000000.0;
411 	qps = (double)(info->total_recv) / dt;
412 	lost = (int)(info->total_sent - info->total_recv) - (int)info->io_num;
413 	if(!info->quiet) {
414 		printf("overall time: 	%g sec\n",
415 			(double)timeout.tv_sec +
416 			(double)timeout.tv_usec/1000000.);
417 		if(lost > 0)
418 			printf("Packets lost: 	%d\n", (int)lost);
419 
420 		for(i=0; i<(int)(sizeof(info->by_rcode)/sizeof(size_t)); i++)
421 		{
422 			if(info->by_rcode[i] > 0) {
423 				char rc[16];
424 				sldns_wire2str_rcode_buf(i, rc, sizeof(rc));
425 				printf("%d(%5s): 	%u replies\n",
426 					i, rc, (unsigned)info->by_rcode[i]);
427 			}
428 		}
429 	}
430 	printf("average qps: 	%g\n", qps);
431 }
432 
433 /** perform the performance test */
434 static void
perfmain(struct perfinfo * info)435 perfmain(struct perfinfo* info)
436 {
437 	perfsetup(info);
438 	while(!info->exit) {
439 		perfselect(info);
440 	}
441 	perfendstats(info);
442 	perffree(info);
443 }
444 
445 /** parse a query line to a packet into buffer */
446 static int
qlist_parse_line(sldns_buffer * buf,char * p)447 qlist_parse_line(sldns_buffer* buf, char* p)
448 {
449 	char nm[1024], cl[1024], tp[1024], fl[1024];
450 	int r;
451 	int rec = 1, edns = 0;
452 	struct query_info qinfo;
453 	nm[0] = 0; cl[0] = 0; tp[0] = 0; fl[0] = 0;
454 	r = sscanf(p, " %1023s %1023s %1023s %1023s", nm, cl, tp, fl);
455 	if(r != 3 && r != 4)
456 		return 0;
457 	/*printf("nm='%s', cl='%s', tp='%s', fl='%s'\n", nm, cl, tp, fl);*/
458 	if(strcmp(tp, "IN") == 0 || strcmp(tp, "CH") == 0) {
459 		qinfo.qtype = sldns_get_rr_type_by_name(cl);
460 		qinfo.qclass = sldns_get_rr_class_by_name(tp);
461 	} else {
462 		qinfo.qtype = sldns_get_rr_type_by_name(tp);
463 		qinfo.qclass = sldns_get_rr_class_by_name(cl);
464 	}
465 	if(fl[0] == '+') rec = 1;
466 	else if(fl[0] == '-') rec = 0;
467 	else if(fl[0] == 'E') edns = 1;
468 	if((fl[0] == '+' || fl[0] == '-') && fl[1] == 'E')
469 		edns = 1;
470 	qinfo.qname = sldns_str2wire_dname(nm, &qinfo.qname_len);
471 	if(!qinfo.qname)
472 		return 0;
473 	qinfo.local_alias = NULL;
474 	qinfo_query_encode(buf, &qinfo);
475 	sldns_buffer_write_u16_at(buf, 0, 0); /* zero ID */
476 	if(rec) LDNS_RD_SET(sldns_buffer_begin(buf));
477 	if(edns) {
478 		struct edns_data ed;
479 		memset(&ed, 0, sizeof(ed));
480 		ed.edns_present = 1;
481 		ed.udp_size = EDNS_ADVERTISED_SIZE;
482 		/* Set DO bit in all EDNS datagrams ... */
483 		ed.bits = EDNS_DO;
484 		attach_edns_record(buf, &ed);
485 	}
486 	free(qinfo.qname);
487 	return 1;
488 }
489 
490 /** grow query list capacity */
491 static void
qlist_grow_capacity(struct perfinfo * info)492 qlist_grow_capacity(struct perfinfo* info)
493 {
494 	size_t newcap = (size_t)((info->qlist_capacity==0)?16:
495 		info->qlist_capacity*2);
496 	uint8_t** d = (uint8_t**)calloc(sizeof(uint8_t*), newcap);
497 	size_t* l = (size_t*)calloc(sizeof(size_t), newcap);
498 	if(!d || !l) fatal_exit("out of memory");
499 	if(info->qlist_data && info->qlist_capacity)
500 		memcpy(d, info->qlist_data, sizeof(uint8_t*)*
501 			info->qlist_capacity);
502 	if(info->qlist_len && info->qlist_capacity)
503 		memcpy(l, info->qlist_len, sizeof(size_t)*
504 			info->qlist_capacity);
505 	free(info->qlist_data);
506 	free(info->qlist_len);
507 	info->qlist_data = d;
508 	info->qlist_len = l;
509 	info->qlist_capacity = newcap;
510 }
511 
512 /** setup query list in info */
513 static void
qlist_add_line(struct perfinfo * info,char * line,int no)514 qlist_add_line(struct perfinfo* info, char* line, int no)
515 {
516 	if(!qlist_parse_line(info->buf, line)) {
517 		printf("error parsing query %d: %s\n", no, line);
518 		exit(1);
519 	}
520 	sldns_buffer_write_u16_at(info->buf, 0, (uint16_t)info->qlist_size);
521 	if(info->qlist_size + 1 > info->qlist_capacity) {
522 		qlist_grow_capacity(info);
523 	}
524 	info->qlist_len[info->qlist_size] = sldns_buffer_limit(info->buf);
525 	info->qlist_data[info->qlist_size] = memdup(
526 		sldns_buffer_begin(info->buf), sldns_buffer_limit(info->buf));
527 	if(!info->qlist_data[info->qlist_size])
528 		fatal_exit("out of memory");
529 	info->qlist_size ++;
530 }
531 
532 /** setup query list in info */
533 static void
qlist_read_file(struct perfinfo * info,char * fname)534 qlist_read_file(struct perfinfo* info, char* fname)
535 {
536 	char buf[1024];
537 	char *p;
538 	FILE* in = fopen(fname, "r");
539 	int lineno = 0;
540 	if(!in) {
541 		perror(fname);
542 		exit(1);
543 	}
544 	while(fgets(buf, (int)sizeof(buf), in)) {
545 		lineno++;
546 		buf[sizeof(buf)-1] = 0;
547 		p = buf;
548 		while(*p == ' ' || *p == '\t')
549 			p++;
550 		if(p[0] == 0 || p[0] == '\n' || p[0] == ';' || p[0] == '#')
551 			continue;
552 		qlist_add_line(info, p, lineno);
553 	}
554 	printf("Read %s, got %u queries\n", fname, (unsigned)info->qlist_size);
555 	fclose(in);
556 }
557 
558 /** getopt global, in case header files fail to declare it. */
559 extern int optind;
560 /** getopt global, in case header files fail to declare it. */
561 extern char* optarg;
562 
563 /** main program for perf */
main(int argc,char * argv[])564 int main(int argc, char* argv[])
565 {
566 	char* nm = argv[0];
567 	int c;
568 	struct perfinfo info;
569 #ifdef USE_WINSOCK
570 	int r;
571 	WSADATA wsa_data;
572 #endif
573 
574 	/* defaults */
575 	memset(&info, 0, sizeof(info));
576 	info.io_num = 16;
577 
578 	checklock_start();
579 	log_init(NULL, 0, NULL);
580 	log_ident_set("perf");
581 #ifdef USE_WINSOCK
582 	if((r = WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0)
583 		fatal_exit("WSAStartup failed: %s", wsa_strerror(r));
584 #endif
585 
586 	info.buf = sldns_buffer_new(65553);
587 	if(!info.buf) fatal_exit("out of memory");
588 
589 	/* parse the options */
590 	while( (c=getopt(argc, argv, "d:ha:f:q")) != -1) {
591 		switch(c) {
592 		case 'q':
593 			info.quiet = 1;
594 			break;
595 		case 'd':
596 			if(atoi(optarg)==0 && strcmp(optarg, "0")!=0) {
597 				printf("-d not a number %s", optarg);
598 				exit(1);
599 			}
600 			info.duration = atoi(optarg);
601 			break;
602 		case 'a':
603 			qlist_add_line(&info, optarg, 0);
604 			break;
605 		case 'f':
606 			qlist_read_file(&info, optarg);
607 			break;
608 		case '?':
609 		case 'h':
610 		default:
611 			usage(nm);
612 		}
613 	}
614 	argc -= optind;
615 	argv += optind;
616 
617 	if(argc != 1) {
618 		printf("error: pass server IP address on commandline.\n");
619 		usage(nm);
620 	}
621 	if(!extstrtoaddr(argv[0], &info.dest, &info.destlen)) {
622 		printf("Could not parse ip: %s\n", argv[0]);
623 		exit(1);
624 	}
625 	if(info.qlist_size == 0) {
626 		printf("No queries to make, use -f or -a.\n");
627 		exit(1);
628 	}
629 
630 	/* do the performance test */
631 	perfmain(&info);
632 
633 	sldns_buffer_free(info.buf);
634 #ifdef USE_WINSOCK
635 	WSACleanup();
636 #endif
637 	checklock_stop();
638 	return 0;
639 }
640