diff options
Diffstat (limited to 'src/service/util/gnunet-service-resolver.c')
-rw-r--r-- | src/service/util/gnunet-service-resolver.c | 1384 |
1 files changed, 1384 insertions, 0 deletions
diff --git a/src/service/util/gnunet-service-resolver.c b/src/service/util/gnunet-service-resolver.c new file mode 100644 index 000000000..8d57738ed --- /dev/null +++ b/src/service/util/gnunet-service-resolver.c | |||
@@ -0,0 +1,1384 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet. | ||
3 | Copyright (C) 2007-2016 GNUnet e.V. | ||
4 | |||
5 | GNUnet is free software: you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Affero General Public License as published | ||
7 | by the Free Software Foundation, either version 3 of the License, | ||
8 | or (at your option) any later version. | ||
9 | |||
10 | GNUnet is distributed in the hope that it will be useful, but | ||
11 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | Affero General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Affero General Public License | ||
16 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | SPDX-License-Identifier: AGPL3.0-or-later | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file util/gnunet-service-resolver.c | ||
23 | * @brief code to do DNS resolution | ||
24 | * @author Christian Grothoff | ||
25 | */ | ||
26 | |||
27 | #include "platform.h" | ||
28 | #include "gnunet_util_lib.h" | ||
29 | #include "gnunet_protocols.h" | ||
30 | #include "gnunet_statistics_service.h" | ||
31 | #include "../../lib/util/resolver.h" | ||
32 | |||
33 | |||
34 | /** | ||
35 | * How long do we wait for DNS answers? | ||
36 | */ | ||
37 | #define DNS_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30) | ||
38 | |||
39 | /** | ||
40 | * Maximum number of hostnames we cache results for. | ||
41 | */ | ||
42 | #define MAX_CACHE 1024 | ||
43 | |||
44 | /** | ||
45 | * Entry in list of cached DNS records for a hostname. | ||
46 | */ | ||
47 | struct RecordListEntry | ||
48 | { | ||
49 | /** | ||
50 | * This is a doubly linked list. | ||
51 | */ | ||
52 | struct RecordListEntry *next; | ||
53 | |||
54 | /** | ||
55 | * This is a doubly linked list. | ||
56 | */ | ||
57 | struct RecordListEntry *prev; | ||
58 | |||
59 | /** | ||
60 | * Cached data. | ||
61 | */ | ||
62 | struct GNUNET_DNSPARSER_Record *record; | ||
63 | }; | ||
64 | |||
65 | |||
66 | /** | ||
67 | * A cached DNS lookup result. | ||
68 | */ | ||
69 | struct ResolveCache | ||
70 | { | ||
71 | /** | ||
72 | * This is a doubly linked list. | ||
73 | */ | ||
74 | struct ResolveCache *next; | ||
75 | |||
76 | /** | ||
77 | * This is a doubly linked list. | ||
78 | */ | ||
79 | struct ResolveCache *prev; | ||
80 | |||
81 | /** | ||
82 | * Which hostname is this cache for? | ||
83 | */ | ||
84 | char *hostname; | ||
85 | |||
86 | /** | ||
87 | * head of a double linked list containing the lookup results | ||
88 | */ | ||
89 | struct RecordListEntry *records_head; | ||
90 | |||
91 | /** | ||
92 | * tail of a double linked list containing the lookup results | ||
93 | */ | ||
94 | struct RecordListEntry *records_tail; | ||
95 | }; | ||
96 | |||
97 | |||
98 | /** | ||
99 | * Information about pending lookups. | ||
100 | */ | ||
101 | struct ActiveLookup | ||
102 | { | ||
103 | /** | ||
104 | * Stored in a DLL. | ||
105 | */ | ||
106 | struct ActiveLookup *next; | ||
107 | |||
108 | /** | ||
109 | * Stored in a DLL. | ||
110 | */ | ||
111 | struct ActiveLookup *prev; | ||
112 | |||
113 | /** | ||
114 | * The client that queried the records contained in this cache entry. | ||
115 | */ | ||
116 | struct GNUNET_SERVICE_Client *client; | ||
117 | |||
118 | /** | ||
119 | * handle for cancelling a request | ||
120 | */ | ||
121 | struct GNUNET_DNSSTUB_RequestSocket *resolve_handle; | ||
122 | |||
123 | /** | ||
124 | * handle for the resolution timeout task | ||
125 | */ | ||
126 | struct GNUNET_SCHEDULER_Task *timeout_task; | ||
127 | |||
128 | /** | ||
129 | * Which hostname are we resolving? | ||
130 | */ | ||
131 | char *hostname; | ||
132 | |||
133 | /** | ||
134 | * If @a record_type is #GNUNET_DNSPARSER_TYPE_ALL, did we go again | ||
135 | * for the AAAA records yet? | ||
136 | */ | ||
137 | int did_aaaa; | ||
138 | |||
139 | /** | ||
140 | * type of queried DNS record | ||
141 | */ | ||
142 | uint16_t record_type; | ||
143 | |||
144 | /** | ||
145 | * Unique request ID of a client if a query for this hostname/record_type | ||
146 | * is currently pending, undefined otherwise. | ||
147 | */ | ||
148 | uint32_t client_request_id; | ||
149 | |||
150 | /** | ||
151 | * Unique DNS request ID of a client if a query for this hostname/record_type | ||
152 | * is currently pending, undefined otherwise. | ||
153 | */ | ||
154 | uint16_t dns_id; | ||
155 | }; | ||
156 | |||
157 | |||
158 | /** | ||
159 | * Start of the linked list of cached DNS lookup results. | ||
160 | */ | ||
161 | static struct ResolveCache *cache_head; | ||
162 | |||
163 | /** | ||
164 | * Tail of the linked list of cached DNS lookup results. | ||
165 | */ | ||
166 | static struct ResolveCache *cache_tail; | ||
167 | |||
168 | /** | ||
169 | * Head of the linked list of DNS lookup results from /etc/hosts. | ||
170 | */ | ||
171 | static struct ResolveCache *hosts_head; | ||
172 | |||
173 | /** | ||
174 | * Tail of the linked list of DNS lookup results from /etc/hosts. | ||
175 | */ | ||
176 | static struct ResolveCache *hosts_tail; | ||
177 | |||
178 | /** | ||
179 | * Start of the linked list of active DNS lookups. | ||
180 | */ | ||
181 | static struct ActiveLookup *lookup_head; | ||
182 | |||
183 | /** | ||
184 | * Tail of the linked list of active DNS lookups. | ||
185 | */ | ||
186 | static struct ActiveLookup *lookup_tail; | ||
187 | |||
188 | /** | ||
189 | * context of dnsstub library | ||
190 | */ | ||
191 | static struct GNUNET_DNSSTUB_Context *dnsstub_ctx; | ||
192 | |||
193 | /** | ||
194 | * My domain, to be appended to the hostname to get a FQDN. | ||
195 | */ | ||
196 | static char *my_domain; | ||
197 | |||
198 | /** | ||
199 | * How many entries do we have in #cache_head DLL? | ||
200 | */ | ||
201 | static unsigned int cache_size; | ||
202 | |||
203 | |||
204 | /** | ||
205 | * Remove @a entry from cache. | ||
206 | * | ||
207 | * @param rc entry to free | ||
208 | */ | ||
209 | static void | ||
210 | free_cache_entry (struct ResolveCache *rc) | ||
211 | { | ||
212 | struct RecordListEntry *pos; | ||
213 | |||
214 | while (NULL != (pos = rc->records_head)) | ||
215 | { | ||
216 | GNUNET_CONTAINER_DLL_remove (rc->records_head, rc->records_tail, pos); | ||
217 | GNUNET_DNSPARSER_free_record (pos->record); | ||
218 | GNUNET_free (pos->record); | ||
219 | GNUNET_free (pos); | ||
220 | } | ||
221 | GNUNET_free (rc->hostname); | ||
222 | GNUNET_CONTAINER_DLL_remove (cache_head, cache_tail, rc); | ||
223 | cache_size--; | ||
224 | GNUNET_free (rc); | ||
225 | } | ||
226 | |||
227 | |||
228 | /** | ||
229 | * Remove @a entry from cache. | ||
230 | * | ||
231 | * @param rc entry to free | ||
232 | */ | ||
233 | static void | ||
234 | free_hosts_entry (struct ResolveCache *rc) | ||
235 | { | ||
236 | struct RecordListEntry *pos; | ||
237 | |||
238 | while (NULL != (pos = rc->records_head)) | ||
239 | { | ||
240 | GNUNET_CONTAINER_DLL_remove (rc->records_head, rc->records_tail, pos); | ||
241 | GNUNET_DNSPARSER_free_record (pos->record); | ||
242 | GNUNET_free (pos->record); | ||
243 | GNUNET_free (pos); | ||
244 | } | ||
245 | GNUNET_free (rc->hostname); | ||
246 | GNUNET_CONTAINER_DLL_remove (hosts_head, hosts_tail, rc); | ||
247 | cache_size--; | ||
248 | GNUNET_free (rc); | ||
249 | } | ||
250 | |||
251 | |||
252 | /** | ||
253 | * Release resources associated with @a al | ||
254 | * | ||
255 | * @param al an active lookup | ||
256 | */ | ||
257 | static void | ||
258 | free_active_lookup (struct ActiveLookup *al) | ||
259 | { | ||
260 | GNUNET_CONTAINER_DLL_remove (lookup_head, lookup_tail, al); | ||
261 | if (NULL != al->resolve_handle) | ||
262 | { | ||
263 | GNUNET_DNSSTUB_resolve_cancel (al->resolve_handle); | ||
264 | al->resolve_handle = NULL; | ||
265 | } | ||
266 | if (NULL != al->timeout_task) | ||
267 | { | ||
268 | GNUNET_SCHEDULER_cancel (al->timeout_task); | ||
269 | al->timeout_task = NULL; | ||
270 | } | ||
271 | GNUNET_free (al->hostname); | ||
272 | GNUNET_free (al); | ||
273 | } | ||
274 | |||
275 | |||
276 | /** | ||
277 | * Find out if the configuration file line contains a string | ||
278 | * starting with "nameserver ", and if so, return a copy of | ||
279 | * the nameserver's IP. | ||
280 | * | ||
281 | * @param line line to parse | ||
282 | * @param line_len number of characters in @a line | ||
283 | * @return NULL if no nameserver is configured in this @a line | ||
284 | */ | ||
285 | static char * | ||
286 | extract_dns_server (const char *line, size_t line_len) | ||
287 | { | ||
288 | if (0 == strncmp (line, "nameserver ", strlen ("nameserver "))) | ||
289 | return GNUNET_strndup (line + strlen ("nameserver "), | ||
290 | line_len - strlen ("nameserver ")); | ||
291 | return NULL; | ||
292 | } | ||
293 | |||
294 | |||
295 | /** | ||
296 | * Find out if the configuration file line contains a string | ||
297 | * starting with "search ", and if so, return a copy of | ||
298 | * the machine's search domain. | ||
299 | * | ||
300 | * @param line line to parse | ||
301 | * @param line_len number of characters in @a line | ||
302 | * @return NULL if no nameserver is configured in this @a line | ||
303 | */ | ||
304 | static char * | ||
305 | extract_search_domain (const char *line, size_t line_len) | ||
306 | { | ||
307 | if (0 == strncmp (line, "search ", strlen ("search "))) | ||
308 | return GNUNET_strndup (line + strlen ("search "), | ||
309 | line_len - strlen ("search ")); | ||
310 | return NULL; | ||
311 | } | ||
312 | |||
313 | |||
314 | /** | ||
315 | * Reads the list of nameservers from /etc/resolve.conf | ||
316 | * | ||
317 | * @param[out] server_addrs a list of null-terminated server address strings | ||
318 | * @return the number of server addresses in @a server_addrs, -1 on error | ||
319 | */ | ||
320 | static int | ||
321 | lookup_dns_servers (char ***server_addrs) | ||
322 | { | ||
323 | struct GNUNET_DISK_FileHandle *fh; | ||
324 | struct GNUNET_DISK_MapHandle *mh; | ||
325 | off_t bytes_read; | ||
326 | const char *buf; | ||
327 | size_t read_offset; | ||
328 | unsigned int num_dns_servers; | ||
329 | |||
330 | fh = GNUNET_DISK_file_open ("/etc/resolv.conf", | ||
331 | GNUNET_DISK_OPEN_READ, | ||
332 | GNUNET_DISK_PERM_NONE); | ||
333 | if (NULL == fh) | ||
334 | { | ||
335 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
336 | "Could not open /etc/resolv.conf. " | ||
337 | "DNS resolution will not be possible.\n"); | ||
338 | return -1; | ||
339 | } | ||
340 | if (GNUNET_OK != GNUNET_DISK_file_handle_size (fh, &bytes_read)) | ||
341 | { | ||
342 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
343 | "Could not determine size of /etc/resolv.conf. " | ||
344 | "DNS resolution will not be possible.\n"); | ||
345 | GNUNET_DISK_file_close (fh); | ||
346 | return -1; | ||
347 | } | ||
348 | if (((unsigned long long) bytes_read) > (unsigned long long) SIZE_MAX) | ||
349 | { | ||
350 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
351 | "/etc/resolv.conf file too large to mmap. " | ||
352 | "DNS resolution will not be possible.\n"); | ||
353 | GNUNET_DISK_file_close (fh); | ||
354 | return -1; | ||
355 | } | ||
356 | buf = GNUNET_DISK_file_map (fh, | ||
357 | &mh, | ||
358 | GNUNET_DISK_MAP_TYPE_READ, | ||
359 | (size_t) bytes_read); | ||
360 | *server_addrs = NULL; | ||
361 | read_offset = 0; | ||
362 | num_dns_servers = 0; | ||
363 | while (read_offset < (size_t) bytes_read) | ||
364 | { | ||
365 | const char *newline; | ||
366 | size_t line_len; | ||
367 | char *dns_server; | ||
368 | |||
369 | newline = strchr (buf + read_offset, '\n'); | ||
370 | if (NULL == newline) | ||
371 | break; | ||
372 | line_len = newline - buf - read_offset; | ||
373 | dns_server = extract_dns_server (buf + read_offset, line_len); | ||
374 | if (NULL != dns_server) | ||
375 | { | ||
376 | GNUNET_array_append (*server_addrs, num_dns_servers, dns_server); | ||
377 | } | ||
378 | else if (NULL == my_domain) | ||
379 | { | ||
380 | my_domain = extract_search_domain (buf + read_offset, line_len); | ||
381 | } | ||
382 | read_offset += line_len + 1; | ||
383 | } | ||
384 | GNUNET_DISK_file_unmap (mh); | ||
385 | GNUNET_DISK_file_close (fh); | ||
386 | return (int) num_dns_servers; | ||
387 | } | ||
388 | |||
389 | |||
390 | /** | ||
391 | * Compute name to use for DNS reverse lookups from @a ip. | ||
392 | * | ||
393 | * @param ip IP address to resolve, in binary format, network byte order | ||
394 | * @param af address family of @a ip, AF_INET or AF_INET6 | ||
395 | */ | ||
396 | static char * | ||
397 | make_reverse_hostname (const void *ip, int af) | ||
398 | { | ||
399 | char *buf = GNUNET_new_array (80, char); | ||
400 | int pos = 0; | ||
401 | |||
402 | if (AF_INET == af) | ||
403 | { | ||
404 | struct in_addr *addr = (struct in_addr *) ip; | ||
405 | uint32_t ip_int = addr->s_addr; | ||
406 | |||
407 | for (int i = 3; i >= 0; i--) | ||
408 | { | ||
409 | int n = | ||
410 | GNUNET_snprintf (buf + pos, 80 - pos, "%u.", ((uint8_t *) &ip_int)[i]); | ||
411 | if (n < 0) | ||
412 | { | ||
413 | GNUNET_free (buf); | ||
414 | return NULL; | ||
415 | } | ||
416 | pos += n; | ||
417 | } | ||
418 | pos += GNUNET_snprintf (buf + pos, 80 - pos, "in-addr.arpa"); | ||
419 | } | ||
420 | else if (AF_INET6 == af) | ||
421 | { | ||
422 | struct in6_addr *addr = (struct in6_addr *) ip; | ||
423 | for (int i = 15; i >= 0; i--) | ||
424 | { | ||
425 | int n = | ||
426 | GNUNET_snprintf (buf + pos, 80 - pos, "%x.", addr->s6_addr[i] & 0xf); | ||
427 | if (n < 0) | ||
428 | { | ||
429 | GNUNET_free (buf); | ||
430 | return NULL; | ||
431 | } | ||
432 | pos += n; | ||
433 | n = GNUNET_snprintf (buf + pos, 80 - pos, "%x.", addr->s6_addr[i] >> 4); | ||
434 | if (n < 0) | ||
435 | { | ||
436 | GNUNET_free (buf); | ||
437 | return NULL; | ||
438 | } | ||
439 | pos += n; | ||
440 | } | ||
441 | pos += GNUNET_snprintf (buf + pos, 80 - pos, "ip6.arpa"); | ||
442 | } | ||
443 | buf[pos] = '\0'; | ||
444 | return buf; | ||
445 | } | ||
446 | |||
447 | |||
448 | /** | ||
449 | * Send DNS @a record back to our @a client. | ||
450 | * | ||
451 | * @param record information to transmit | ||
452 | * @param record_type requested record type from client | ||
453 | * @param client_request_id to which request are we responding | ||
454 | * @param client where to send @a record | ||
455 | * @return #GNUNET_YES if we sent a reply, | ||
456 | * #GNUNET_NO if the record type is not understood or | ||
457 | * does not match @a record_type | ||
458 | */ | ||
459 | static int | ||
460 | send_reply (struct GNUNET_DNSPARSER_Record *record, | ||
461 | uint16_t record_type, | ||
462 | uint32_t client_request_id, | ||
463 | struct GNUNET_SERVICE_Client *client) | ||
464 | { | ||
465 | struct GNUNET_RESOLVER_ResponseMessage *msg; | ||
466 | struct GNUNET_MQ_Envelope *env; | ||
467 | const void *payload; | ||
468 | size_t payload_len; | ||
469 | |||
470 | switch (record->type) | ||
471 | { | ||
472 | case GNUNET_DNSPARSER_TYPE_CNAME: | ||
473 | if (GNUNET_DNSPARSER_TYPE_CNAME != record_type) | ||
474 | return GNUNET_NO; | ||
475 | payload = record->data.hostname; | ||
476 | payload_len = strlen (record->data.hostname) + 1; | ||
477 | break; | ||
478 | |||
479 | case GNUNET_DNSPARSER_TYPE_PTR: | ||
480 | if (GNUNET_DNSPARSER_TYPE_PTR != record_type) | ||
481 | return GNUNET_NO; | ||
482 | payload = record->data.hostname; | ||
483 | payload_len = strlen (record->data.hostname) + 1; | ||
484 | break; | ||
485 | |||
486 | case GNUNET_DNSPARSER_TYPE_A: | ||
487 | if ((GNUNET_DNSPARSER_TYPE_A != record_type) && | ||
488 | (GNUNET_DNSPARSER_TYPE_ALL != record_type)) | ||
489 | return GNUNET_NO; | ||
490 | payload = record->data.raw.data; | ||
491 | payload_len = record->data.raw.data_len; | ||
492 | break; | ||
493 | |||
494 | case GNUNET_DNSPARSER_TYPE_AAAA: | ||
495 | if ((GNUNET_DNSPARSER_TYPE_AAAA != record_type) && | ||
496 | (GNUNET_DNSPARSER_TYPE_ALL != record_type)) | ||
497 | return GNUNET_NO; | ||
498 | payload = record->data.raw.data; | ||
499 | payload_len = record->data.raw.data_len; | ||
500 | break; | ||
501 | |||
502 | default: | ||
503 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
504 | "Cannot handle DNS response type %u: not supported here\n", | ||
505 | record->type); | ||
506 | return GNUNET_NO; | ||
507 | } | ||
508 | env = GNUNET_MQ_msg_extra (msg, | ||
509 | payload_len, | ||
510 | GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE); | ||
511 | msg->client_id = client_request_id; | ||
512 | GNUNET_memcpy (&msg[1], payload, payload_len); | ||
513 | GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env); | ||
514 | return GNUNET_YES; | ||
515 | } | ||
516 | |||
517 | |||
518 | /** | ||
519 | * Send message to @a client that we transmitted all | ||
520 | * responses for @a client_request_id | ||
521 | * | ||
522 | * @param client_request_id to which request are we responding | ||
523 | * @param client where to send @a record | ||
524 | */ | ||
525 | static void | ||
526 | send_end_msg (uint32_t client_request_id, struct GNUNET_SERVICE_Client *client) | ||
527 | { | ||
528 | struct GNUNET_RESOLVER_ResponseMessage *msg; | ||
529 | struct GNUNET_MQ_Envelope *env; | ||
530 | |||
531 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending END message\n"); | ||
532 | env = GNUNET_MQ_msg (msg, GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE); | ||
533 | msg->client_id = client_request_id; | ||
534 | GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env); | ||
535 | } | ||
536 | |||
537 | |||
538 | /** | ||
539 | * Remove expired entries from @a rc | ||
540 | * | ||
541 | * @param rc entry in resolver cache | ||
542 | * @return #GNUNET_YES if @a rc was completely expired | ||
543 | * #GNUNET_NO if some entries are left | ||
544 | */ | ||
545 | static int | ||
546 | remove_expired (struct ResolveCache *rc) | ||
547 | { | ||
548 | struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); | ||
549 | struct RecordListEntry *n; | ||
550 | |||
551 | for (struct RecordListEntry *pos = rc->records_head; NULL != pos; pos = n) | ||
552 | { | ||
553 | n = pos->next; | ||
554 | if (now.abs_value_us > pos->record->expiration_time.abs_value_us) | ||
555 | { | ||
556 | GNUNET_CONTAINER_DLL_remove (rc->records_head, rc->records_tail, pos); | ||
557 | GNUNET_DNSPARSER_free_record (pos->record); | ||
558 | GNUNET_free (pos->record); | ||
559 | GNUNET_free (pos); | ||
560 | } | ||
561 | } | ||
562 | if (NULL == rc->records_head) | ||
563 | { | ||
564 | free_cache_entry (rc); | ||
565 | return GNUNET_YES; | ||
566 | } | ||
567 | return GNUNET_NO; | ||
568 | } | ||
569 | |||
570 | |||
571 | /** | ||
572 | * Process DNS request for @a hostname with request ID @a request_id | ||
573 | * from @a client demanding records of type @a record_type. | ||
574 | * | ||
575 | * @param hostname DNS name to resolve | ||
576 | * @param record_type desired record type | ||
577 | * @param client_request_id client's request ID | ||
578 | * @param client who should get the result? | ||
579 | */ | ||
580 | static void | ||
581 | process_get (const char *hostname, | ||
582 | uint16_t record_type, | ||
583 | uint32_t client_request_id, | ||
584 | struct GNUNET_SERVICE_Client *client); | ||
585 | |||
586 | |||
587 | /** | ||
588 | * Get an IP address as a string (works for both IPv4 and IPv6). Note | ||
589 | * that the resolution happens asynchronously and that the first call | ||
590 | * may not immediately result in the FQN (but instead in a | ||
591 | * human-readable IP address). | ||
592 | * | ||
593 | * @param hostname what hostname was to be resolved | ||
594 | * @param record_type what type of record was requested | ||
595 | * @param client_request_id unique identification of the client's request | ||
596 | * @param client handle to the client making the request (for sending the reply) | ||
597 | */ | ||
598 | static int | ||
599 | try_cache (const char *hostname, | ||
600 | uint16_t record_type, | ||
601 | uint32_t client_request_id, | ||
602 | struct GNUNET_SERVICE_Client *client) | ||
603 | { | ||
604 | struct ResolveCache *pos; | ||
605 | struct ResolveCache *next; | ||
606 | int found; | ||
607 | int in_hosts; | ||
608 | |||
609 | in_hosts = GNUNET_NO; | ||
610 | for (pos = hosts_head; NULL != pos; pos = pos->next) | ||
611 | if (0 == strcmp (pos->hostname, hostname)) | ||
612 | { | ||
613 | in_hosts = GNUNET_YES; | ||
614 | break; | ||
615 | } | ||
616 | if (NULL == pos) | ||
617 | { | ||
618 | next = cache_head; | ||
619 | for (pos = next; NULL != pos; pos = next) | ||
620 | { | ||
621 | next = pos->next; | ||
622 | if (GNUNET_YES == remove_expired (pos)) | ||
623 | continue; | ||
624 | if (0 == strcmp (pos->hostname, hostname)) | ||
625 | break; | ||
626 | } | ||
627 | } | ||
628 | if (NULL == pos) | ||
629 | { | ||
630 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No cache entry for '%s'\n", hostname); | ||
631 | return GNUNET_NO; | ||
632 | } | ||
633 | if ((GNUNET_NO == in_hosts) && (cache_head != pos)) | ||
634 | { | ||
635 | /* move result to head to achieve LRU for cache eviction */ | ||
636 | GNUNET_CONTAINER_DLL_remove (cache_head, cache_tail, pos); | ||
637 | GNUNET_CONTAINER_DLL_insert (cache_head, cache_tail, pos); | ||
638 | } | ||
639 | found = GNUNET_NO; | ||
640 | for (struct RecordListEntry *rle = pos->records_head; NULL != rle; | ||
641 | rle = rle->next) | ||
642 | { | ||
643 | const struct GNUNET_DNSPARSER_Record *record = rle->record; | ||
644 | |||
645 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
646 | "Checking cache entry for '%s', record is for '%s'\n", | ||
647 | hostname, | ||
648 | record->name); | ||
649 | if ((GNUNET_DNSPARSER_TYPE_CNAME == record->type) && | ||
650 | (GNUNET_DNSPARSER_TYPE_CNAME != record_type) && (GNUNET_NO == found)) | ||
651 | { | ||
652 | const char *hostname = record->data.hostname; | ||
653 | |||
654 | process_get (hostname, record_type, client_request_id, client); | ||
655 | return GNUNET_YES; /* counts as a cache "hit" */ | ||
656 | } | ||
657 | if (0 == strcmp (record->name, hostname)) | ||
658 | found |= send_reply (rle->record, record_type, client_request_id, client); | ||
659 | } | ||
660 | if (GNUNET_NO == found) | ||
661 | return GNUNET_NO; /* had records, but none matched! */ | ||
662 | send_end_msg (client_request_id, client); | ||
663 | return GNUNET_YES; | ||
664 | } | ||
665 | |||
666 | |||
667 | /** | ||
668 | * Create DNS query for @a hostname of type @a type | ||
669 | * with DNS request ID @a dns_id. | ||
670 | * | ||
671 | * @param hostname DNS name to query | ||
672 | * @param type requested DNS record type | ||
673 | * @param dns_id what should be the DNS request ID | ||
674 | * @param[out] packet_buf where to write the request packet | ||
675 | * @param[out] packet_size set to size of @a packet_buf on success | ||
676 | * @return #GNUNET_OK on success | ||
677 | */ | ||
678 | static int | ||
679 | pack (const char *hostname, | ||
680 | uint16_t type, | ||
681 | uint16_t dns_id, | ||
682 | char **packet_buf, | ||
683 | size_t *packet_size) | ||
684 | { | ||
685 | struct GNUNET_DNSPARSER_Query query; | ||
686 | struct GNUNET_DNSPARSER_Packet packet; | ||
687 | |||
688 | query.name = (char *) hostname; | ||
689 | query.type = type; | ||
690 | query.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET; | ||
691 | memset (&packet, 0, sizeof(packet)); | ||
692 | packet.num_queries = 1; | ||
693 | packet.queries = &query; | ||
694 | packet.id = htons (dns_id); | ||
695 | packet.flags.recursion_desired = 1; | ||
696 | if (GNUNET_OK != | ||
697 | GNUNET_DNSPARSER_pack (&packet, UINT16_MAX, packet_buf, packet_size)) | ||
698 | { | ||
699 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
700 | "Failed to pack query for hostname `%s'\n", | ||
701 | hostname); | ||
702 | packet_buf = NULL; | ||
703 | return GNUNET_SYSERR; | ||
704 | } | ||
705 | return GNUNET_OK; | ||
706 | } | ||
707 | |||
708 | |||
709 | static void | ||
710 | cache_answers (const char *name, | ||
711 | struct GNUNET_DNSPARSER_Record *records, | ||
712 | unsigned int num_records) | ||
713 | { | ||
714 | struct ResolveCache *rc; | ||
715 | struct GNUNET_DNSPARSER_Record *record; | ||
716 | struct RecordListEntry *rle; | ||
717 | |||
718 | for (unsigned int i = 0; i != num_records; i++) | ||
719 | { | ||
720 | record = &records[i]; | ||
721 | |||
722 | for (rc = cache_head; NULL != rc; rc = rc->next) | ||
723 | if (0 == strcasecmp (rc->hostname, name)) | ||
724 | break; | ||
725 | if (NULL == rc) | ||
726 | { | ||
727 | rc = GNUNET_new (struct ResolveCache); | ||
728 | rc->hostname = GNUNET_strdup (name); | ||
729 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
730 | "Caching record for name %s under %s\n", | ||
731 | record->name, name); | ||
732 | GNUNET_CONTAINER_DLL_insert (cache_head, cache_tail, rc); | ||
733 | cache_size++; | ||
734 | } | ||
735 | /* TODO: ought to check first if we have this exact record | ||
736 | already in the cache! */ | ||
737 | rle = GNUNET_new (struct RecordListEntry); | ||
738 | rle->record = GNUNET_DNSPARSER_duplicate_record (record); | ||
739 | GNUNET_CONTAINER_DLL_insert (rc->records_head, rc->records_tail, rle); | ||
740 | } | ||
741 | } | ||
742 | |||
743 | |||
744 | /** | ||
745 | * We got a result from DNS. Add it to the cache and | ||
746 | * see if we can make our client happy... | ||
747 | * | ||
748 | * @param cls the `struct ActiveLookup` | ||
749 | * @param dns the DNS response | ||
750 | * @param dns_len number of bytes in @a dns | ||
751 | */ | ||
752 | static void | ||
753 | handle_resolve_result (void *cls, | ||
754 | const struct GNUNET_TUN_DnsHeader *dns, | ||
755 | size_t dns_len) | ||
756 | { | ||
757 | struct ActiveLookup *al = cls; | ||
758 | struct GNUNET_DNSPARSER_Packet *parsed; | ||
759 | |||
760 | parsed = GNUNET_DNSPARSER_parse ((const char *) dns, dns_len); | ||
761 | if (NULL == parsed) | ||
762 | { | ||
763 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
764 | "Failed to parse DNS reply (hostname %s, request ID %u)\n", | ||
765 | al->hostname, | ||
766 | al->dns_id); | ||
767 | return; | ||
768 | } | ||
769 | if (al->dns_id != ntohs (parsed->id)) | ||
770 | { | ||
771 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
772 | "Request ID in DNS reply does not match\n"); | ||
773 | GNUNET_DNSPARSER_free_packet (parsed); | ||
774 | return; | ||
775 | } | ||
776 | if (0 == parsed->num_answers + parsed->num_authority_records | ||
777 | + parsed->num_additional_records) | ||
778 | { | ||
779 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
780 | "DNS reply (hostname %s, request ID %u) contains no answers\n", | ||
781 | al->hostname, | ||
782 | (unsigned int) al->client_request_id); | ||
783 | /* resume by trying again from cache */ | ||
784 | if (GNUNET_NO == try_cache (al->hostname, | ||
785 | al->record_type, | ||
786 | al->client_request_id, | ||
787 | al->client)) | ||
788 | /* cache failed, tell client we could not get an answer */ | ||
789 | { | ||
790 | send_end_msg (al->client_request_id, al->client); | ||
791 | } | ||
792 | GNUNET_DNSPARSER_free_packet (parsed); | ||
793 | free_active_lookup (al); | ||
794 | return; | ||
795 | } | ||
796 | /* LRU-based cache eviction: we remove from tail */ | ||
797 | while (cache_size > MAX_CACHE) | ||
798 | free_cache_entry (cache_tail); | ||
799 | |||
800 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
801 | "Got reply for hostname %s and request ID %u\n", | ||
802 | al->hostname, | ||
803 | (unsigned int) al->client_request_id); | ||
804 | /* add to cache */ | ||
805 | cache_answers (al->hostname, parsed->answers, parsed->num_answers); | ||
806 | cache_answers (al->hostname, | ||
807 | parsed->authority_records, | ||
808 | parsed->num_authority_records); | ||
809 | cache_answers (al->hostname, | ||
810 | parsed->additional_records, | ||
811 | parsed->num_additional_records); | ||
812 | |||
813 | /* see if we need to do the 2nd request for AAAA records */ | ||
814 | if ((GNUNET_DNSPARSER_TYPE_ALL == al->record_type) && | ||
815 | (GNUNET_NO == al->did_aaaa)) | ||
816 | { | ||
817 | char *packet_buf; | ||
818 | size_t packet_size; | ||
819 | uint16_t dns_id; | ||
820 | |||
821 | dns_id = (uint16_t) GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, | ||
822 | UINT16_MAX); | ||
823 | if (GNUNET_OK == pack (al->hostname, | ||
824 | GNUNET_DNSPARSER_TYPE_AAAA, | ||
825 | dns_id, | ||
826 | &packet_buf, | ||
827 | &packet_size)) | ||
828 | { | ||
829 | al->did_aaaa = GNUNET_YES; | ||
830 | al->dns_id = dns_id; | ||
831 | GNUNET_DNSSTUB_resolve_cancel (al->resolve_handle); | ||
832 | al->resolve_handle = GNUNET_DNSSTUB_resolve (dnsstub_ctx, | ||
833 | packet_buf, | ||
834 | packet_size, | ||
835 | &handle_resolve_result, | ||
836 | al); | ||
837 | GNUNET_free (packet_buf); | ||
838 | GNUNET_DNSPARSER_free_packet (parsed); | ||
839 | return; | ||
840 | } | ||
841 | } | ||
842 | |||
843 | /* resume by trying again from cache */ | ||
844 | if (GNUNET_NO == try_cache (al->hostname, | ||
845 | al->record_type, | ||
846 | al->client_request_id, | ||
847 | al->client)) | ||
848 | /* cache failed, tell client we could not get an answer */ | ||
849 | { | ||
850 | send_end_msg (al->client_request_id, al->client); | ||
851 | } | ||
852 | free_active_lookup (al); | ||
853 | GNUNET_DNSPARSER_free_packet (parsed); | ||
854 | } | ||
855 | |||
856 | |||
857 | /** | ||
858 | * We encountered a timeout trying to perform a | ||
859 | * DNS lookup. | ||
860 | * | ||
861 | * @param cls a `struct ActiveLookup` | ||
862 | */ | ||
863 | static void | ||
864 | handle_resolve_timeout (void *cls) | ||
865 | { | ||
866 | struct ActiveLookup *al = cls; | ||
867 | |||
868 | al->timeout_task = NULL; | ||
869 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "DNS lookup timeout!\n"); | ||
870 | send_end_msg (al->client_request_id, al->client); | ||
871 | free_active_lookup (al); | ||
872 | } | ||
873 | |||
874 | |||
875 | /** | ||
876 | * Initiate an active lookup, then cache the result and | ||
877 | * try to then complete the resolution. | ||
878 | * | ||
879 | * @param hostname DNS name to resolve | ||
880 | * @param record_type record type to locate | ||
881 | * @param client_request_id client request ID | ||
882 | * @param client handle to the client | ||
883 | * @return #GNUNET_OK if the DNS query is now pending | ||
884 | */ | ||
885 | static int | ||
886 | resolve_and_cache (const char *hostname, | ||
887 | uint16_t record_type, | ||
888 | uint32_t client_request_id, | ||
889 | struct GNUNET_SERVICE_Client *client) | ||
890 | { | ||
891 | char *packet_buf; | ||
892 | size_t packet_size; | ||
893 | struct ActiveLookup *al; | ||
894 | uint16_t dns_id; | ||
895 | uint16_t type; | ||
896 | |||
897 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "resolve_and_cache `%s'\n", hostname); | ||
898 | dns_id = (uint16_t) GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, | ||
899 | UINT16_MAX); | ||
900 | |||
901 | if (GNUNET_DNSPARSER_TYPE_ALL == record_type) | ||
902 | type = GNUNET_DNSPARSER_TYPE_A; | ||
903 | else | ||
904 | type = record_type; | ||
905 | if (GNUNET_OK != pack (hostname, type, dns_id, &packet_buf, &packet_size)) | ||
906 | { | ||
907 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
908 | "Failed to pack query for hostname `%s'\n", | ||
909 | hostname); | ||
910 | return GNUNET_SYSERR; | ||
911 | } | ||
912 | |||
913 | al = GNUNET_new (struct ActiveLookup); | ||
914 | al->hostname = GNUNET_strdup (hostname); | ||
915 | al->record_type = record_type; | ||
916 | al->client_request_id = client_request_id; | ||
917 | al->dns_id = dns_id; | ||
918 | al->client = client; | ||
919 | al->timeout_task = | ||
920 | GNUNET_SCHEDULER_add_delayed (DNS_TIMEOUT, &handle_resolve_timeout, al); | ||
921 | al->resolve_handle = GNUNET_DNSSTUB_resolve (dnsstub_ctx, | ||
922 | packet_buf, | ||
923 | packet_size, | ||
924 | &handle_resolve_result, | ||
925 | al); | ||
926 | GNUNET_free (packet_buf); | ||
927 | GNUNET_CONTAINER_DLL_insert (lookup_head, lookup_tail, al); | ||
928 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
929 | "Resolving %s, client_request_id = %u, dns_id = %u\n", | ||
930 | hostname, | ||
931 | (unsigned int) client_request_id, | ||
932 | (unsigned int) dns_id); | ||
933 | return GNUNET_OK; | ||
934 | } | ||
935 | |||
936 | |||
937 | /** | ||
938 | * Process DNS request for @a hostname with request ID @a client_request_id | ||
939 | * from @a client demanding records of type @a record_type. | ||
940 | * | ||
941 | * @param hostname DNS name to resolve | ||
942 | * @param record_type desired record type | ||
943 | * @param client_request_id client's request ID | ||
944 | * @param client who should get the result? | ||
945 | */ | ||
946 | static void | ||
947 | process_get (const char *hostname, | ||
948 | uint16_t record_type, | ||
949 | uint32_t client_request_id, | ||
950 | struct GNUNET_SERVICE_Client *client) | ||
951 | { | ||
952 | char fqdn[255]; | ||
953 | |||
954 | if (GNUNET_NO != try_cache (hostname, record_type, client_request_id, client)) | ||
955 | return; | ||
956 | if ((NULL != my_domain) && (NULL == strchr (hostname, (unsigned char) '.')) && | ||
957 | (strlen (hostname) + strlen (my_domain) <= 253)) | ||
958 | { | ||
959 | GNUNET_snprintf (fqdn, sizeof(fqdn), "%s.%s", hostname, my_domain); | ||
960 | } | ||
961 | else if (strlen (hostname) < 255) | ||
962 | { | ||
963 | GNUNET_snprintf (fqdn, sizeof(fqdn), "%s", hostname); | ||
964 | } | ||
965 | else | ||
966 | { | ||
967 | GNUNET_break (0); | ||
968 | GNUNET_SERVICE_client_drop (client); | ||
969 | return; | ||
970 | } | ||
971 | if (GNUNET_NO == try_cache (fqdn, record_type, client_request_id, client)) | ||
972 | { | ||
973 | if (GNUNET_OK != | ||
974 | resolve_and_cache (fqdn, record_type, client_request_id, client)) | ||
975 | { | ||
976 | send_end_msg (client_request_id, client); | ||
977 | } | ||
978 | } | ||
979 | } | ||
980 | |||
981 | |||
982 | /** | ||
983 | * Verify well-formedness of GET-message. | ||
984 | * | ||
985 | * @param cls closure, unused | ||
986 | * @param get the actual message | ||
987 | * @return #GNUNET_OK if @a get is well-formed | ||
988 | */ | ||
989 | static int | ||
990 | check_get (void *cls, const struct GNUNET_RESOLVER_GetMessage *get) | ||
991 | { | ||
992 | uint16_t size; | ||
993 | int direction; | ||
994 | int af; | ||
995 | |||
996 | (void) cls; | ||
997 | size = ntohs (get->header.size) - sizeof(*get); | ||
998 | direction = ntohl (get->direction); | ||
999 | if (GNUNET_NO == direction) | ||
1000 | { | ||
1001 | GNUNET_MQ_check_zero_termination (get); | ||
1002 | return GNUNET_OK; | ||
1003 | } | ||
1004 | af = ntohl (get->af); | ||
1005 | switch (af) | ||
1006 | { | ||
1007 | case AF_INET: | ||
1008 | if (size != sizeof(struct in_addr)) | ||
1009 | { | ||
1010 | GNUNET_break (0); | ||
1011 | return GNUNET_SYSERR; | ||
1012 | } | ||
1013 | break; | ||
1014 | |||
1015 | case AF_INET6: | ||
1016 | if (size != sizeof(struct in6_addr)) | ||
1017 | { | ||
1018 | GNUNET_break (0); | ||
1019 | return GNUNET_SYSERR; | ||
1020 | } | ||
1021 | break; | ||
1022 | |||
1023 | default: | ||
1024 | GNUNET_break (0); | ||
1025 | return GNUNET_SYSERR; | ||
1026 | } | ||
1027 | return GNUNET_OK; | ||
1028 | } | ||
1029 | |||
1030 | |||
1031 | /** | ||
1032 | * Handle GET-message. | ||
1033 | * | ||
1034 | * @param cls identification of the client | ||
1035 | * @param msg the actual message | ||
1036 | */ | ||
1037 | static void | ||
1038 | handle_get (void *cls, const struct GNUNET_RESOLVER_GetMessage *msg) | ||
1039 | { | ||
1040 | struct GNUNET_SERVICE_Client *client = cls; | ||
1041 | int direction; | ||
1042 | int af; | ||
1043 | uint32_t client_request_id; | ||
1044 | char *hostname; | ||
1045 | |||
1046 | direction = ntohl (msg->direction); | ||
1047 | af = ntohl (msg->af); | ||
1048 | client_request_id = msg->client_id; | ||
1049 | GNUNET_SERVICE_client_continue (client); | ||
1050 | if (GNUNET_NO == direction) | ||
1051 | { | ||
1052 | /* IP from hostname */ | ||
1053 | hostname = GNUNET_strdup ((const char *) &msg[1]); | ||
1054 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
1055 | "Client asks to resolve `%s'\n", | ||
1056 | hostname); | ||
1057 | switch (af) | ||
1058 | { | ||
1059 | case AF_UNSPEC: { | ||
1060 | process_get (hostname, | ||
1061 | GNUNET_DNSPARSER_TYPE_ALL, | ||
1062 | client_request_id, | ||
1063 | client); | ||
1064 | break; | ||
1065 | } | ||
1066 | |||
1067 | case AF_INET: { | ||
1068 | process_get (hostname, | ||
1069 | GNUNET_DNSPARSER_TYPE_A, | ||
1070 | client_request_id, | ||
1071 | client); | ||
1072 | break; | ||
1073 | } | ||
1074 | |||
1075 | case AF_INET6: { | ||
1076 | process_get (hostname, | ||
1077 | GNUNET_DNSPARSER_TYPE_AAAA, | ||
1078 | client_request_id, | ||
1079 | client); | ||
1080 | break; | ||
1081 | } | ||
1082 | |||
1083 | default: { | ||
1084 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "got invalid af: %d\n", af); | ||
1085 | GNUNET_assert (0); | ||
1086 | } | ||
1087 | } | ||
1088 | } | ||
1089 | else | ||
1090 | { | ||
1091 | /* hostname from IP */ | ||
1092 | hostname = make_reverse_hostname (&msg[1], af); | ||
1093 | process_get (hostname, | ||
1094 | GNUNET_DNSPARSER_TYPE_PTR, | ||
1095 | client_request_id, | ||
1096 | client); | ||
1097 | } | ||
1098 | GNUNET_free (hostname); | ||
1099 | } | ||
1100 | |||
1101 | |||
1102 | /** | ||
1103 | * Service is shutting down, clean up. | ||
1104 | * | ||
1105 | * @param cls NULL, unused | ||
1106 | */ | ||
1107 | static void | ||
1108 | shutdown_task (void *cls) | ||
1109 | { | ||
1110 | (void) cls; | ||
1111 | |||
1112 | while (NULL != lookup_head) | ||
1113 | free_active_lookup (lookup_head); | ||
1114 | while (NULL != cache_head) | ||
1115 | free_cache_entry (cache_head); | ||
1116 | while (NULL != hosts_head) | ||
1117 | free_hosts_entry (hosts_head); | ||
1118 | GNUNET_DNSSTUB_stop (dnsstub_ctx); | ||
1119 | GNUNET_free (my_domain); | ||
1120 | } | ||
1121 | |||
1122 | |||
1123 | /** | ||
1124 | * Add information about a host from /etc/hosts | ||
1125 | * to our cache. | ||
1126 | * | ||
1127 | * @param hostname the name of the host | ||
1128 | * @param rec_type DNS record type to use | ||
1129 | * @param data payload | ||
1130 | * @param data_size number of bytes in @a data | ||
1131 | */ | ||
1132 | static void | ||
1133 | add_host (const char *hostname, | ||
1134 | uint16_t rec_type, | ||
1135 | const void *data, | ||
1136 | size_t data_size) | ||
1137 | { | ||
1138 | struct ResolveCache *rc; | ||
1139 | struct RecordListEntry *rle; | ||
1140 | struct GNUNET_DNSPARSER_Record *rec; | ||
1141 | |||
1142 | rec = GNUNET_malloc (sizeof(struct GNUNET_DNSPARSER_Record)); | ||
1143 | rec->expiration_time = GNUNET_TIME_UNIT_FOREVER_ABS; | ||
1144 | rec->type = rec_type; | ||
1145 | rec->dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET; | ||
1146 | rec->name = GNUNET_strdup (hostname); | ||
1147 | rec->data.raw.data = GNUNET_memdup (data, data_size); | ||
1148 | rec->data.raw.data_len = data_size; | ||
1149 | rle = GNUNET_new (struct RecordListEntry); | ||
1150 | rle->record = rec; | ||
1151 | rc = GNUNET_new (struct ResolveCache); | ||
1152 | rc->hostname = GNUNET_strdup (hostname); | ||
1153 | GNUNET_CONTAINER_DLL_insert (rc->records_head, rc->records_tail, rle); | ||
1154 | GNUNET_CONTAINER_DLL_insert (hosts_head, hosts_tail, rc); | ||
1155 | } | ||
1156 | |||
1157 | |||
1158 | /** | ||
1159 | * Extract host information from a line in /etc/hosts | ||
1160 | * | ||
1161 | * @param line the line to parse | ||
1162 | * @param line_len number of bytes in @a line | ||
1163 | */ | ||
1164 | static void | ||
1165 | extract_hosts (const char *line, size_t line_len) | ||
1166 | { | ||
1167 | const char *c; | ||
1168 | struct in_addr v4; | ||
1169 | struct in6_addr v6; | ||
1170 | char *tbuf; | ||
1171 | char *tok; | ||
1172 | |||
1173 | /* ignore everything after '#' */ | ||
1174 | c = memrchr (line, (unsigned char) '#', line_len); | ||
1175 | if (NULL != c) | ||
1176 | line_len = c - line; | ||
1177 | /* ignore leading whitespace */ | ||
1178 | while ((0 < line_len) && isspace ((unsigned char) *line)) | ||
1179 | { | ||
1180 | line++; | ||
1181 | line_len--; | ||
1182 | } | ||
1183 | tbuf = GNUNET_strndup (line, line_len); | ||
1184 | tok = strtok (tbuf, " \t"); | ||
1185 | if (NULL == tok) | ||
1186 | { | ||
1187 | GNUNET_free (tbuf); | ||
1188 | return; | ||
1189 | } | ||
1190 | if (1 == inet_pton (AF_INET, tok, &v4)) | ||
1191 | { | ||
1192 | while (NULL != (tok = strtok (NULL, " \t"))) | ||
1193 | add_host (tok, GNUNET_DNSPARSER_TYPE_A, &v4, sizeof(struct in_addr)); | ||
1194 | } | ||
1195 | else if (1 == inet_pton (AF_INET6, tok, &v6)) | ||
1196 | { | ||
1197 | while (NULL != (tok = strtok (NULL, " \t"))) | ||
1198 | add_host (tok, GNUNET_DNSPARSER_TYPE_AAAA, &v6, sizeof(struct in6_addr)); | ||
1199 | } | ||
1200 | GNUNET_free (tbuf); | ||
1201 | } | ||
1202 | |||
1203 | |||
1204 | /** | ||
1205 | * Reads the list of hosts from /etc/hosts. | ||
1206 | */ | ||
1207 | static void | ||
1208 | load_etc_hosts (void) | ||
1209 | { | ||
1210 | struct GNUNET_DISK_FileHandle *fh; | ||
1211 | struct GNUNET_DISK_MapHandle *mh; | ||
1212 | off_t bytes_read; | ||
1213 | const char *buf; | ||
1214 | size_t read_offset; | ||
1215 | |||
1216 | fh = GNUNET_DISK_file_open ("/etc/hosts", | ||
1217 | GNUNET_DISK_OPEN_READ, | ||
1218 | GNUNET_DISK_PERM_NONE); | ||
1219 | if (NULL == fh) | ||
1220 | { | ||
1221 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Failed to open /etc/hosts"); | ||
1222 | return; | ||
1223 | } | ||
1224 | if (GNUNET_OK != GNUNET_DISK_file_handle_size (fh, &bytes_read)) | ||
1225 | { | ||
1226 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
1227 | "Could not determine size of /etc/hosts. " | ||
1228 | "DNS resolution will not be possible.\n"); | ||
1229 | GNUNET_DISK_file_close (fh); | ||
1230 | return; | ||
1231 | } | ||
1232 | if (((unsigned long long) bytes_read) > (unsigned long long) SIZE_MAX) | ||
1233 | { | ||
1234 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
1235 | "/etc/hosts file too large to mmap. " | ||
1236 | "DNS resolution will not be possible.\n"); | ||
1237 | GNUNET_DISK_file_close (fh); | ||
1238 | return; | ||
1239 | } | ||
1240 | buf = GNUNET_DISK_file_map (fh, | ||
1241 | &mh, | ||
1242 | GNUNET_DISK_MAP_TYPE_READ, | ||
1243 | (size_t) bytes_read); | ||
1244 | read_offset = 0; | ||
1245 | while (read_offset < (size_t) bytes_read) | ||
1246 | { | ||
1247 | const char *newline; | ||
1248 | size_t line_len; | ||
1249 | |||
1250 | newline = strchr (buf + read_offset, '\n'); | ||
1251 | if (NULL == newline) | ||
1252 | break; | ||
1253 | line_len = newline - buf - read_offset; | ||
1254 | extract_hosts (buf + read_offset, line_len); | ||
1255 | read_offset += line_len + 1; | ||
1256 | } | ||
1257 | GNUNET_DISK_file_unmap (mh); | ||
1258 | GNUNET_DISK_file_close (fh); | ||
1259 | } | ||
1260 | |||
1261 | |||
1262 | /** | ||
1263 | * Service is starting, initialize everything. | ||
1264 | * | ||
1265 | * @param cls NULL, unused | ||
1266 | * @param cfg our configuration | ||
1267 | * @param sh service handle | ||
1268 | */ | ||
1269 | static void | ||
1270 | init_cb (void *cls, | ||
1271 | const struct GNUNET_CONFIGURATION_Handle *cfg, | ||
1272 | struct GNUNET_SERVICE_Handle *sh) | ||
1273 | { | ||
1274 | char **dns_servers; | ||
1275 | int num_dns_servers; | ||
1276 | |||
1277 | (void) cfg; | ||
1278 | (void) sh; | ||
1279 | load_etc_hosts (); | ||
1280 | GNUNET_SCHEDULER_add_shutdown (&shutdown_task, cls); | ||
1281 | dnsstub_ctx = GNUNET_DNSSTUB_start (128); | ||
1282 | dns_servers = NULL; | ||
1283 | num_dns_servers = lookup_dns_servers (&dns_servers); | ||
1284 | if (0 >= num_dns_servers) | ||
1285 | { | ||
1286 | GNUNET_log ( | ||
1287 | GNUNET_ERROR_TYPE_ERROR, | ||
1288 | _ ("No DNS server available. DNS resolution will not be possible.\n")); | ||
1289 | return; | ||
1290 | } | ||
1291 | for (int i = 0; i < num_dns_servers; i++) | ||
1292 | { | ||
1293 | int result = GNUNET_DNSSTUB_add_dns_ip (dnsstub_ctx, dns_servers[i]); | ||
1294 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
1295 | "Adding DNS server '%s': %s\n", | ||
1296 | dns_servers[i], | ||
1297 | GNUNET_OK == result ? "success" : "failure"); | ||
1298 | GNUNET_free (dns_servers[i]); | ||
1299 | } | ||
1300 | GNUNET_free (dns_servers); | ||
1301 | } | ||
1302 | |||
1303 | |||
1304 | /** | ||
1305 | * Callback called when a client connects to the service. | ||
1306 | * | ||
1307 | * @param cls closure for the service, unused | ||
1308 | * @param c the new client that connected to the service | ||
1309 | * @param mq the message queue used to send messages to the client | ||
1310 | * @return @a c | ||
1311 | */ | ||
1312 | static void * | ||
1313 | connect_cb (void *cls, | ||
1314 | struct GNUNET_SERVICE_Client *c, | ||
1315 | struct GNUNET_MQ_Handle *mq) | ||
1316 | { | ||
1317 | (void) cls; | ||
1318 | (void) mq; | ||
1319 | |||
1320 | return c; | ||
1321 | } | ||
1322 | |||
1323 | |||
1324 | /** | ||
1325 | * Callback called when a client disconnected from the service | ||
1326 | * | ||
1327 | * @param cls closure for the service | ||
1328 | * @param c the client that disconnected | ||
1329 | * @param internal_cls should be equal to @a c | ||
1330 | */ | ||
1331 | static void | ||
1332 | disconnect_cb (void *cls, struct GNUNET_SERVICE_Client *c, void *internal_cls) | ||
1333 | { | ||
1334 | struct ActiveLookup *n; | ||
1335 | |||
1336 | (void) cls; | ||
1337 | |||
1338 | GNUNET_assert (c == internal_cls); | ||
1339 | n = lookup_head; | ||
1340 | for (struct ActiveLookup *al = n; NULL != al; al = n) | ||
1341 | { | ||
1342 | n = al->next; | ||
1343 | if (al->client == c) | ||
1344 | free_active_lookup (al); | ||
1345 | } | ||
1346 | } | ||
1347 | |||
1348 | |||
1349 | /** | ||
1350 | * Define "main" method using service macro. | ||
1351 | */ | ||
1352 | GNUNET_SERVICE_MAIN ( | ||
1353 | "resolver", | ||
1354 | GNUNET_SERVICE_OPTION_NONE, | ||
1355 | &init_cb, | ||
1356 | &connect_cb, | ||
1357 | &disconnect_cb, | ||
1358 | NULL, | ||
1359 | GNUNET_MQ_hd_var_size (get, | ||
1360 | GNUNET_MESSAGE_TYPE_RESOLVER_REQUEST, | ||
1361 | struct GNUNET_RESOLVER_GetMessage, | ||
1362 | NULL), | ||
1363 | GNUNET_MQ_handler_end ()); | ||
1364 | |||
1365 | |||
1366 | #if defined(__linux__) && defined(__GLIBC__) | ||
1367 | #include <malloc.h> | ||
1368 | |||
1369 | /** | ||
1370 | * MINIMIZE heap size (way below 128k) since this process doesn't need much. | ||
1371 | */ | ||
1372 | void __attribute__ ((constructor)) | ||
1373 | GNUNET_RESOLVER_memory_init () | ||
1374 | { | ||
1375 | mallopt (M_TRIM_THRESHOLD, 4 * 1024); | ||
1376 | mallopt (M_TOP_PAD, 1 * 1024); | ||
1377 | malloc_trim (0); | ||
1378 | } | ||
1379 | |||
1380 | |||
1381 | #endif | ||
1382 | |||
1383 | |||
1384 | /* end of gnunet-service-resolver.c */ | ||