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