Mercurial > notdcc
comparison dcclib/helper.c @ 0:c7f6b056b673
First import of vendor version
author | Peter Gervai <grin@grin.hu> |
---|---|
date | Tue, 10 Mar 2009 13:49:58 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c7f6b056b673 |
---|---|
1 /* Distributed Checksum Clearinghouse | |
2 * | |
3 * helper processes for DNS blacklists | |
4 * | |
5 * Copyright (c) 2008 by Rhyolite Software, LLC | |
6 * | |
7 * This agreement is not applicable to any entity which sells anti-spam | |
8 * solutions to others or provides an anti-spam solution as part of a | |
9 * security solution sold to other entities, or to a private network | |
10 * which employs the DCC or uses data provided by operation of the DCC | |
11 * but does not provide corresponding data to other users. | |
12 * | |
13 * Permission to use, copy, modify, and distribute this software without | |
14 * changes for any purpose with or without fee is hereby granted, provided | |
15 * that the above copyright notice and this permission notice appear in all | |
16 * copies and any distributed versions or copies are either unchanged | |
17 * or not called anything similar to "DCC" or "Distributed Checksum | |
18 * Clearinghouse". | |
19 * | |
20 * Parties not eligible to receive a license under this agreement can | |
21 * obtain a commercial license to use DCC by contacting Rhyolite Software | |
22 * at sales@rhyolite.com. | |
23 * | |
24 * A commercial license would be for Distributed Checksum and Reputation | |
25 * Clearinghouse software. That software includes additional features. This | |
26 * free license for Distributed ChecksumClearinghouse Software does not in any | |
27 * way grant permision to use Distributed Checksum and Reputation Clearinghouse | |
28 * software | |
29 * | |
30 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL | |
31 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES | |
32 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC | |
33 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES | |
34 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, | |
35 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, | |
36 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS | |
37 * SOFTWARE. | |
38 * | |
39 * Rhyolite Software DCC 1.3.103-1.42 $Revision$ | |
40 */ | |
41 | |
42 #include "helper.h" | |
43 #include "dcc_heap_debug.h" | |
44 #include <signal.h> | |
45 #include <sys/wait.h> | |
46 | |
47 | |
48 #ifdef HAVE_HELPERS | |
49 | |
50 #define HELPER_MAX_FAILURES 5 /* shutdown & restart these total */ | |
51 | |
52 #define MAX_SLOW 2 /* 50% excess for slow DNS resolver */ | |
53 | |
54 | |
55 /* add to the argv list for the helper processes */ | |
56 void | |
57 helper_save_arg(const char *flag, const char *value) | |
58 { | |
59 char const **new_arg; | |
60 int i; | |
61 | |
62 if (helper.free_args <= 1) { | |
63 /* reserve space for the argv[0] and the null terminator */ | |
64 helper.free_args += 5; | |
65 i = (helper.argc + 2*helper.free_args) * sizeof(char *); | |
66 new_arg = dcc_malloc(i); | |
67 memset(new_arg, 0, i); | |
68 if (helper.argv) { | |
69 for (i = 0; i < helper.argc; ++i) | |
70 new_arg[i] = helper.argv[i]; | |
71 dcc_free(helper.argv); | |
72 } else { | |
73 ++helper.argc; | |
74 } | |
75 helper.argv = new_arg; | |
76 } | |
77 | |
78 helper.argv[helper.argc] = flag; | |
79 helper.argv[++helper.argc] = value; | |
80 helper.argv[++helper.argc] = 0; | |
81 --helper.free_args; | |
82 } | |
83 | |
84 | |
85 | |
86 /* initialize things for the parent or one of the helping children */ | |
87 void | |
88 helper_init(int max_helpers) | |
89 { | |
90 helper.sn = getpid() + time(0); | |
91 | |
92 helper.pipe_write = -1; | |
93 helper.pipe_read = -1; | |
94 helper.soc = INVALID_SOCKET; | |
95 helper.req_len = sizeof(DNSBL_REQ); | |
96 | |
97 if (max_helpers) { | |
98 /* max_helpers=0 if we are starting a child, | |
99 * but != 0 in the parent after parsing all args | |
100 * | |
101 * default to dccifd or dccm max_work */ | |
102 if (!helper.max_helpers) | |
103 helper.max_helpers = max_helpers; | |
104 | |
105 helper.pids = dcc_malloc(sizeof(pid_t) * helper.max_helpers); | |
106 memset(helper.pids, 0, sizeof(pid_t) * helper.max_helpers); | |
107 } | |
108 | |
109 have_helpers = helper_lock_init(); | |
110 } | |
111 | |
112 | |
113 | |
114 /* collect zombies of helpers that died from boredom or otherwise */ | |
115 void | |
116 reap_helpers(u_char locked) | |
117 { | |
118 int status; | |
119 pid_t pid; | |
120 int pid_inx; | |
121 | |
122 if (!locked) | |
123 helper_lock(); | |
124 | |
125 for (;;) { | |
126 wait_again:; | |
127 pid = waitpid(0, &status, WNOHANG); | |
128 if (0 >= pid) { | |
129 if (!locked) | |
130 helper_unlock(); | |
131 return; | |
132 } | |
133 | |
134 for (pid_inx = 0; ; ++pid_inx) { | |
135 if (pid_inx >= helper.max_helpers) { | |
136 /* not an acknowledged child */ | |
137 if (helper.debug >= 1) | |
138 dcc_trace_msg("not a DNSBL" | |
139 " helper process reaped"); | |
140 goto wait_again; | |
141 } | |
142 | |
143 if (helper.pids[pid_inx] == pid) | |
144 break; | |
145 } | |
146 | |
147 helper.pids[pid_inx] = 0; | |
148 | |
149 /* races with dying helpers can confuse us */ | |
150 if (--helper.total_helpers < 0) { | |
151 if (helper.debug) | |
152 dcc_trace_msg("DNSBL total helpers=%d", | |
153 helper.total_helpers); | |
154 helper.total_helpers = 0; | |
155 } | |
156 | |
157 if (helper.slow_helpers > helper.total_helpers/MAX_SLOW) | |
158 helper.slow_helpers = helper.total_helpers/MAX_SLOW; | |
159 | |
160 if (--helper.idle_helpers < 0) { | |
161 /* We cannot be certain that a helper that quit was | |
162 * idle, but it should have been. */ | |
163 if (helper.debug) | |
164 dcc_trace_msg("DNSBL idle helpers=%d", | |
165 helper.idle_helpers); | |
166 helper.idle_helpers = 0; | |
167 } else if (helper.idle_helpers > helper.total_helpers) { | |
168 /* The limit on the total_helpers can let us | |
169 * drive idle_helpers<0 | |
170 * which can make the previous fix wrong */ | |
171 if (helper.debug) | |
172 dcc_trace_msg("DNSBL idle helpers=%d" | |
173 " total helpers=%d", | |
174 helper.idle_helpers, | |
175 helper.total_helpers); | |
176 helper.idle_helpers = helper.total_helpers; | |
177 } | |
178 | |
179 /* this is called from the "totals" thread | |
180 * and so cannot use thr_error_msg() */ | |
181 | |
182 #if defined(WIFEXITED) && defined(WEXITSTATUS) | |
183 if (WIFEXITED(status)) { | |
184 if (WEXITSTATUS(status) == 0) { | |
185 if (helper.debug > 1) | |
186 dcc_trace_msg("DNSBL helper %d quit," | |
187 " leaving %d helpers," | |
188 " %d idle", | |
189 (int)pid, | |
190 helper.total_helpers, | |
191 helper.idle_helpers); | |
192 continue; | |
193 } | |
194 dcc_error_msg("DNSBL helper %d quit with exit(%d)," | |
195 " leaving %d helpers, %d idle", | |
196 (int)pid, WEXITSTATUS(status), | |
197 helper.total_helpers, | |
198 helper.idle_helpers); | |
199 continue; | |
200 } | |
201 #endif | |
202 #if defined(WTERMSIG) && defined(WIFSIGNALED) | |
203 if (WIFSIGNALED(status)) { | |
204 dcc_error_msg("DNSBL helper %d quit with signal %d," | |
205 " leaving %d helpers, %d idle", | |
206 (int)pid, WTERMSIG(status), | |
207 helper.total_helpers, | |
208 helper.idle_helpers); | |
209 continue; | |
210 } | |
211 #endif | |
212 dcc_error_msg("DNSBL helper %d quit with %d," | |
213 " leaving %d helpers, %d idle", | |
214 (int)pid, status, | |
215 helper.total_helpers, helper.idle_helpers); | |
216 } | |
217 } | |
218 | |
219 | |
220 | |
221 /* must be called with the counter mutex */ | |
222 static void | |
223 terminate_helpers(void) | |
224 { | |
225 if (helper.pipe_write != -1) { | |
226 close(helper.pipe_write); | |
227 helper.pipe_write = -1; | |
228 } | |
229 if (helper.pipe_read != -1) { | |
230 close(helper.pipe_read); | |
231 helper.pipe_read = -1; | |
232 } | |
233 if (helper.soc != INVALID_SOCKET) { | |
234 closesocket(helper.soc); | |
235 helper.soc = INVALID_SOCKET; | |
236 } | |
237 | |
238 reap_helpers(1); | |
239 ++helper.gen; | |
240 memset(helper.pids, 0, sizeof(pid_t) * helper.max_helpers); | |
241 helper.total_helpers = 0; | |
242 helper.idle_helpers = 0; | |
243 helper.slow_helpers = 0; | |
244 | |
245 helper.failures = 0; | |
246 } | |
247 | |
248 | |
249 | |
250 static void | |
251 help_finish(u_int gen, u_char ok, u_char counted, u_char locked) | |
252 { | |
253 if (!locked) | |
254 helper_lock(); | |
255 | |
256 /* forget it if the children have been restarted */ | |
257 if (gen == helper.gen) { | |
258 if (counted) | |
259 ++helper.idle_helpers; | |
260 | |
261 if (!ok) { | |
262 if (++helper.failures >= HELPER_MAX_FAILURES) { | |
263 if (helper.debug) | |
264 dcc_trace_msg("restart DNSBL helpers"); | |
265 terminate_helpers(); | |
266 } else { | |
267 reap_helpers(1); | |
268 } | |
269 } | |
270 } | |
271 | |
272 if (!locked) | |
273 helper_unlock(); | |
274 } | |
275 | |
276 | |
277 | |
278 static u_char | |
279 helper_soc_open(DCC_EMSG emsg, DCC_CLNT_CTXT *ctxt) | |
280 { | |
281 u_char result; | |
282 | |
283 if (ctxt->soc != INVALID_SOCKET) | |
284 return 1; | |
285 | |
286 dcc_ctxts_lock(); | |
287 if (!dcc_info_lock(emsg)) { | |
288 dcc_ctxts_unlock(); | |
289 return 0; | |
290 } | |
291 result = dcc_clnt_soc_reopen(emsg, ctxt); | |
292 if (!dcc_info_unlock(emsg)) | |
293 result = 0; | |
294 dcc_ctxts_unlock(); | |
295 return result; | |
296 } | |
297 | |
298 | |
299 | |
300 static u_char | |
301 helper_soc_connect(DCC_EMSG emsg, DCC_CLNT_CTXT *ctxt, const DCC_SOCKU *su) | |
302 { | |
303 u_char result; | |
304 | |
305 #ifdef linux | |
306 /* since at least some versions of Linux refuse to reconnect, | |
307 * just disconnect */ | |
308 su = 0; | |
309 #endif | |
310 | |
311 dcc_ctxts_lock(); | |
312 if (!dcc_info_lock(emsg)) { | |
313 dcc_ctxts_unlock(); | |
314 return 0; | |
315 } | |
316 result = dcc_clnt_connect(emsg, ctxt, su); | |
317 if (!dcc_info_unlock(emsg)) | |
318 result = 0; | |
319 dcc_ctxts_unlock(); | |
320 | |
321 return result; | |
322 } | |
323 | |
324 | |
325 | |
326 /* open the helper socket used to send requests to helper processes | |
327 * must be called with the counter mutex */ | |
328 static u_char | |
329 open_helper(DCC_EMSG emsg, DCC_CLNT_CTXT *ctxt, void *lp) | |
330 { | |
331 struct in6_addr ipv6_loopback; | |
332 struct in_addr ipv4_loopback; | |
333 DCC_SOCKLEN_T soc_len; | |
334 static int rcvbuf = 32*1024; | |
335 static u_char rcvbuf_set = 0; | |
336 | |
337 /* We want to create a new socket with the same choice of | |
338 * IPv4 or IPv6 as the DCC client context's socket. To do that, | |
339 * we must ensure that the context's socket is healthy. */ | |
340 if (!helper_soc_open(emsg, ctxt)) { | |
341 thr_error_msg(lp, "DNSBL helper soc_open(): %s", emsg); | |
342 return 0; | |
343 } | |
344 | |
345 if (dcc_clnt_info->src.family != AF_UNSPEC) { | |
346 /* bind to the send-from address if available */ | |
347 dcc_mk_su(&helper.su, dcc_clnt_info->src.family, | |
348 &dcc_clnt_info->src.u, 0); | |
349 } else if (ctxt->flags & DCC_CTXT_USING_IPV4) { | |
350 ipv4_loopback.s_addr = ntohl(0x7f000001); | |
351 dcc_mk_su(&helper.su, AF_INET, &ipv4_loopback, 0); | |
352 } else { | |
353 memset(&ipv6_loopback, 0, sizeof(ipv6_loopback)); | |
354 ipv6_loopback.s6_addr32[3] = ntohl(1); | |
355 dcc_mk_su(&helper.su, AF_INET6, &ipv6_loopback, 0); | |
356 } | |
357 clean_stdio(); | |
358 if (0 >= dcc_udp_bind(emsg, &helper.soc, &helper.su, 0)) { | |
359 thr_error_msg(lp, "DNSBL helper bind(%s): %s", | |
360 dcc_su2str_err(&helper.su), emsg); | |
361 terminate_helpers(); | |
362 return 0; | |
363 } | |
364 soc_len = sizeof(helper.su); | |
365 if (0 > getsockname(helper.soc, &helper.su.sa, &soc_len)) { | |
366 thr_error_msg(lp, "DNSBL helper getsockname(%d, %s): %s", | |
367 helper.soc, dcc_su2str_err(&helper.su), | |
368 ERROR_STR()); | |
369 terminate_helpers(); | |
370 return 0; | |
371 } | |
372 for (;;) { | |
373 if (!setsockopt(helper.soc, SOL_SOCKET, SO_RCVBUF, | |
374 &rcvbuf, sizeof(rcvbuf))) | |
375 break; | |
376 if (rcvbuf_set || rcvbuf <= 4096) { | |
377 thr_error_msg(lp, | |
378 "DNSBL setsockopt(%s,SO_RCVBUF=%d): %s", | |
379 dcc_su2str_err(&helper.su), | |
380 rcvbuf, ERROR_STR()); | |
381 break; | |
382 } | |
383 rcvbuf -= 4096; | |
384 } | |
385 rcvbuf_set = 1; | |
386 return 1; | |
387 } | |
388 | |
389 | |
390 | |
391 /* Create the pipe used to awaken and terminate the helper processes | |
392 * must be called with the counter mutex */ | |
393 static u_char | |
394 ready_helpers(void *lp) | |
395 { | |
396 int fds[2]; | |
397 | |
398 if (helper.pipe_write >= 0 | |
399 && helper.pipe_read >= 0) | |
400 return 1; | |
401 | |
402 terminate_helpers(); | |
403 | |
404 /* give the helper child processes an FD that will go dead | |
405 * if the parent dies or otherwise closes the other end */ | |
406 clean_stdio(); | |
407 if (0 > pipe(fds)) { | |
408 thr_error_msg(lp, "DNSBL parent helper pipe(): %s", | |
409 ERROR_STR()); | |
410 terminate_helpers(); | |
411 return 0; | |
412 } | |
413 helper.pipe_read = fds[0]; | |
414 helper.pipe_write = fds[1]; | |
415 if (0 > fcntl(helper.pipe_write, F_SETFD, FD_CLOEXEC)) { | |
416 thr_error_msg(lp, "DNSBL helper fcntl(FD_CLOEXEC): %s", | |
417 ERROR_STR()); | |
418 terminate_helpers(); | |
419 return 0; | |
420 } | |
421 | |
422 return 1; | |
423 } | |
424 | |
425 | |
426 | |
427 /* Start a new helper process. | |
428 * The counter mutex must be locked */ | |
429 static u_char | |
430 new_helper(DCC_EMSG emsg, DCC_CLNT_CTXT *ctxt, void *lp) | |
431 { | |
432 pid_t pid; | |
433 char arg_buf[sizeof("set:")+sizeof(HELPER_PAT)+8+8+8]; | |
434 char trace_buf[200]; | |
435 char *bufp; | |
436 SOCKET soc; | |
437 int pid_inx, buf_len, i, j; | |
438 | |
439 /* open the socket if necessary */ | |
440 if (helper.soc == INVALID_SOCKET) { | |
441 if (!ready_helpers(lp)) | |
442 return 0; | |
443 if (!open_helper(emsg, ctxt, lp)) | |
444 return 0; | |
445 } | |
446 | |
447 reap_helpers(1); | |
448 for (pid_inx = 0; ; ++pid_inx) { | |
449 if (pid_inx >= helper.max_helpers) | |
450 dcc_logbad(EX_SOFTWARE, "no free DNSBL pids[] entry"); | |
451 if (helper.pids[pid_inx] == 0) | |
452 break; | |
453 } | |
454 | |
455 fflush(stdout); | |
456 fflush(stderr); | |
457 pid = fork(); | |
458 if (pid < 0) { | |
459 thr_error_msg(lp, "DNSBL helper fork(): %s", ERROR_STR()); | |
460 return 0; | |
461 } | |
462 | |
463 if (pid != 0) { | |
464 /* this is the parent */ | |
465 helper.pids[pid_inx] = pid; | |
466 ++helper.total_helpers; | |
467 return 1; | |
468 } | |
469 | |
470 dcc_rel_priv(); /* no fun or games */ | |
471 clean_stdio(); | |
472 | |
473 /* reset FD_CLOEXEC without affecting parent */ | |
474 soc = dup(helper.soc); | |
475 if (soc == INVALID_SOCKET) | |
476 dcc_logbad(EX_UNAVAILABLE, "DNSBL helper soc dup(%d): %s", | |
477 helper.soc, ERROR_STR()); | |
478 | |
479 snprintf(arg_buf, sizeof(arg_buf), "set:"HELPER_PAT, | |
480 soc, helper.pipe_read, helper.total_helpers); | |
481 helper_save_arg("-B", arg_buf); | |
482 helper.argv[0] = dnsbl_progpath; | |
483 buf_len = sizeof(trace_buf); | |
484 bufp = trace_buf; | |
485 for (i = 0; i < helper.argc && buf_len > 2; ++i) { | |
486 j = snprintf(bufp, buf_len, "%s ", helper.argv[i]); | |
487 buf_len -= j; | |
488 bufp += j; | |
489 } | |
490 if (helper.debug >= 4) | |
491 dcc_trace_msg("DNSBL helper exec %s", trace_buf); | |
492 | |
493 execv(helper.argv[0], (char * const *)helper.argv); | |
494 /* This process should continue at helper_child() */ | |
495 | |
496 dcc_logbad(EX_UNAVAILABLE, "exec(%s): %s", | |
497 trace_buf, ERROR_STR()); | |
498 } | |
499 | |
500 | |
501 | |
502 static void NRATTRIB | |
503 helper_exit(const char *reason) | |
504 { | |
505 if (helper.debug > 1) | |
506 dcc_trace_msg("helper process on %s %s", | |
507 dcc_su2str_err(&helper.su), reason); | |
508 | |
509 exit(0); | |
510 } | |
511 | |
512 | |
513 | |
514 static u_char helper_alarm_hit; | |
515 static void | |
516 helper_alarm(int s UATTRIB) | |
517 { | |
518 helper_alarm_hit = 1; | |
519 } | |
520 | |
521 | |
522 | |
523 /* helper processes start here via fork()/exec() in the parent */ | |
524 void NRATTRIB | |
525 helper_child(SOCKET soc, int fd, int total_helpers) | |
526 { | |
527 sigset_t sigs; | |
528 DCC_SOCKLEN_T soc_len; | |
529 DNSBL_REQ req; | |
530 int req_len; | |
531 DNSBL_RESP resp; | |
532 DCC_SOCKU req_su; | |
533 DCC_SOCKLEN_T su_len; | |
534 struct timeval now; | |
535 u_char wake_buf; | |
536 int secs, i; | |
537 | |
538 /* this process inherits via exec() by dccm or dccifd odd signal | |
539 * blocking from some pthreads implementations including FreeBSD 5.* */ | |
540 signal(SIGHUP, SIG_IGN); | |
541 signal(SIGINT, SIG_IGN); | |
542 signal(SIGTERM, SIG_IGN); | |
543 sigemptyset(&sigs); | |
544 sigaddset(&sigs, SIGALRM); | |
545 sigprocmask(SIG_UNBLOCK, &sigs, 0); | |
546 | |
547 helper_init(0); | |
548 if (have_helpers) | |
549 dcc_logbad(EX_SOFTWARE, "no threads for DNSBL helpers"); | |
550 | |
551 helper.total_helpers = total_helpers; | |
552 | |
553 helper.pipe_read = fd; | |
554 helper.soc = soc; | |
555 soc_len = sizeof(helper.su); | |
556 if (0 > getsockname(helper.soc, &helper.su.sa, &soc_len)) | |
557 dcc_logbad(EX_IOERR, "DNSBL helper getsockname(%d): %s", | |
558 helper.soc, ERROR_STR()); | |
559 | |
560 if (helper.debug > 1) | |
561 dcc_trace_msg("DNSBL helper process starting on %s", | |
562 dcc_su2str_err(&helper.su)); | |
563 | |
564 for (;;) { | |
565 /* Use read() and SIGALRM to watch for a wake-up byte | |
566 * from the parent, the parent ending and closing the pipe, | |
567 * or enough idle time to require our retirement. This | |
568 * tactic awakens a single child for each wake-up call | |
569 * from the parent. Using select() or poll() on the main | |
570 * socket awakens a thundering herd of children */ | |
571 secs = HELPER_IDLE_STOP_SECS+1; | |
572 if (helper.total_helpers > 0) | |
573 secs /= helper.total_helpers+1; | |
574 if (secs < 5) | |
575 secs = 5; | |
576 signal(SIGALRM, helper_alarm); | |
577 #ifdef HAVE_SIGINTERRUPT | |
578 siginterrupt(SIGALRM, 1); | |
579 #endif | |
580 helper_alarm_hit = 0; | |
581 alarm(secs); | |
582 for (;;) { | |
583 su_len = sizeof(req_su); | |
584 req_len = recvfrom(helper.soc, &req, ISZ(req), 0, | |
585 &req_su.sa, &su_len); | |
586 | |
587 /* sleep until awakened if no work is ready */ | |
588 if (req_len <= 0) { | |
589 if (req_len == 0) | |
590 dcc_logbad(EX_IOERR, | |
591 "DNSBL helper recvfrom()=0"); | |
592 if (!DCC_BLOCK_ERROR()) | |
593 dcc_logbad(EX_IOERR, | |
594 "DNSBL helper recvfrom():" | |
595 " %s", | |
596 ERROR_STR()); | |
597 if (helper_alarm_hit) | |
598 helper_exit("idle helper exit"); | |
599 | |
600 i = read(helper.pipe_read, &wake_buf, 1); | |
601 | |
602 /* The other end of the pipe can be marked | |
603 * non-blocking by some pthreads | |
604 * implementations. That makes read() on this | |
605 * end fail with EAGAIN. When that happens, | |
606 * fall back on select() or poll(). | |
607 * Even on such pthread implementations, | |
608 * it rarely happens. */ | |
609 if (i < 0 && DCC_BLOCK_ERROR()) { | |
610 DCC_EMSG emsg; | |
611 i = dcc_select_poll(emsg, | |
612 helper.pipe_read, 0, | |
613 -1); | |
614 if (i < 0) | |
615 dcc_logbad(EX_IOERR, | |
616 "dnsbl HELPER select():" | |
617 " %s", emsg); | |
618 } | |
619 | |
620 /* loof for work after a wake-up call */ | |
621 if (i > 0) | |
622 continue; | |
623 | |
624 if (helper_alarm_hit) | |
625 continue; | |
626 if (i == 0) | |
627 helper_exit("shutdown"); | |
628 if (i < 0) { | |
629 dcc_logbad(EX_OSERR, | |
630 "DNSBL read(terminate): %s", | |
631 ERROR_STR()); | |
632 } | |
633 } | |
634 if (req_len != helper.req_len) { | |
635 if (helper.debug) | |
636 dcc_trace_msg("DNSBL helper" | |
637 " recvfrom(parent %s)=%d" | |
638 " instead of %d", | |
639 dcc_su2str_err(&req_su), | |
640 req_len, | |
641 helper.req_len); | |
642 continue; | |
643 } | |
644 | |
645 /* we might get stray packets because we cannot | |
646 * connect to a single port */ | |
647 if (!DCC_SU_EQ(&helper.su, &helper.su)) { | |
648 if (helper.debug) | |
649 dcc_trace_msg("DNSBL helper" | |
650 " request from" | |
651 " %s instead of %s", | |
652 dcc_su2str_err(&req_su), | |
653 dcc_su2str_err(&helper.su)); | |
654 continue; | |
655 } | |
656 | |
657 if (req.hdr.magic != HELPER_MAGIC_REQ | |
658 || req.hdr.version != HELPER_VERSION) { | |
659 if (helper.debug) | |
660 dcc_trace_msg("DNSBL helper" | |
661 " recvfrom(parent %s)" | |
662 " magic=%#08x", | |
663 dcc_su2str_err(&req_su), | |
664 req.hdr.magic); | |
665 continue; | |
666 } | |
667 break; | |
668 } | |
669 gettimeofday(&now, 0); | |
670 alarm(0); | |
671 | |
672 /* do not bother working if it is already too late to answer, | |
673 * perhaps because a previous helper died */ | |
674 if (tv_diff2us(&now, &req.hdr.start) >= req.hdr.avail_us) { | |
675 if (helper.debug > 1) | |
676 dcc_trace_msg("%s DNSBL helper" | |
677 " already too late to answer", | |
678 req.hdr.id); | |
679 continue; | |
680 } | |
681 | |
682 memset(&resp, 0, sizeof(resp)); | |
683 resp.hdr.magic = HELPER_MAGIC_RESP; | |
684 resp.hdr.version = HELPER_VERSION; | |
685 resp.hdr.sn = req.hdr.sn; | |
686 | |
687 /* do the work and send an answer if we have one */ | |
688 if (!dnsbl_work(&req, &resp)) | |
689 continue; | |
690 | |
691 /* do not answer if it is too late */ | |
692 gettimeofday(&now, 0); | |
693 if (tv_diff2us(&now, &req.hdr.start) > (req.hdr.avail_us | |
694 + DCC_US/2)) { | |
695 if (helper.debug > 1) | |
696 dcc_trace_msg("%s DNSBL helper" | |
697 " too late to answer", | |
698 req.hdr.id); | |
699 continue; | |
700 } | |
701 | |
702 i = sendto(helper.soc, &resp, sizeof(resp), 0, | |
703 &req_su.sa, DCC_SU_LEN(&req_su)); | |
704 if (i != sizeof(resp)) { | |
705 if (i < 0) | |
706 dcc_error_msg("%s helper sendto(%s): %s", | |
707 req.hdr.id, | |
708 dcc_su2str_err(&req_su), | |
709 ERROR_STR()); | |
710 else | |
711 dcc_error_msg("%s helper sendto(%s)=%d", | |
712 req.hdr.id, | |
713 dcc_su2str_err(&req_su), i); | |
714 } | |
715 } | |
716 } | |
717 | |
718 | |
719 | |
720 /* ask a helper process to do some filtering */ | |
721 u_char /* 1=got an answer */ | |
722 ask_helper(DCC_CLNT_CTXT *ctxt, void *log_ctxt, | |
723 time_t avail_us, /* spend at most this much time */ | |
724 HELPER_REQ_HDR *req, /* request sent to helper */ | |
725 int req_len, | |
726 HELPER_RESP_HDR *resp, /* put answer here */ | |
727 int resp_len) | |
728 { | |
729 DCC_EMSG emsg; | |
730 DCC_SOCKU send_su; | |
731 DCC_SOCKLEN_T su_len; | |
732 DCC_SOCKU recv_su; | |
733 char sustr[DCC_SU2STR_SIZE]; | |
734 u_char counted; | |
735 u_int gen; | |
736 struct timeval now; | |
737 time_t us; | |
738 int i; | |
739 | |
740 emsg[0] = '\0'; | |
741 | |
742 /* keep the lock until we have sent our request and wake-up call | |
743 * to ensure that some other thread does not shut down all of | |
744 * the helpers. */ | |
745 helper_lock(); | |
746 gettimeofday(&now, 0); | |
747 | |
748 /* If it has been a long time since we used a helper, then the last | |
749 * of them might be about to die of boredom. Fix that race by | |
750 * restarting all of them. | |
751 * Most dying helpers should be reaped by the totals timer thread. */ | |
752 if (helper.idle_helpers > 0 | |
753 && DCC_IS_TIME(now.tv_sec, helper.idle_restart, | |
754 HELPER_IDLE_RESTART)) { | |
755 reap_helpers(1); | |
756 if (helper.idle_helpers > 0) | |
757 terminate_helpers(); | |
758 gettimeofday(&now, 0); | |
759 } | |
760 helper.idle_restart = now.tv_sec + HELPER_IDLE_RESTART; | |
761 | |
762 if (helper.idle_helpers - helper.slow_helpers > 0) { | |
763 /* avoid taking the last idle helper because there are | |
764 * usually fewer truly idle helpers than we think because | |
765 * we don't always wait for them to finish */ | |
766 if (helper.idle_helpers > 2 | |
767 || helper.total_helpers >= helper.max_helpers | |
768 || !new_helper(emsg, ctxt, log_ctxt)) | |
769 --helper.idle_helpers; | |
770 counted = 1; | |
771 } else if (helper.total_helpers >= helper.max_helpers) { | |
772 if (helper.debug > 0) | |
773 thr_trace_msg(log_ctxt, "%s DNSBL %d idle, %d slow, and" | |
774 " %d total DNSBL helpers", req->id, | |
775 helper.idle_helpers, helper.slow_helpers, | |
776 helper.total_helpers); | |
777 counted = 0; | |
778 } else { | |
779 if (!new_helper(emsg, ctxt, log_ctxt)) { | |
780 helper_unlock(); | |
781 return 0; | |
782 } | |
783 counted = 1; | |
784 } | |
785 | |
786 /* The resolution of the BIND timeout limits is seconds, so even on | |
787 * systems where the timeout limits work, the helper might delay | |
788 * a second or two. To keep the count of idle helpers as accurate | |
789 * as possible, always wait at least 1 second for an answer | |
790 * and 2 seconds for an answer to reach the parent. */ | |
791 req->avail_us = avail_us; | |
792 avail_us += DCC_US; | |
793 req->start = now; | |
794 req->magic = HELPER_MAGIC_REQ; | |
795 req->version = HELPER_VERSION; | |
796 | |
797 req->sn = ++helper.sn; | |
798 gen = helper.gen; | |
799 | |
800 /* snapshot the address in case another thread restarts the helpers */ | |
801 send_su = helper.su; | |
802 | |
803 /* Use sendto() if the socket is not already conencted. | |
804 * If it is already connected, then on many systems other than Linux, | |
805 * it is possible and presumably cheap to reconnected it, so do so. */ | |
806 if (!helper_soc_open(emsg, ctxt)) { | |
807 thr_trace_msg(log_ctxt, "DNSBL reopen(): %s", emsg); | |
808 help_finish(gen, 0, counted, 1); | |
809 helper_unlock(); | |
810 return 0; | |
811 } | |
812 if (ctxt->conn_su.sa.sa_family != AF_UNSPEC | |
813 && memcmp(&ctxt->conn_su, &send_su, sizeof(ctxt->conn_su)) | |
814 && !helper_soc_connect(emsg, ctxt, &send_su)) { | |
815 thr_trace_msg(log_ctxt, "DNSBL soc_connect(): %s", emsg); | |
816 help_finish(gen, 0, counted, 1); | |
817 helper_unlock(); | |
818 return 0; | |
819 } | |
820 if (ctxt->conn_su.sa.sa_family == AF_UNSPEC) { | |
821 i = sendto(ctxt->soc, req, req_len, 0, | |
822 &send_su.sa, DCC_SU_LEN(&send_su)); | |
823 } else { | |
824 i = send(ctxt->soc, req, req_len, 0); | |
825 } | |
826 if (i != req_len) { | |
827 if (i < 0) | |
828 thr_trace_msg(log_ctxt, "%s DNSBL sendto(%s): %s", | |
829 req->id, dcc_su2str(sustr, sizeof(sustr), | |
830 &send_su), | |
831 ERROR_STR()); | |
832 else | |
833 thr_trace_msg(log_ctxt, "%s DNSBL sendto(%s)=%d", | |
834 req->id, dcc_su2str(sustr, sizeof(sustr), | |
835 &send_su), | |
836 i); | |
837 help_finish(gen, 0, counted, 1); | |
838 helper_unlock(); | |
839 return 0; | |
840 } | |
841 | |
842 /* awaken a helper */ | |
843 i = write(helper.pipe_write, "x", 1); | |
844 if (i != 1) { | |
845 if (i < 0) | |
846 thr_trace_msg(log_ctxt, | |
847 "%s DNSBL write(pipe_write=%d): %s", | |
848 req->id, helper.pipe_write, ERROR_STR()); | |
849 else | |
850 thr_trace_msg(log_ctxt, | |
851 "%s DNSBL write(pipe_write)=%d", | |
852 req->id, i); | |
853 help_finish(gen, 0, counted, 1); | |
854 helper_unlock(); | |
855 return 0; | |
856 } | |
857 helper_unlock(); | |
858 | |
859 for (;;) { | |
860 us = avail_us - tv_diff2us(&now, &req->start); | |
861 if (us < 0) | |
862 us = 0; | |
863 i = dcc_select_poll(0, ctxt->soc, 1, us); | |
864 if (i < 0) { | |
865 thr_error_msg(log_ctxt, "%s DNSBL select_poll: %s", | |
866 req->id, ERROR_STR()); | |
867 help_finish(gen, 0, counted, 0); | |
868 return 0; | |
869 } | |
870 gettimeofday(&now, 0); | |
871 | |
872 if (i == 0) { | |
873 if (helper.debug) | |
874 thr_trace_msg(log_ctxt, | |
875 "%s DNSBL no helper answer after" | |
876 " %1.f sec", req->id, | |
877 tv_diff2us(&now, &req->start) | |
878 / (DCC_US*1.0)); | |
879 helper_lock(); | |
880 if (helper.slow_helpers<=helper.total_helpers/MAX_SLOW) | |
881 ++helper.slow_helpers; | |
882 help_finish(gen, 0, counted, 1); | |
883 helper_unlock(); | |
884 return 0; | |
885 } | |
886 | |
887 | |
888 su_len = sizeof(recv_su); | |
889 i = recvfrom(ctxt->soc, resp, resp_len, | |
890 0, &recv_su.sa, &su_len); | |
891 /* because we are using UDP, we might get stray packets */ | |
892 if (i != resp_len) { | |
893 if (i < 0) { | |
894 thr_trace_msg(log_ctxt, | |
895 "%s DNSBL recvfrom(): %s", | |
896 req->id, ERROR_STR()); | |
897 if (DCC_BLOCK_ERROR()) | |
898 continue; | |
899 help_finish(gen, 0, counted, 0); | |
900 return 0; | |
901 } | |
902 if (helper.debug > 1) | |
903 thr_trace_msg(log_ctxt, | |
904 "%s DNSBL recvfrom(%s)=%d", | |
905 req->id, | |
906 dcc_su2str_err(&recv_su), i); | |
907 continue; | |
908 } | |
909 if (!DCC_SU_EQ(&send_su, &recv_su)) { | |
910 if (helper.debug != 0) | |
911 thr_trace_msg(log_ctxt, | |
912 "%s DNSBL recvfrom(%s)" | |
913 " instead of %s", | |
914 req->id, | |
915 dcc_su2str_err(&recv_su), | |
916 dcc_su2str_err(&send_su)); | |
917 continue; | |
918 } | |
919 if (resp->magic != HELPER_MAGIC_RESP | |
920 || resp->version != HELPER_VERSION | |
921 || resp->sn != req->sn) { | |
922 if (helper.debug >1 ) | |
923 thr_trace_msg(log_ctxt, | |
924 "%s DNSBL recvfrom(%s)" | |
925 " magic=%#08x sn=%d", | |
926 req->id, dcc_su2str_err(&recv_su), | |
927 resp->magic, resp->sn); | |
928 continue; | |
929 } | |
930 | |
931 help_finish(gen, 1, counted, 0); | |
932 return 1; | |
933 } | |
934 } | |
935 #endif /* HAVE_HELPERS */ |