]>
Commit | Line | Data |
---|---|---|
89c4ed63 A |
1 | /* |
2 | * daemon/worker.c - worker that handles a pending list of requests. | |
3 | * | |
4 | * Copyright (c) 2007, 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 file implements the worker that handles callbacks on events, for | |
40 | * pending requests. | |
41 | */ | |
42 | #include "config.h" | |
43 | #include "util/log.h" | |
44 | #include "util/net_help.h" | |
45 | #include "util/random.h" | |
46 | #include "daemon/worker.h" | |
47 | #include "daemon/daemon.h" | |
48 | #include "daemon/remote.h" | |
49 | #include "daemon/acl_list.h" | |
50 | #include "util/netevent.h" | |
51 | #include "util/config_file.h" | |
52 | #include "util/module.h" | |
53 | #include "util/regional.h" | |
54 | #include "util/storage/slabhash.h" | |
55 | #include "services/listen_dnsport.h" | |
56 | #include "services/outside_network.h" | |
57 | #include "services/outbound_list.h" | |
58 | #include "services/cache/rrset.h" | |
59 | #include "services/cache/infra.h" | |
60 | #include "services/cache/dns.h" | |
61 | #include "services/mesh.h" | |
62 | #include "services/localzone.h" | |
63 | #include "util/data/msgparse.h" | |
64 | #include "util/data/msgencode.h" | |
65 | #include "util/data/dname.h" | |
66 | #include "util/fptr_wlist.h" | |
67 | #include "util/tube.h" | |
68 | #include "iterator/iter_fwd.h" | |
69 | #include "iterator/iter_hints.h" | |
70 | #include "validator/autotrust.h" | |
71 | #include "validator/val_anchor.h" | |
72 | #include "libunbound/context.h" | |
73 | #include "libunbound/libworker.h" | |
74 | #include "ldns/sbuffer.h" | |
75 | ||
76 | #ifdef HAVE_SYS_TYPES_H | |
77 | # include <sys/types.h> | |
78 | #endif | |
79 | #ifdef HAVE_NETDB_H | |
80 | #include <netdb.h> | |
81 | #endif | |
82 | #include <signal.h> | |
83 | #ifdef UB_ON_WINDOWS | |
84 | #include "winrc/win_svc.h" | |
85 | #endif | |
86 | ||
87 | /** Size of an UDP datagram */ | |
88 | #define NORMAL_UDP_SIZE 512 /* bytes */ | |
89 | ||
90 | /** | |
91 | * seconds to add to prefetch leeway. This is a TTL that expires old rrsets | |
92 | * earlier than they should in order to put the new update into the cache. | |
93 | * This additional value is to make sure that if not all TTLs are equal in | |
94 | * the message to be updated(and replaced), that rrsets with up to this much | |
95 | * extra TTL are also replaced. This means that the resulting new message | |
96 | * will have (most likely) this TTL at least, avoiding very small 'split | |
97 | * second' TTLs due to operators choosing relative primes for TTLs (or so). | |
98 | * Also has to be at least one to break ties (and overwrite cached entry). | |
99 | */ | |
100 | #define PREFETCH_EXPIRY_ADD 60 | |
101 | ||
102 | #ifdef UNBOUND_ALLOC_STATS | |
103 | /** measure memory leakage */ | |
104 | static void | |
105 | debug_memleak(size_t accounted, size_t heap, | |
106 | size_t total_alloc, size_t total_free) | |
107 | { | |
108 | static int init = 0; | |
109 | static size_t base_heap, base_accounted, base_alloc, base_free; | |
110 | size_t base_af, cur_af, grow_af, grow_acc; | |
111 | if(!init) { | |
112 | init = 1; | |
113 | base_heap = heap; | |
114 | base_accounted = accounted; | |
115 | base_alloc = total_alloc; | |
116 | base_free = total_free; | |
117 | } | |
118 | base_af = base_alloc - base_free; | |
119 | cur_af = total_alloc - total_free; | |
120 | grow_af = cur_af - base_af; | |
121 | grow_acc = accounted - base_accounted; | |
122 | log_info("Leakage: %d leaked. growth: %u use, %u acc, %u heap", | |
123 | (int)(grow_af - grow_acc), (unsigned)grow_af, | |
124 | (unsigned)grow_acc, (unsigned)(heap - base_heap)); | |
125 | } | |
126 | ||
127 | /** give debug heap size indication */ | |
128 | static void | |
129 | debug_total_mem(size_t calctotal) | |
130 | { | |
131 | #ifdef HAVE_SBRK | |
132 | extern void* unbound_start_brk; | |
133 | extern size_t unbound_mem_alloc, unbound_mem_freed; | |
134 | void* cur = sbrk(0); | |
135 | int total = cur-unbound_start_brk; | |
136 | log_info("Total heap memory estimate: %u total-alloc: %u " | |
137 | "total-free: %u", (unsigned)total, | |
138 | (unsigned)unbound_mem_alloc, (unsigned)unbound_mem_freed); | |
139 | debug_memleak(calctotal, (size_t)total, | |
140 | unbound_mem_alloc, unbound_mem_freed); | |
141 | #else | |
142 | (void)calctotal; | |
143 | #endif /* HAVE_SBRK */ | |
144 | } | |
145 | #endif /* UNBOUND_ALLOC_STATS */ | |
146 | ||
147 | /** Report on memory usage by this thread and global */ | |
148 | static void | |
149 | worker_mem_report(struct worker* ATTR_UNUSED(worker), | |
150 | struct serviced_query* ATTR_UNUSED(cur_serv)) | |
151 | { | |
152 | #ifdef UNBOUND_ALLOC_STATS | |
153 | /* debug func in validator module */ | |
154 | size_t total, front, back, mesh, msg, rrset, infra, ac, superac; | |
155 | size_t me, iter, val, anch; | |
156 | int i; | |
157 | if(verbosity < VERB_ALGO) | |
158 | return; | |
159 | front = listen_get_mem(worker->front); | |
160 | back = outnet_get_mem(worker->back); | |
161 | msg = slabhash_get_mem(worker->env.msg_cache); | |
162 | rrset = slabhash_get_mem(&worker->env.rrset_cache->table); | |
163 | infra = infra_get_mem(worker->env.infra_cache); | |
164 | mesh = mesh_get_mem(worker->env.mesh); | |
165 | ac = alloc_get_mem(&worker->alloc); | |
166 | superac = alloc_get_mem(&worker->daemon->superalloc); | |
167 | anch = anchors_get_mem(worker->env.anchors); | |
168 | iter = 0; | |
169 | val = 0; | |
170 | for(i=0; i<worker->env.mesh->mods.num; i++) { | |
171 | fptr_ok(fptr_whitelist_mod_get_mem(worker->env.mesh-> | |
172 | mods.mod[i]->get_mem)); | |
173 | if(strcmp(worker->env.mesh->mods.mod[i]->name, "validator")==0) | |
174 | val += (*worker->env.mesh->mods.mod[i]->get_mem) | |
175 | (&worker->env, i); | |
176 | else iter += (*worker->env.mesh->mods.mod[i]->get_mem) | |
177 | (&worker->env, i); | |
178 | } | |
179 | me = sizeof(*worker) + sizeof(*worker->base) + sizeof(*worker->comsig) | |
180 | + comm_point_get_mem(worker->cmd_com) | |
181 | + sizeof(worker->rndstate) | |
182 | + regional_get_mem(worker->scratchpad) | |
183 | + sizeof(*worker->env.scratch_buffer) | |
184 | + sldns_buffer_capacity(worker->env.scratch_buffer) | |
185 | + forwards_get_mem(worker->env.fwds) | |
186 | + hints_get_mem(worker->env.hints); | |
187 | if(worker->thread_num == 0) | |
188 | me += acl_list_get_mem(worker->daemon->acl); | |
189 | if(cur_serv) { | |
190 | me += serviced_get_mem(cur_serv); | |
191 | } | |
192 | total = front+back+mesh+msg+rrset+infra+iter+val+ac+superac+me; | |
193 | log_info("Memory conditions: %u front=%u back=%u mesh=%u msg=%u " | |
194 | "rrset=%u infra=%u iter=%u val=%u anchors=%u " | |
195 | "alloccache=%u globalalloccache=%u me=%u", | |
196 | (unsigned)total, (unsigned)front, (unsigned)back, | |
197 | (unsigned)mesh, (unsigned)msg, (unsigned)rrset, | |
198 | (unsigned)infra, (unsigned)iter, (unsigned)val, (unsigned)anch, | |
199 | (unsigned)ac, (unsigned)superac, (unsigned)me); | |
200 | debug_total_mem(total); | |
201 | #else /* no UNBOUND_ALLOC_STATS */ | |
202 | size_t val = 0; | |
203 | int i; | |
204 | if(verbosity < VERB_QUERY) | |
205 | return; | |
206 | for(i=0; i<worker->env.mesh->mods.num; i++) { | |
207 | fptr_ok(fptr_whitelist_mod_get_mem(worker->env.mesh-> | |
208 | mods.mod[i]->get_mem)); | |
209 | if(strcmp(worker->env.mesh->mods.mod[i]->name, "validator")==0) | |
210 | val += (*worker->env.mesh->mods.mod[i]->get_mem) | |
211 | (&worker->env, i); | |
212 | } | |
213 | verbose(VERB_QUERY, "cache memory msg=%u rrset=%u infra=%u val=%u", | |
214 | (unsigned)slabhash_get_mem(worker->env.msg_cache), | |
215 | (unsigned)slabhash_get_mem(&worker->env.rrset_cache->table), | |
216 | (unsigned)infra_get_mem(worker->env.infra_cache), | |
217 | (unsigned)val); | |
218 | #endif /* UNBOUND_ALLOC_STATS */ | |
219 | } | |
220 | ||
221 | void | |
222 | worker_send_cmd(struct worker* worker, enum worker_commands cmd) | |
223 | { | |
224 | uint32_t c = (uint32_t)htonl(cmd); | |
225 | if(!tube_write_msg(worker->cmd, (uint8_t*)&c, sizeof(c), 0)) { | |
226 | log_err("worker send cmd %d failed", (int)cmd); | |
227 | } | |
228 | } | |
229 | ||
230 | int | |
231 | worker_handle_reply(struct comm_point* c, void* arg, int error, | |
232 | struct comm_reply* reply_info) | |
233 | { | |
234 | struct module_qstate* q = (struct module_qstate*)arg; | |
235 | struct worker* worker = q->env->worker; | |
236 | struct outbound_entry e; | |
237 | e.qstate = q; | |
238 | e.qsent = NULL; | |
239 | ||
240 | if(error != 0) { | |
241 | mesh_report_reply(worker->env.mesh, &e, reply_info, error); | |
242 | worker_mem_report(worker, NULL); | |
243 | return 0; | |
244 | } | |
245 | /* sanity check. */ | |
246 | if(!LDNS_QR_WIRE(sldns_buffer_begin(c->buffer)) | |
247 | || LDNS_OPCODE_WIRE(sldns_buffer_begin(c->buffer)) != | |
248 | LDNS_PACKET_QUERY | |
249 | || LDNS_QDCOUNT(sldns_buffer_begin(c->buffer)) > 1) { | |
250 | /* error becomes timeout for the module as if this reply | |
251 | * never arrived. */ | |
252 | mesh_report_reply(worker->env.mesh, &e, reply_info, | |
253 | NETEVENT_TIMEOUT); | |
254 | worker_mem_report(worker, NULL); | |
255 | return 0; | |
256 | } | |
257 | mesh_report_reply(worker->env.mesh, &e, reply_info, NETEVENT_NOERROR); | |
258 | worker_mem_report(worker, NULL); | |
259 | return 0; | |
260 | } | |
261 | ||
262 | int | |
263 | worker_handle_service_reply(struct comm_point* c, void* arg, int error, | |
264 | struct comm_reply* reply_info) | |
265 | { | |
266 | struct outbound_entry* e = (struct outbound_entry*)arg; | |
267 | struct worker* worker = e->qstate->env->worker; | |
268 | struct serviced_query *sq = e->qsent; | |
269 | ||
270 | verbose(VERB_ALGO, "worker svcd callback for qstate %p", e->qstate); | |
271 | if(error != 0) { | |
272 | mesh_report_reply(worker->env.mesh, e, reply_info, error); | |
273 | worker_mem_report(worker, sq); | |
274 | return 0; | |
275 | } | |
276 | /* sanity check. */ | |
277 | if(!LDNS_QR_WIRE(sldns_buffer_begin(c->buffer)) | |
278 | || LDNS_OPCODE_WIRE(sldns_buffer_begin(c->buffer)) != | |
279 | LDNS_PACKET_QUERY | |
280 | || LDNS_QDCOUNT(sldns_buffer_begin(c->buffer)) > 1) { | |
281 | /* error becomes timeout for the module as if this reply | |
282 | * never arrived. */ | |
283 | verbose(VERB_ALGO, "worker: bad reply handled as timeout"); | |
284 | mesh_report_reply(worker->env.mesh, e, reply_info, | |
285 | NETEVENT_TIMEOUT); | |
286 | worker_mem_report(worker, sq); | |
287 | return 0; | |
288 | } | |
289 | mesh_report_reply(worker->env.mesh, e, reply_info, NETEVENT_NOERROR); | |
290 | worker_mem_report(worker, sq); | |
291 | return 0; | |
292 | } | |
293 | ||
294 | /** check request sanity. | |
295 | * @param pkt: the wire packet to examine for sanity. | |
296 | * @param worker: parameters for checking. | |
297 | * @return error code, 0 OK, or -1 discard. | |
298 | */ | |
299 | static int | |
300 | worker_check_request(sldns_buffer* pkt, struct worker* worker) | |
301 | { | |
302 | if(sldns_buffer_limit(pkt) < LDNS_HEADER_SIZE) { | |
303 | verbose(VERB_QUERY, "request too short, discarded"); | |
304 | return -1; | |
305 | } | |
306 | if(sldns_buffer_limit(pkt) > NORMAL_UDP_SIZE && | |
307 | worker->daemon->cfg->harden_large_queries) { | |
308 | verbose(VERB_QUERY, "request too large, discarded"); | |
309 | return -1; | |
310 | } | |
311 | if(LDNS_QR_WIRE(sldns_buffer_begin(pkt))) { | |
312 | verbose(VERB_QUERY, "request has QR bit on, discarded"); | |
313 | return -1; | |
314 | } | |
315 | if(LDNS_TC_WIRE(sldns_buffer_begin(pkt))) { | |
316 | LDNS_TC_CLR(sldns_buffer_begin(pkt)); | |
317 | verbose(VERB_QUERY, "request bad, has TC bit on"); | |
318 | return LDNS_RCODE_FORMERR; | |
319 | } | |
320 | if(LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)) != LDNS_PACKET_QUERY) { | |
321 | verbose(VERB_QUERY, "request unknown opcode %d", | |
322 | LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt))); | |
323 | return LDNS_RCODE_NOTIMPL; | |
324 | } | |
325 | if(LDNS_QDCOUNT(sldns_buffer_begin(pkt)) != 1) { | |
326 | verbose(VERB_QUERY, "request wrong nr qd=%d", | |
327 | LDNS_QDCOUNT(sldns_buffer_begin(pkt))); | |
328 | return LDNS_RCODE_FORMERR; | |
329 | } | |
330 | if(LDNS_ANCOUNT(sldns_buffer_begin(pkt)) != 0) { | |
331 | verbose(VERB_QUERY, "request wrong nr an=%d", | |
332 | LDNS_ANCOUNT(sldns_buffer_begin(pkt))); | |
333 | return LDNS_RCODE_FORMERR; | |
334 | } | |
335 | if(LDNS_NSCOUNT(sldns_buffer_begin(pkt)) != 0) { | |
336 | verbose(VERB_QUERY, "request wrong nr ns=%d", | |
337 | LDNS_NSCOUNT(sldns_buffer_begin(pkt))); | |
338 | return LDNS_RCODE_FORMERR; | |
339 | } | |
340 | if(LDNS_ARCOUNT(sldns_buffer_begin(pkt)) > 1) { | |
341 | verbose(VERB_QUERY, "request wrong nr ar=%d", | |
342 | LDNS_ARCOUNT(sldns_buffer_begin(pkt))); | |
343 | return LDNS_RCODE_FORMERR; | |
344 | } | |
345 | return 0; | |
346 | } | |
347 | ||
348 | void | |
349 | worker_handle_control_cmd(struct tube* ATTR_UNUSED(tube), uint8_t* msg, | |
350 | size_t len, int error, void* arg) | |
351 | { | |
352 | struct worker* worker = (struct worker*)arg; | |
353 | enum worker_commands cmd; | |
354 | if(error != NETEVENT_NOERROR) { | |
355 | free(msg); | |
356 | if(error == NETEVENT_CLOSED) | |
357 | comm_base_exit(worker->base); | |
358 | else log_info("control event: %d", error); | |
359 | return; | |
360 | } | |
361 | if(len != sizeof(uint32_t)) { | |
362 | fatal_exit("bad control msg length %d", (int)len); | |
363 | } | |
364 | cmd = sldns_read_uint32(msg); | |
365 | free(msg); | |
366 | switch(cmd) { | |
367 | case worker_cmd_quit: | |
368 | verbose(VERB_ALGO, "got control cmd quit"); | |
369 | comm_base_exit(worker->base); | |
370 | break; | |
371 | case worker_cmd_stats: | |
372 | verbose(VERB_ALGO, "got control cmd stats"); | |
373 | server_stats_reply(worker, 1); | |
374 | break; | |
375 | case worker_cmd_stats_noreset: | |
376 | verbose(VERB_ALGO, "got control cmd stats_noreset"); | |
377 | server_stats_reply(worker, 0); | |
378 | break; | |
379 | case worker_cmd_remote: | |
380 | verbose(VERB_ALGO, "got control cmd remote"); | |
381 | daemon_remote_exec(worker); | |
382 | break; | |
383 | default: | |
384 | log_err("bad command %d", (int)cmd); | |
385 | break; | |
386 | } | |
387 | } | |
388 | ||
389 | /** check if a delegation is secure */ | |
390 | static enum sec_status | |
391 | check_delegation_secure(struct reply_info *rep) | |
392 | { | |
393 | /* return smallest security status */ | |
394 | size_t i; | |
395 | enum sec_status sec = sec_status_secure; | |
396 | enum sec_status s; | |
397 | size_t num = rep->an_numrrsets + rep->ns_numrrsets; | |
398 | /* check if answer and authority are OK */ | |
399 | for(i=0; i<num; i++) { | |
400 | s = ((struct packed_rrset_data*)rep->rrsets[i]->entry.data) | |
401 | ->security; | |
402 | if(s < sec) | |
403 | sec = s; | |
404 | } | |
405 | /* in additional, only unchecked triggers revalidation */ | |
406 | for(i=num; i<rep->rrset_count; i++) { | |
407 | s = ((struct packed_rrset_data*)rep->rrsets[i]->entry.data) | |
408 | ->security; | |
409 | if(s == sec_status_unchecked) | |
410 | return s; | |
411 | } | |
412 | return sec; | |
413 | } | |
414 | ||
415 | /** remove nonsecure from a delegation referral additional section */ | |
416 | static void | |
417 | deleg_remove_nonsecure_additional(struct reply_info* rep) | |
418 | { | |
419 | /* we can simply edit it, since we are working in the scratch region */ | |
420 | size_t i; | |
421 | enum sec_status s; | |
422 | ||
423 | for(i = rep->an_numrrsets+rep->ns_numrrsets; i<rep->rrset_count; i++) { | |
424 | s = ((struct packed_rrset_data*)rep->rrsets[i]->entry.data) | |
425 | ->security; | |
426 | if(s != sec_status_secure) { | |
427 | memmove(rep->rrsets+i, rep->rrsets+i+1, | |
428 | sizeof(struct ub_packed_rrset_key*)* | |
429 | (rep->rrset_count - i - 1)); | |
430 | rep->ar_numrrsets--; | |
431 | rep->rrset_count--; | |
432 | i--; | |
433 | } | |
434 | } | |
435 | } | |
436 | ||
437 | /** answer nonrecursive query from the cache */ | |
438 | static int | |
439 | answer_norec_from_cache(struct worker* worker, struct query_info* qinfo, | |
440 | uint16_t id, uint16_t flags, struct comm_reply* repinfo, | |
441 | struct edns_data* edns) | |
442 | { | |
443 | /* for a nonrecursive query return either: | |
444 | * o an error (servfail; we try to avoid this) | |
445 | * o a delegation (closest we have; this routine tries that) | |
446 | * o the answer (checked by answer_from_cache) | |
447 | * | |
448 | * So, grab a delegation from the rrset cache. | |
449 | * Then check if it needs validation, if so, this routine fails, | |
450 | * so that iterator can prime and validator can verify rrsets. | |
451 | */ | |
452 | uint16_t udpsize = edns->udp_size; | |
453 | int secure = 0; | |
454 | time_t timenow = *worker->env.now; | |
455 | int must_validate = (!(flags&BIT_CD) || worker->env.cfg->ignore_cd) | |
456 | && worker->env.need_to_validate; | |
457 | struct dns_msg *msg = NULL; | |
458 | struct delegpt *dp; | |
459 | ||
460 | dp = dns_cache_find_delegation(&worker->env, qinfo->qname, | |
461 | qinfo->qname_len, qinfo->qtype, qinfo->qclass, | |
462 | worker->scratchpad, &msg, timenow); | |
463 | if(!dp) { /* no delegation, need to reprime */ | |
464 | regional_free_all(worker->scratchpad); | |
465 | return 0; | |
466 | } | |
467 | if(must_validate) { | |
468 | switch(check_delegation_secure(msg->rep)) { | |
469 | case sec_status_unchecked: | |
470 | /* some rrsets have not been verified yet, go and | |
471 | * let validator do that */ | |
472 | regional_free_all(worker->scratchpad); | |
473 | return 0; | |
474 | case sec_status_bogus: | |
475 | /* some rrsets are bogus, reply servfail */ | |
476 | edns->edns_version = EDNS_ADVERTISED_VERSION; | |
477 | edns->udp_size = EDNS_ADVERTISED_SIZE; | |
478 | edns->ext_rcode = 0; | |
479 | edns->bits &= EDNS_DO; | |
480 | error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, | |
481 | &msg->qinfo, id, flags, edns); | |
482 | regional_free_all(worker->scratchpad); | |
483 | if(worker->stats.extended) { | |
484 | worker->stats.ans_bogus++; | |
485 | worker->stats.ans_rcode[LDNS_RCODE_SERVFAIL]++; | |
486 | } | |
487 | return 1; | |
488 | case sec_status_secure: | |
489 | /* all rrsets are secure */ | |
490 | /* remove non-secure rrsets from the add. section*/ | |
491 | if(worker->env.cfg->val_clean_additional) | |
492 | deleg_remove_nonsecure_additional(msg->rep); | |
493 | secure = 1; | |
494 | break; | |
495 | case sec_status_indeterminate: | |
496 | case sec_status_insecure: | |
497 | default: | |
498 | /* not secure */ | |
499 | secure = 0; | |
500 | break; | |
501 | } | |
502 | } | |
503 | /* return this delegation from the cache */ | |
504 | edns->edns_version = EDNS_ADVERTISED_VERSION; | |
505 | edns->udp_size = EDNS_ADVERTISED_SIZE; | |
506 | edns->ext_rcode = 0; | |
507 | edns->bits &= EDNS_DO; | |
508 | msg->rep->flags |= BIT_QR|BIT_RA; | |
509 | if(!reply_info_answer_encode(&msg->qinfo, msg->rep, id, flags, | |
510 | repinfo->c->buffer, 0, 1, worker->scratchpad, | |
511 | udpsize, edns, (int)(edns->bits & EDNS_DO), secure)) { | |
512 | error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, | |
513 | &msg->qinfo, id, flags, edns); | |
514 | } | |
515 | regional_free_all(worker->scratchpad); | |
516 | if(worker->stats.extended) { | |
517 | if(secure) worker->stats.ans_secure++; | |
518 | server_stats_insrcode(&worker->stats, repinfo->c->buffer); | |
519 | } | |
520 | return 1; | |
521 | } | |
522 | ||
523 | /** answer query from the cache */ | |
524 | static int | |
525 | answer_from_cache(struct worker* worker, struct query_info* qinfo, | |
526 | struct reply_info* rep, uint16_t id, uint16_t flags, | |
527 | struct comm_reply* repinfo, struct edns_data* edns) | |
528 | { | |
529 | time_t timenow = *worker->env.now; | |
530 | uint16_t udpsize = edns->udp_size; | |
531 | int secure; | |
532 | int must_validate = (!(flags&BIT_CD) || worker->env.cfg->ignore_cd) | |
533 | && worker->env.need_to_validate; | |
534 | /* see if it is possible */ | |
535 | if(rep->ttl < timenow) { | |
536 | /* the rrsets may have been updated in the meantime. | |
537 | * we will refetch the message format from the | |
538 | * authoritative server | |
539 | */ | |
540 | return 0; | |
541 | } | |
542 | if(!rrset_array_lock(rep->ref, rep->rrset_count, timenow)) | |
543 | return 0; | |
544 | /* locked and ids and ttls are OK. */ | |
545 | /* check CNAME chain (if any) */ | |
546 | if(rep->an_numrrsets > 0 && (rep->rrsets[0]->rk.type == | |
547 | htons(LDNS_RR_TYPE_CNAME) || rep->rrsets[0]->rk.type == | |
548 | htons(LDNS_RR_TYPE_DNAME))) { | |
549 | if(!reply_check_cname_chain(rep)) { | |
550 | /* cname chain invalid, redo iterator steps */ | |
551 | verbose(VERB_ALGO, "Cache reply: cname chain broken"); | |
552 | bail_out: | |
553 | rrset_array_unlock_touch(worker->env.rrset_cache, | |
554 | worker->scratchpad, rep->ref, rep->rrset_count); | |
555 | regional_free_all(worker->scratchpad); | |
556 | return 0; | |
557 | } | |
558 | } | |
559 | /* check security status of the cached answer */ | |
560 | if( rep->security == sec_status_bogus && must_validate) { | |
561 | /* BAD cached */ | |
562 | edns->edns_version = EDNS_ADVERTISED_VERSION; | |
563 | edns->udp_size = EDNS_ADVERTISED_SIZE; | |
564 | edns->ext_rcode = 0; | |
565 | edns->bits &= EDNS_DO; | |
566 | error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, | |
567 | qinfo, id, flags, edns); | |
568 | rrset_array_unlock_touch(worker->env.rrset_cache, | |
569 | worker->scratchpad, rep->ref, rep->rrset_count); | |
570 | regional_free_all(worker->scratchpad); | |
571 | if(worker->stats.extended) { | |
572 | worker->stats.ans_bogus ++; | |
573 | worker->stats.ans_rcode[LDNS_RCODE_SERVFAIL] ++; | |
574 | } | |
575 | return 1; | |
576 | } else if( rep->security == sec_status_unchecked && must_validate) { | |
577 | verbose(VERB_ALGO, "Cache reply: unchecked entry needs " | |
578 | "validation"); | |
579 | goto bail_out; /* need to validate cache entry first */ | |
580 | } else if(rep->security == sec_status_secure) { | |
581 | if(reply_all_rrsets_secure(rep)) | |
582 | secure = 1; | |
583 | else { | |
584 | if(must_validate) { | |
585 | verbose(VERB_ALGO, "Cache reply: secure entry" | |
586 | " changed status"); | |
587 | goto bail_out; /* rrset changed, re-verify */ | |
588 | } | |
589 | secure = 0; | |
590 | } | |
591 | } else secure = 0; | |
592 | ||
593 | edns->edns_version = EDNS_ADVERTISED_VERSION; | |
594 | edns->udp_size = EDNS_ADVERTISED_SIZE; | |
595 | edns->ext_rcode = 0; | |
596 | edns->bits &= EDNS_DO; | |
597 | if(!reply_info_answer_encode(qinfo, rep, id, flags, | |
598 | repinfo->c->buffer, timenow, 1, worker->scratchpad, | |
599 | udpsize, edns, (int)(edns->bits & EDNS_DO), secure)) { | |
600 | error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, | |
601 | qinfo, id, flags, edns); | |
602 | } | |
603 | /* cannot send the reply right now, because blocking network syscall | |
604 | * is bad while holding locks. */ | |
605 | rrset_array_unlock_touch(worker->env.rrset_cache, worker->scratchpad, | |
606 | rep->ref, rep->rrset_count); | |
607 | regional_free_all(worker->scratchpad); | |
608 | if(worker->stats.extended) { | |
609 | if(secure) worker->stats.ans_secure++; | |
610 | server_stats_insrcode(&worker->stats, repinfo->c->buffer); | |
611 | } | |
612 | /* go and return this buffer to the client */ | |
613 | return 1; | |
614 | } | |
615 | ||
616 | /** Reply to client and perform prefetch to keep cache up to date */ | |
617 | static void | |
618 | reply_and_prefetch(struct worker* worker, struct query_info* qinfo, | |
619 | uint16_t flags, struct comm_reply* repinfo, time_t leeway) | |
620 | { | |
621 | /* first send answer to client to keep its latency | |
622 | * as small as a cachereply */ | |
623 | comm_point_send_reply(repinfo); | |
624 | server_stats_prefetch(&worker->stats, worker); | |
625 | ||
626 | /* create the prefetch in the mesh as a normal lookup without | |
627 | * client addrs waiting, which has the cache blacklisted (to bypass | |
628 | * the cache and go to the network for the data). */ | |
629 | /* this (potentially) runs the mesh for the new query */ | |
630 | mesh_new_prefetch(worker->env.mesh, qinfo, flags, leeway + | |
631 | PREFETCH_EXPIRY_ADD); | |
632 | } | |
633 | ||
634 | /** | |
635 | * Fill CH class answer into buffer. Keeps query. | |
636 | * @param pkt: buffer | |
637 | * @param str: string to put into text record (<255). | |
638 | * @param edns: edns reply information. | |
639 | */ | |
640 | static void | |
641 | chaos_replystr(sldns_buffer* pkt, const char* str, struct edns_data* edns) | |
642 | { | |
643 | size_t len = strlen(str); | |
644 | unsigned int rd = LDNS_RD_WIRE(sldns_buffer_begin(pkt)); | |
645 | unsigned int cd = LDNS_CD_WIRE(sldns_buffer_begin(pkt)); | |
646 | if(len>255) len=255; /* cap size of TXT record */ | |
647 | sldns_buffer_clear(pkt); | |
648 | sldns_buffer_skip(pkt, (ssize_t)sizeof(uint16_t)); /* skip id */ | |
649 | sldns_buffer_write_u16(pkt, (uint16_t)(BIT_QR|BIT_RA)); | |
650 | if(rd) LDNS_RD_SET(sldns_buffer_begin(pkt)); | |
651 | if(cd) LDNS_CD_SET(sldns_buffer_begin(pkt)); | |
652 | sldns_buffer_write_u16(pkt, 1); /* qdcount */ | |
653 | sldns_buffer_write_u16(pkt, 1); /* ancount */ | |
654 | sldns_buffer_write_u16(pkt, 0); /* nscount */ | |
655 | sldns_buffer_write_u16(pkt, 0); /* arcount */ | |
656 | (void)query_dname_len(pkt); /* skip qname */ | |
657 | sldns_buffer_skip(pkt, (ssize_t)sizeof(uint16_t)); /* skip qtype */ | |
658 | sldns_buffer_skip(pkt, (ssize_t)sizeof(uint16_t)); /* skip qclass */ | |
659 | sldns_buffer_write_u16(pkt, 0xc00c); /* compr ptr to query */ | |
660 | sldns_buffer_write_u16(pkt, LDNS_RR_TYPE_TXT); | |
661 | sldns_buffer_write_u16(pkt, LDNS_RR_CLASS_CH); | |
662 | sldns_buffer_write_u32(pkt, 0); /* TTL */ | |
663 | sldns_buffer_write_u16(pkt, sizeof(uint8_t) + len); | |
664 | sldns_buffer_write_u8(pkt, len); | |
665 | sldns_buffer_write(pkt, str, len); | |
666 | sldns_buffer_flip(pkt); | |
667 | edns->edns_version = EDNS_ADVERTISED_VERSION; | |
668 | edns->udp_size = EDNS_ADVERTISED_SIZE; | |
669 | edns->bits &= EDNS_DO; | |
670 | attach_edns_record(pkt, edns); | |
671 | } | |
672 | ||
673 | /** | |
674 | * Answer CH class queries. | |
675 | * @param w: worker | |
676 | * @param qinfo: query info. Pointer into packet buffer. | |
677 | * @param edns: edns info from query. | |
678 | * @param pkt: packet buffer. | |
679 | * @return: true if a reply is to be sent. | |
680 | */ | |
681 | static int | |
682 | answer_chaos(struct worker* w, struct query_info* qinfo, | |
683 | struct edns_data* edns, sldns_buffer* pkt) | |
684 | { | |
685 | struct config_file* cfg = w->env.cfg; | |
686 | if(qinfo->qtype != LDNS_RR_TYPE_ANY && qinfo->qtype != LDNS_RR_TYPE_TXT) | |
687 | return 0; | |
688 | if(query_dname_compare(qinfo->qname, | |
689 | (uint8_t*)"\002id\006server") == 0 || | |
690 | query_dname_compare(qinfo->qname, | |
691 | (uint8_t*)"\010hostname\004bind") == 0) | |
692 | { | |
693 | if(cfg->hide_identity) | |
694 | return 0; | |
695 | if(cfg->identity==NULL || cfg->identity[0]==0) { | |
696 | char buf[MAXHOSTNAMELEN+1]; | |
697 | if (gethostname(buf, MAXHOSTNAMELEN) == 0) { | |
698 | buf[MAXHOSTNAMELEN] = 0; | |
699 | chaos_replystr(pkt, buf, edns); | |
700 | } else { | |
701 | log_err("gethostname: %s", strerror(errno)); | |
702 | chaos_replystr(pkt, "no hostname", edns); | |
703 | } | |
704 | } | |
705 | else chaos_replystr(pkt, cfg->identity, edns); | |
706 | return 1; | |
707 | } | |
708 | if(query_dname_compare(qinfo->qname, | |
709 | (uint8_t*)"\007version\006server") == 0 || | |
710 | query_dname_compare(qinfo->qname, | |
711 | (uint8_t*)"\007version\004bind") == 0) | |
712 | { | |
713 | if(cfg->hide_version) | |
714 | return 0; | |
715 | if(cfg->version==NULL || cfg->version[0]==0) | |
716 | chaos_replystr(pkt, PACKAGE_STRING, edns); | |
717 | else chaos_replystr(pkt, cfg->version, edns); | |
718 | return 1; | |
719 | } | |
720 | return 0; | |
721 | } | |
722 | ||
723 | static int | |
724 | deny_refuse(struct comm_point* c, enum acl_access acl, | |
725 | enum acl_access deny, enum acl_access refuse, | |
726 | struct worker* worker, struct comm_reply* repinfo) | |
727 | { | |
728 | if(acl == deny) { | |
729 | comm_point_drop_reply(repinfo); | |
730 | if(worker->stats.extended) | |
731 | worker->stats.unwanted_queries++; | |
732 | return 0; | |
733 | } else if(acl == refuse) { | |
734 | log_addr(VERB_ALGO, "refused query from", | |
735 | &repinfo->addr, repinfo->addrlen); | |
736 | log_buf(VERB_ALGO, "refuse", c->buffer); | |
737 | if(worker->stats.extended) | |
738 | worker->stats.unwanted_queries++; | |
739 | if(worker_check_request(c->buffer, worker) == -1) { | |
740 | comm_point_drop_reply(repinfo); | |
741 | return 0; /* discard this */ | |
742 | } | |
743 | sldns_buffer_set_limit(c->buffer, LDNS_HEADER_SIZE); | |
744 | sldns_buffer_write_at(c->buffer, 4, | |
745 | (uint8_t*)"\0\0\0\0\0\0\0\0", 8); | |
746 | LDNS_QR_SET(sldns_buffer_begin(c->buffer)); | |
747 | LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), | |
748 | LDNS_RCODE_REFUSED); | |
749 | return 1; | |
750 | } | |
751 | ||
752 | return -1; | |
753 | } | |
754 | ||
755 | static int | |
756 | deny_refuse_all(struct comm_point* c, enum acl_access acl, | |
757 | struct worker* worker, struct comm_reply* repinfo) | |
758 | { | |
759 | return deny_refuse(c, acl, acl_deny, acl_refuse, worker, repinfo); | |
760 | } | |
761 | ||
762 | static int | |
763 | deny_refuse_non_local(struct comm_point* c, enum acl_access acl, | |
764 | struct worker* worker, struct comm_reply* repinfo) | |
765 | { | |
766 | return deny_refuse(c, acl, acl_deny_non_local, acl_refuse_non_local, worker, repinfo); | |
767 | } | |
768 | ||
769 | int | |
770 | worker_handle_request(struct comm_point* c, void* arg, int error, | |
771 | struct comm_reply* repinfo) | |
772 | { | |
773 | struct worker* worker = (struct worker*)arg; | |
774 | int ret; | |
775 | hashvalue_t h; | |
776 | struct lruhash_entry* e; | |
777 | struct query_info qinfo; | |
778 | struct edns_data edns; | |
779 | enum acl_access acl; | |
780 | int rc = 0; | |
781 | ||
782 | if(error != NETEVENT_NOERROR) { | |
783 | /* some bad tcp query DNS formats give these error calls */ | |
784 | verbose(VERB_ALGO, "handle request called with err=%d", error); | |
785 | return 0; | |
786 | } | |
787 | #ifdef USE_DNSTAP | |
788 | if(worker->dtenv.log_client_query_messages) | |
789 | dt_msg_send_client_query(&worker->dtenv, &repinfo->addr, c->type, | |
790 | c->buffer); | |
791 | #endif | |
792 | acl = acl_list_lookup(worker->daemon->acl, &repinfo->addr, | |
793 | repinfo->addrlen); | |
794 | if((ret=deny_refuse_all(c, acl, worker, repinfo)) != -1) | |
795 | { | |
796 | if(ret == 1) | |
797 | goto send_reply; | |
798 | return ret; | |
799 | } | |
800 | if((ret=worker_check_request(c->buffer, worker)) != 0) { | |
801 | verbose(VERB_ALGO, "worker check request: bad query."); | |
802 | log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); | |
803 | if(ret != -1) { | |
804 | LDNS_QR_SET(sldns_buffer_begin(c->buffer)); | |
805 | LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret); | |
806 | return 1; | |
807 | } | |
808 | comm_point_drop_reply(repinfo); | |
809 | return 0; | |
810 | } | |
811 | worker->stats.num_queries++; | |
812 | /* see if query is in the cache */ | |
813 | if(!query_info_parse(&qinfo, c->buffer)) { | |
814 | verbose(VERB_ALGO, "worker parse request: formerror."); | |
815 | log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); | |
816 | sldns_buffer_rewind(c->buffer); | |
817 | LDNS_QR_SET(sldns_buffer_begin(c->buffer)); | |
818 | LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), | |
819 | LDNS_RCODE_FORMERR); | |
820 | server_stats_insrcode(&worker->stats, c->buffer); | |
821 | goto send_reply; | |
822 | } | |
823 | if(worker->env.cfg->log_queries) { | |
824 | char ip[128]; | |
825 | addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip)); | |
826 | log_nametypeclass(0, ip, qinfo.qname, qinfo.qtype, qinfo.qclass); | |
827 | } | |
828 | if(qinfo.qtype == LDNS_RR_TYPE_AXFR || | |
829 | qinfo.qtype == LDNS_RR_TYPE_IXFR) { | |
830 | verbose(VERB_ALGO, "worker request: refused zone transfer."); | |
831 | log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); | |
832 | sldns_buffer_rewind(c->buffer); | |
833 | LDNS_QR_SET(sldns_buffer_begin(c->buffer)); | |
834 | LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), | |
835 | LDNS_RCODE_REFUSED); | |
836 | if(worker->stats.extended) { | |
837 | worker->stats.qtype[qinfo.qtype]++; | |
838 | server_stats_insrcode(&worker->stats, c->buffer); | |
839 | } | |
840 | goto send_reply; | |
841 | } | |
842 | if((ret=parse_edns_from_pkt(c->buffer, &edns)) != 0) { | |
843 | verbose(VERB_ALGO, "worker parse edns: formerror."); | |
844 | log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); | |
845 | sldns_buffer_rewind(c->buffer); | |
846 | LDNS_QR_SET(sldns_buffer_begin(c->buffer)); | |
847 | LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret); | |
848 | server_stats_insrcode(&worker->stats, c->buffer); | |
849 | goto send_reply; | |
850 | } | |
851 | if(edns.edns_present && edns.edns_version != 0) { | |
852 | edns.ext_rcode = (uint8_t)(EDNS_RCODE_BADVERS>>4); | |
853 | edns.edns_version = EDNS_ADVERTISED_VERSION; | |
854 | edns.udp_size = EDNS_ADVERTISED_SIZE; | |
855 | edns.bits &= EDNS_DO; | |
856 | verbose(VERB_ALGO, "query with bad edns version."); | |
857 | log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); | |
858 | error_encode(c->buffer, EDNS_RCODE_BADVERS&0xf, &qinfo, | |
859 | *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), | |
860 | sldns_buffer_read_u16_at(c->buffer, 2), NULL); | |
861 | attach_edns_record(c->buffer, &edns); | |
862 | goto send_reply; | |
863 | } | |
864 | if(edns.edns_present && edns.udp_size < NORMAL_UDP_SIZE && | |
865 | worker->daemon->cfg->harden_short_bufsize) { | |
866 | verbose(VERB_QUERY, "worker request: EDNS bufsize %d ignored", | |
867 | (int)edns.udp_size); | |
868 | log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); | |
869 | edns.udp_size = NORMAL_UDP_SIZE; | |
870 | } | |
871 | if(edns.udp_size > worker->daemon->cfg->max_udp_size && | |
872 | c->type == comm_udp) { | |
873 | verbose(VERB_QUERY, | |
874 | "worker request: max UDP reply size modified" | |
875 | " (%d to max-udp-size)", (int)edns.udp_size); | |
876 | log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen); | |
877 | edns.udp_size = worker->daemon->cfg->max_udp_size; | |
878 | } | |
879 | if(edns.udp_size < LDNS_HEADER_SIZE) { | |
880 | verbose(VERB_ALGO, "worker request: edns is too small."); | |
881 | log_addr(VERB_CLIENT, "from", &repinfo->addr, repinfo->addrlen); | |
882 | LDNS_QR_SET(sldns_buffer_begin(c->buffer)); | |
883 | LDNS_TC_SET(sldns_buffer_begin(c->buffer)); | |
884 | LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), | |
885 | LDNS_RCODE_SERVFAIL); | |
886 | sldns_buffer_set_position(c->buffer, LDNS_HEADER_SIZE); | |
887 | sldns_buffer_write_at(c->buffer, 4, | |
888 | (uint8_t*)"\0\0\0\0\0\0\0\0", 8); | |
889 | sldns_buffer_flip(c->buffer); | |
890 | goto send_reply; | |
891 | } | |
892 | if(worker->stats.extended) | |
893 | server_stats_insquery(&worker->stats, c, qinfo.qtype, | |
894 | qinfo.qclass, &edns, repinfo); | |
895 | if(c->type != comm_udp) | |
896 | edns.udp_size = 65535; /* max size for TCP replies */ | |
897 | if(qinfo.qclass == LDNS_RR_CLASS_CH && answer_chaos(worker, &qinfo, | |
898 | &edns, c->buffer)) { | |
899 | server_stats_insrcode(&worker->stats, c->buffer); | |
900 | goto send_reply; | |
901 | } | |
902 | if(local_zones_answer(worker->daemon->local_zones, &qinfo, &edns, | |
903 | c->buffer, worker->scratchpad)) { | |
904 | regional_free_all(worker->scratchpad); | |
905 | if(sldns_buffer_limit(c->buffer) == 0) { | |
906 | comm_point_drop_reply(repinfo); | |
907 | return 0; | |
908 | } | |
909 | server_stats_insrcode(&worker->stats, c->buffer); | |
910 | goto send_reply; | |
911 | } | |
912 | ||
913 | /* We've looked in our local zones. If the answer isn't there, we | |
914 | * might need to bail out based on ACLs now. */ | |
915 | if((ret=deny_refuse_non_local(c, acl, worker, repinfo)) != -1) | |
916 | { | |
917 | if(ret == 1) | |
918 | goto send_reply; | |
919 | return ret; | |
920 | } | |
921 | ||
922 | /* If this request does not have the recursion bit set, verify | |
923 | * ACLs allow the snooping. */ | |
924 | if(!(LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) && | |
925 | acl != acl_allow_snoop ) { | |
926 | sldns_buffer_set_limit(c->buffer, LDNS_HEADER_SIZE); | |
927 | sldns_buffer_write_at(c->buffer, 4, | |
928 | (uint8_t*)"\0\0\0\0\0\0\0\0", 8); | |
929 | LDNS_QR_SET(sldns_buffer_begin(c->buffer)); | |
930 | LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), | |
931 | LDNS_RCODE_REFUSED); | |
932 | sldns_buffer_flip(c->buffer); | |
933 | server_stats_insrcode(&worker->stats, c->buffer); | |
934 | log_addr(VERB_ALGO, "refused nonrec (cache snoop) query from", | |
935 | &repinfo->addr, repinfo->addrlen); | |
936 | goto send_reply; | |
937 | } | |
938 | h = query_info_hash(&qinfo, sldns_buffer_read_u16_at(c->buffer, 2)); | |
939 | if((e=slabhash_lookup(worker->env.msg_cache, h, &qinfo, 0))) { | |
940 | /* answer from cache - we have acquired a readlock on it */ | |
941 | if(answer_from_cache(worker, &qinfo, | |
942 | (struct reply_info*)e->data, | |
943 | *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), | |
944 | sldns_buffer_read_u16_at(c->buffer, 2), repinfo, | |
945 | &edns)) { | |
946 | /* prefetch it if the prefetch TTL expired */ | |
947 | if(worker->env.cfg->prefetch && *worker->env.now >= | |
948 | ((struct reply_info*)e->data)->prefetch_ttl) { | |
949 | time_t leeway = ((struct reply_info*)e-> | |
950 | data)->ttl - *worker->env.now; | |
951 | lock_rw_unlock(&e->lock); | |
952 | reply_and_prefetch(worker, &qinfo, | |
953 | sldns_buffer_read_u16_at(c->buffer, 2), | |
954 | repinfo, leeway); | |
955 | rc = 0; | |
956 | goto send_reply_rc; | |
957 | } | |
958 | lock_rw_unlock(&e->lock); | |
959 | goto send_reply; | |
960 | } | |
961 | verbose(VERB_ALGO, "answer from the cache failed"); | |
962 | lock_rw_unlock(&e->lock); | |
963 | } | |
964 | if(!LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) { | |
965 | if(answer_norec_from_cache(worker, &qinfo, | |
966 | *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), | |
967 | sldns_buffer_read_u16_at(c->buffer, 2), repinfo, | |
968 | &edns)) { | |
969 | goto send_reply; | |
970 | } | |
971 | verbose(VERB_ALGO, "answer norec from cache -- " | |
972 | "need to validate or not primed"); | |
973 | } | |
974 | sldns_buffer_rewind(c->buffer); | |
975 | server_stats_querymiss(&worker->stats, worker); | |
976 | ||
977 | if(verbosity >= VERB_CLIENT) { | |
978 | if(c->type == comm_udp) | |
979 | log_addr(VERB_CLIENT, "udp request from", | |
980 | &repinfo->addr, repinfo->addrlen); | |
981 | else log_addr(VERB_CLIENT, "tcp request from", | |
982 | &repinfo->addr, repinfo->addrlen); | |
983 | } | |
984 | ||
985 | /* grab a work request structure for this new request */ | |
986 | mesh_new_client(worker->env.mesh, &qinfo, | |
987 | sldns_buffer_read_u16_at(c->buffer, 2), | |
988 | &edns, repinfo, *(uint16_t*)(void *)sldns_buffer_begin(c->buffer)); | |
989 | worker_mem_report(worker, NULL); | |
990 | return 0; | |
991 | ||
992 | send_reply: | |
993 | rc = 1; | |
994 | send_reply_rc: | |
995 | #ifdef USE_DNSTAP | |
996 | if(worker->dtenv.log_client_response_messages) | |
997 | dt_msg_send_client_response(&worker->dtenv, &repinfo->addr, | |
998 | c->type, c->buffer); | |
999 | #endif | |
1000 | return rc; | |
1001 | } | |
1002 | ||
1003 | void | |
1004 | worker_sighandler(int sig, void* arg) | |
1005 | { | |
1006 | /* note that log, print, syscalls here give race conditions. | |
1007 | * And cause hangups if the log-lock is held by the application. */ | |
1008 | struct worker* worker = (struct worker*)arg; | |
1009 | switch(sig) { | |
1010 | #ifdef SIGHUP | |
1011 | case SIGHUP: | |
1012 | comm_base_exit(worker->base); | |
1013 | break; | |
1014 | #endif | |
1015 | case SIGINT: | |
1016 | worker->need_to_exit = 1; | |
1017 | comm_base_exit(worker->base); | |
1018 | break; | |
1019 | #ifdef SIGQUIT | |
1020 | case SIGQUIT: | |
1021 | worker->need_to_exit = 1; | |
1022 | comm_base_exit(worker->base); | |
1023 | break; | |
1024 | #endif | |
1025 | case SIGTERM: | |
1026 | worker->need_to_exit = 1; | |
1027 | comm_base_exit(worker->base); | |
1028 | break; | |
1029 | default: | |
1030 | /* unknown signal, ignored */ | |
1031 | break; | |
1032 | } | |
1033 | } | |
1034 | ||
1035 | /** restart statistics timer for worker, if enabled */ | |
1036 | static void | |
1037 | worker_restart_timer(struct worker* worker) | |
1038 | { | |
1039 | if(worker->env.cfg->stat_interval > 0) { | |
1040 | struct timeval tv; | |
1041 | #ifndef S_SPLINT_S | |
1042 | tv.tv_sec = worker->env.cfg->stat_interval; | |
1043 | tv.tv_usec = 0; | |
1044 | #endif | |
1045 | comm_timer_set(worker->stat_timer, &tv); | |
1046 | } | |
1047 | } | |
1048 | ||
1049 | void worker_stat_timer_cb(void* arg) | |
1050 | { | |
1051 | struct worker* worker = (struct worker*)arg; | |
1052 | server_stats_log(&worker->stats, worker, worker->thread_num); | |
1053 | mesh_stats(worker->env.mesh, "mesh has"); | |
1054 | worker_mem_report(worker, NULL); | |
1055 | if(!worker->daemon->cfg->stat_cumulative) { | |
1056 | worker_stats_clear(worker); | |
1057 | } | |
1058 | /* start next timer */ | |
1059 | worker_restart_timer(worker); | |
1060 | } | |
1061 | ||
1062 | void worker_probe_timer_cb(void* arg) | |
1063 | { | |
1064 | struct worker* worker = (struct worker*)arg; | |
1065 | struct timeval tv; | |
1066 | #ifndef S_SPLINT_S | |
1067 | tv.tv_sec = (time_t)autr_probe_timer(&worker->env); | |
1068 | tv.tv_usec = 0; | |
1069 | #endif | |
1070 | if(tv.tv_sec != 0) | |
1071 | comm_timer_set(worker->env.probe_timer, &tv); | |
1072 | } | |
1073 | ||
1074 | struct worker* | |
1075 | worker_create(struct daemon* daemon, int id, int* ports, int n) | |
1076 | { | |
1077 | unsigned int seed; | |
1078 | struct worker* worker = (struct worker*)calloc(1, | |
1079 | sizeof(struct worker)); | |
1080 | if(!worker) | |
1081 | return NULL; | |
1082 | worker->numports = n; | |
1083 | worker->ports = (int*)memdup(ports, sizeof(int)*n); | |
1084 | if(!worker->ports) { | |
1085 | free(worker); | |
1086 | return NULL; | |
1087 | } | |
1088 | worker->daemon = daemon; | |
1089 | worker->thread_num = id; | |
1090 | if(!(worker->cmd = tube_create())) { | |
1091 | free(worker->ports); | |
1092 | free(worker); | |
1093 | return NULL; | |
1094 | } | |
1095 | /* create random state here to avoid locking trouble in RAND_bytes */ | |
1096 | seed = (unsigned int)time(NULL) ^ (unsigned int)getpid() ^ | |
1097 | (((unsigned int)worker->thread_num)<<17); | |
1098 | /* shift thread_num so it does not match out pid bits */ | |
1099 | if(!(worker->rndstate = ub_initstate(seed, daemon->rand))) { | |
1100 | seed = 0; | |
1101 | log_err("could not init random numbers."); | |
1102 | tube_delete(worker->cmd); | |
1103 | free(worker->ports); | |
1104 | free(worker); | |
1105 | return NULL; | |
1106 | } | |
1107 | seed = 0; | |
1108 | #ifdef USE_DNSTAP | |
1109 | if(daemon->cfg->dnstap) { | |
1110 | log_assert(daemon->dtenv != NULL); | |
1111 | memcpy(&worker->dtenv, daemon->dtenv, sizeof(struct dt_env)); | |
1112 | if(!dt_init(&worker->dtenv)) | |
1113 | fatal_exit("dt_init failed"); | |
1114 | } | |
1115 | #endif | |
1116 | return worker; | |
1117 | } | |
1118 | ||
1119 | int | |
1120 | worker_init(struct worker* worker, struct config_file *cfg, | |
1121 | struct listen_port* ports, int do_sigs) | |
1122 | { | |
1123 | #ifdef USE_DNSTAP | |
1124 | struct dt_env* dtenv = &worker->dtenv; | |
1125 | #else | |
1126 | void* dtenv = NULL; | |
1127 | #endif | |
1128 | worker->need_to_exit = 0; | |
1129 | worker->base = comm_base_create(do_sigs); | |
1130 | if(!worker->base) { | |
1131 | log_err("could not create event handling base"); | |
1132 | worker_delete(worker); | |
1133 | return 0; | |
1134 | } | |
1135 | comm_base_set_slow_accept_handlers(worker->base, &worker_stop_accept, | |
1136 | &worker_start_accept, worker); | |
1137 | if(do_sigs) { | |
1138 | #ifdef SIGHUP | |
1139 | ub_thread_sig_unblock(SIGHUP); | |
1140 | #endif | |
1141 | ub_thread_sig_unblock(SIGINT); | |
1142 | #ifdef SIGQUIT | |
1143 | ub_thread_sig_unblock(SIGQUIT); | |
1144 | #endif | |
1145 | ub_thread_sig_unblock(SIGTERM); | |
1146 | #ifndef LIBEVENT_SIGNAL_PROBLEM | |
1147 | worker->comsig = comm_signal_create(worker->base, | |
1148 | worker_sighandler, worker); | |
1149 | if(!worker->comsig | |
1150 | #ifdef SIGHUP | |
1151 | || !comm_signal_bind(worker->comsig, SIGHUP) | |
1152 | #endif | |
1153 | #ifdef SIGQUIT | |
1154 | || !comm_signal_bind(worker->comsig, SIGQUIT) | |
1155 | #endif | |
1156 | || !comm_signal_bind(worker->comsig, SIGTERM) | |
1157 | || !comm_signal_bind(worker->comsig, SIGINT)) { | |
1158 | log_err("could not create signal handlers"); | |
1159 | worker_delete(worker); | |
1160 | return 0; | |
1161 | } | |
1162 | #endif /* LIBEVENT_SIGNAL_PROBLEM */ | |
1163 | if(!daemon_remote_open_accept(worker->daemon->rc, | |
1164 | worker->daemon->rc_ports, worker)) { | |
1165 | worker_delete(worker); | |
1166 | return 0; | |
1167 | } | |
1168 | #ifdef UB_ON_WINDOWS | |
1169 | wsvc_setup_worker(worker); | |
1170 | #endif /* UB_ON_WINDOWS */ | |
1171 | } else { /* !do_sigs */ | |
1172 | worker->comsig = NULL; | |
1173 | } | |
1174 | worker->front = listen_create(worker->base, ports, | |
1175 | cfg->msg_buffer_size, (int)cfg->incoming_num_tcp, | |
1176 | worker->daemon->listen_sslctx, dtenv, worker_handle_request, | |
1177 | worker); | |
1178 | if(!worker->front) { | |
1179 | log_err("could not create listening sockets"); | |
1180 | worker_delete(worker); | |
1181 | return 0; | |
1182 | } | |
1183 | worker->back = outside_network_create(worker->base, | |
1184 | cfg->msg_buffer_size, (size_t)cfg->outgoing_num_ports, | |
1185 | cfg->out_ifs, cfg->num_out_ifs, cfg->do_ip4, cfg->do_ip6, | |
1186 | cfg->do_tcp?cfg->outgoing_num_tcp:0, | |
1187 | worker->daemon->env->infra_cache, worker->rndstate, | |
1188 | cfg->use_caps_bits_for_id, worker->ports, worker->numports, | |
1189 | cfg->unwanted_threshold, &worker_alloc_cleanup, worker, | |
1190 | cfg->do_udp, worker->daemon->connect_sslctx, cfg->delay_close, | |
1191 | dtenv); | |
1192 | if(!worker->back) { | |
1193 | log_err("could not create outgoing sockets"); | |
1194 | worker_delete(worker); | |
1195 | return 0; | |
1196 | } | |
1197 | /* start listening to commands */ | |
1198 | if(!tube_setup_bg_listen(worker->cmd, worker->base, | |
1199 | &worker_handle_control_cmd, worker)) { | |
1200 | log_err("could not create control compt."); | |
1201 | worker_delete(worker); | |
1202 | return 0; | |
1203 | } | |
1204 | worker->stat_timer = comm_timer_create(worker->base, | |
1205 | worker_stat_timer_cb, worker); | |
1206 | if(!worker->stat_timer) { | |
1207 | log_err("could not create statistics timer"); | |
1208 | } | |
1209 | ||
1210 | /* we use the msg_buffer_size as a good estimate for what the | |
1211 | * user wants for memory usage sizes */ | |
1212 | worker->scratchpad = regional_create_custom(cfg->msg_buffer_size); | |
1213 | if(!worker->scratchpad) { | |
1214 | log_err("malloc failure"); | |
1215 | worker_delete(worker); | |
1216 | return 0; | |
1217 | } | |
1218 | ||
1219 | server_stats_init(&worker->stats, cfg); | |
1220 | alloc_init(&worker->alloc, &worker->daemon->superalloc, | |
1221 | worker->thread_num); | |
1222 | alloc_set_id_cleanup(&worker->alloc, &worker_alloc_cleanup, worker); | |
1223 | worker->env = *worker->daemon->env; | |
1224 | comm_base_timept(worker->base, &worker->env.now, &worker->env.now_tv); | |
1225 | if(worker->thread_num == 0) | |
1226 | log_set_time(worker->env.now); | |
1227 | worker->env.worker = worker; | |
1228 | worker->env.send_query = &worker_send_query; | |
1229 | worker->env.alloc = &worker->alloc; | |
1230 | worker->env.rnd = worker->rndstate; | |
1231 | worker->env.scratch = worker->scratchpad; | |
1232 | worker->env.mesh = mesh_create(&worker->daemon->mods, &worker->env); | |
1233 | worker->env.detach_subs = &mesh_detach_subs; | |
1234 | worker->env.attach_sub = &mesh_attach_sub; | |
1235 | worker->env.kill_sub = &mesh_state_delete; | |
1236 | worker->env.detect_cycle = &mesh_detect_cycle; | |
1237 | worker->env.scratch_buffer = sldns_buffer_new(cfg->msg_buffer_size); | |
1238 | if(!(worker->env.fwds = forwards_create()) || | |
1239 | !forwards_apply_cfg(worker->env.fwds, cfg)) { | |
1240 | log_err("Could not set forward zones"); | |
1241 | worker_delete(worker); | |
1242 | return 0; | |
1243 | } | |
1244 | if(!(worker->env.hints = hints_create()) || | |
1245 | !hints_apply_cfg(worker->env.hints, cfg)) { | |
1246 | log_err("Could not set root or stub hints"); | |
1247 | worker_delete(worker); | |
1248 | return 0; | |
1249 | } | |
1250 | /* one probe timer per process -- if we have 5011 anchors */ | |
1251 | if(autr_get_num_anchors(worker->env.anchors) > 0 | |
1252 | #ifndef THREADS_DISABLED | |
1253 | && worker->thread_num == 0 | |
1254 | #endif | |
1255 | ) { | |
1256 | struct timeval tv; | |
1257 | tv.tv_sec = 0; | |
1258 | tv.tv_usec = 0; | |
1259 | worker->env.probe_timer = comm_timer_create(worker->base, | |
1260 | worker_probe_timer_cb, worker); | |
1261 | if(!worker->env.probe_timer) { | |
1262 | log_err("could not create 5011-probe timer"); | |
1263 | } else { | |
1264 | /* let timer fire, then it can reset itself */ | |
1265 | comm_timer_set(worker->env.probe_timer, &tv); | |
1266 | } | |
1267 | } | |
1268 | if(!worker->env.mesh || !worker->env.scratch_buffer) { | |
1269 | worker_delete(worker); | |
1270 | return 0; | |
1271 | } | |
1272 | worker_mem_report(worker, NULL); | |
1273 | /* if statistics enabled start timer */ | |
1274 | if(worker->env.cfg->stat_interval > 0) { | |
1275 | verbose(VERB_ALGO, "set statistics interval %d secs", | |
1276 | worker->env.cfg->stat_interval); | |
1277 | worker_restart_timer(worker); | |
1278 | } | |
1279 | return 1; | |
1280 | } | |
1281 | ||
1282 | void | |
1283 | worker_work(struct worker* worker) | |
1284 | { | |
1285 | comm_base_dispatch(worker->base); | |
1286 | } | |
1287 | ||
1288 | void | |
1289 | worker_delete(struct worker* worker) | |
1290 | { | |
1291 | if(!worker) | |
1292 | return; | |
1293 | if(worker->env.mesh && verbosity >= VERB_OPS) { | |
1294 | server_stats_log(&worker->stats, worker, worker->thread_num); | |
1295 | mesh_stats(worker->env.mesh, "mesh has"); | |
1296 | worker_mem_report(worker, NULL); | |
1297 | } | |
1298 | outside_network_quit_prepare(worker->back); | |
1299 | mesh_delete(worker->env.mesh); | |
1300 | sldns_buffer_free(worker->env.scratch_buffer); | |
1301 | forwards_delete(worker->env.fwds); | |
1302 | hints_delete(worker->env.hints); | |
1303 | listen_delete(worker->front); | |
1304 | outside_network_delete(worker->back); | |
1305 | comm_signal_delete(worker->comsig); | |
1306 | tube_delete(worker->cmd); | |
1307 | comm_timer_delete(worker->stat_timer); | |
1308 | comm_timer_delete(worker->env.probe_timer); | |
1309 | free(worker->ports); | |
1310 | if(worker->thread_num == 0) { | |
1311 | log_set_time(NULL); | |
1312 | #ifdef UB_ON_WINDOWS | |
1313 | wsvc_desetup_worker(worker); | |
1314 | #endif /* UB_ON_WINDOWS */ | |
1315 | } | |
1316 | comm_base_delete(worker->base); | |
1317 | ub_randfree(worker->rndstate); | |
1318 | alloc_clear(&worker->alloc); | |
1319 | regional_destroy(worker->scratchpad); | |
1320 | free(worker); | |
1321 | } | |
1322 | ||
1323 | struct outbound_entry* | |
1324 | worker_send_query(uint8_t* qname, size_t qnamelen, uint16_t qtype, | |
1325 | uint16_t qclass, uint16_t flags, int dnssec, int want_dnssec, | |
1326 | int nocaps, struct sockaddr_storage* addr, socklen_t addrlen, | |
1327 | uint8_t* zone, size_t zonelen, struct module_qstate* q) | |
1328 | { | |
1329 | struct worker* worker = q->env->worker; | |
1330 | struct outbound_entry* e = (struct outbound_entry*)regional_alloc( | |
1331 | q->region, sizeof(*e)); | |
1332 | if(!e) | |
1333 | return NULL; | |
1334 | e->qstate = q; | |
1335 | e->qsent = outnet_serviced_query(worker->back, qname, | |
1336 | qnamelen, qtype, qclass, flags, dnssec, want_dnssec, nocaps, | |
1337 | q->env->cfg->tcp_upstream, q->env->cfg->ssl_upstream, addr, | |
1338 | addrlen, zone, zonelen, worker_handle_service_reply, e, | |
1339 | worker->back->udp_buff); | |
1340 | if(!e->qsent) { | |
1341 | return NULL; | |
1342 | } | |
1343 | return e; | |
1344 | } | |
1345 | ||
1346 | void | |
1347 | worker_alloc_cleanup(void* arg) | |
1348 | { | |
1349 | struct worker* worker = (struct worker*)arg; | |
1350 | slabhash_clear(&worker->env.rrset_cache->table); | |
1351 | slabhash_clear(worker->env.msg_cache); | |
1352 | } | |
1353 | ||
1354 | void worker_stats_clear(struct worker* worker) | |
1355 | { | |
1356 | server_stats_init(&worker->stats, worker->env.cfg); | |
1357 | mesh_stats_clear(worker->env.mesh); | |
1358 | worker->back->unwanted_replies = 0; | |
1359 | worker->back->num_tcp_outgoing = 0; | |
1360 | } | |
1361 | ||
1362 | void worker_start_accept(void* arg) | |
1363 | { | |
1364 | struct worker* worker = (struct worker*)arg; | |
1365 | listen_start_accept(worker->front); | |
1366 | if(worker->thread_num == 0) | |
1367 | daemon_remote_start_accept(worker->daemon->rc); | |
1368 | } | |
1369 | ||
1370 | void worker_stop_accept(void* arg) | |
1371 | { | |
1372 | struct worker* worker = (struct worker*)arg; | |
1373 | listen_stop_accept(worker->front); | |
1374 | if(worker->thread_num == 0) | |
1375 | daemon_remote_stop_accept(worker->daemon->rc); | |
1376 | } | |
1377 | ||
1378 | /* --- fake callbacks for fptr_wlist to work --- */ | |
1379 | struct outbound_entry* libworker_send_query(uint8_t* ATTR_UNUSED(qname), | |
1380 | size_t ATTR_UNUSED(qnamelen), uint16_t ATTR_UNUSED(qtype), | |
1381 | uint16_t ATTR_UNUSED(qclass), uint16_t ATTR_UNUSED(flags), | |
1382 | int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), | |
1383 | int ATTR_UNUSED(nocaps), struct sockaddr_storage* ATTR_UNUSED(addr), | |
1384 | socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), | |
1385 | size_t ATTR_UNUSED(zonelen), struct module_qstate* ATTR_UNUSED(q)) | |
1386 | { | |
1387 | log_assert(0); | |
1388 | return 0; | |
1389 | } | |
1390 | ||
1391 | int libworker_handle_reply(struct comm_point* ATTR_UNUSED(c), | |
1392 | void* ATTR_UNUSED(arg), int ATTR_UNUSED(error), | |
1393 | struct comm_reply* ATTR_UNUSED(reply_info)) | |
1394 | { | |
1395 | log_assert(0); | |
1396 | return 0; | |
1397 | } | |
1398 | ||
1399 | int libworker_handle_service_reply(struct comm_point* ATTR_UNUSED(c), | |
1400 | void* ATTR_UNUSED(arg), int ATTR_UNUSED(error), | |
1401 | struct comm_reply* ATTR_UNUSED(reply_info)) | |
1402 | { | |
1403 | log_assert(0); | |
1404 | return 0; | |
1405 | } | |
1406 | ||
1407 | void libworker_handle_control_cmd(struct tube* ATTR_UNUSED(tube), | |
1408 | uint8_t* ATTR_UNUSED(buffer), size_t ATTR_UNUSED(len), | |
1409 | int ATTR_UNUSED(error), void* ATTR_UNUSED(arg)) | |
1410 | { | |
1411 | log_assert(0); | |
1412 | } | |
1413 | ||
1414 | void libworker_fg_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode), | |
1415 | sldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(s), | |
1416 | char* ATTR_UNUSED(why_bogus)) | |
1417 | { | |
1418 | log_assert(0); | |
1419 | } | |
1420 | ||
1421 | void libworker_bg_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode), | |
1422 | sldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(s), | |
1423 | char* ATTR_UNUSED(why_bogus)) | |
1424 | { | |
1425 | log_assert(0); | |
1426 | } | |
1427 | ||
1428 | void libworker_event_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode), | |
1429 | sldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(s), | |
1430 | char* ATTR_UNUSED(why_bogus)) | |
1431 | { | |
1432 | log_assert(0); | |
1433 | } | |
1434 | ||
1435 | int context_query_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b)) | |
1436 | { | |
1437 | log_assert(0); | |
1438 | return 0; | |
1439 | } | |
1440 | ||
1441 | int order_lock_cmp(const void* ATTR_UNUSED(e1), const void* ATTR_UNUSED(e2)) | |
1442 | { | |
1443 | log_assert(0); | |
1444 | return 0; | |
1445 | } | |
1446 | ||
1447 | int codeline_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b)) | |
1448 | { | |
1449 | log_assert(0); | |
1450 | return 0; | |
1451 | } | |
1452 |