aboutsummaryrefslogtreecommitdiff
path: root/src/util/gnunet-service-resolver.c
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2018-08-14 15:30:42 +0200
committerChristian Grothoff <christian@grothoff.org>2018-08-14 15:30:42 +0200
commit1c757482ba7ca48fa108f1613cccc5c775bd957b (patch)
treed2d1dab3bc5935caf9c65bf3131bc163475ead38 /src/util/gnunet-service-resolver.c
parent11d4a9262ecaa7b29b1b549c324c4806591eb8b2 (diff)
downloadgnunet-1c757482ba7ca48fa108f1613cccc5c775bd957b.tar.gz
gnunet-1c757482ba7ca48fa108f1613cccc5c775bd957b.zip
fix CNAME handling, caching, out-of-bounds accesses, etc. in gnunet-service-resolver
Diffstat (limited to 'src/util/gnunet-service-resolver.c')
-rw-r--r--src/util/gnunet-service-resolver.c869
1 files changed, 562 insertions, 307 deletions
diff --git a/src/util/gnunet-service-resolver.c b/src/util/gnunet-service-resolver.c
index 5b890261b..06af57509 100644
--- a/src/util/gnunet-service-resolver.c
+++ b/src/util/gnunet-service-resolver.c
@@ -11,7 +11,7 @@
11 WITHOUT ANY WARRANTY; without even the implied warranty of 11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details. 13 Affero General Public License for more details.
14 14
15 You should have received a copy of the GNU Affero General Public License 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/>. 16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17*/ 17*/
@@ -28,15 +28,38 @@
28#include "resolver.h" 28#include "resolver.h"
29 29
30 30
31struct Record 31/**
32 * How long do we wait for DNS answers?
33 */
34#define DNS_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)
35
36/**
37 * Maximum number of hostnames we cache results for.
38 */
39#define MAX_CACHE 1024
40
41/**
42 * Entry in list of cached DNS records for a hostname.
43 */
44struct RecordListEntry
32{ 45{
33 struct Record *next; 46 /**
47 * This is a doubly linked list.
48 */
49 struct RecordListEntry *next;
34 50
35 struct Record *prev; 51 /**
52 * This is a doubly linked list.
53 */
54 struct RecordListEntry *prev;
36 55
56 /**
57 * Cached data.
58 */
37 struct GNUNET_DNSPARSER_Record *record; 59 struct GNUNET_DNSPARSER_Record *record;
38}; 60};
39 61
62
40/** 63/**
41 * A cached DNS lookup result. 64 * A cached DNS lookup result.
42 */ 65 */
@@ -53,30 +76,42 @@ struct ResolveCache
53 struct ResolveCache *prev; 76 struct ResolveCache *prev;
54 77
55 /** 78 /**
56 * type of queried DNS record 79 * Which hostname is this cache for?
57 */ 80 */
58 uint16_t record_type; 81 char *hostname;
59 82
60 /** 83 /**
61 * a pointer to the request_id if a query for this hostname/record_type 84 * head of a double linked list containing the lookup results
62 * is currently pending, NULL otherwise.
63 */ 85 */
64 int16_t *request_id; 86 struct RecordListEntry *records_head;
65 87
66 /** 88 /**
67 * The client that queried the records contained in this cache entry. 89 * tail of a double linked list containing the lookup results
68 */ 90 */
69 struct GNUNET_SERVICE_Client *client; 91 struct RecordListEntry *records_tail;
70 92
93};
94
95
96/**
97 * Information about pending lookups.
98 */
99struct ActiveLookup
100{
71 /** 101 /**
72 * head of a double linked list containing the lookup results 102 * Stored in a DLL.
73 */ 103 */
74 struct Record *records_head; 104 struct ActiveLookup *next;
75 105
76 /** 106 /**
77 * tail of a double linked list containing the lookup results 107 * Stored in a DLL.
78 */ 108 */
79 struct Record *records_tail; 109 struct ActiveLookup *prev;
110
111 /**
112 * The client that queried the records contained in this cache entry.
113 */
114 struct GNUNET_SERVICE_Client *client;
80 115
81 /** 116 /**
82 * handle for cancelling a request 117 * handle for cancelling a request
@@ -88,6 +123,28 @@ struct ResolveCache
88 */ 123 */
89 struct GNUNET_SCHEDULER_Task *timeout_task; 124 struct GNUNET_SCHEDULER_Task *timeout_task;
90 125
126 /**
127 * Which hostname are we resolving?
128 */
129 char *hostname;
130
131 /**
132 * type of queried DNS record
133 */
134 uint16_t record_type;
135
136 /**
137 * Unique request ID of a client if a query for this hostname/record_type
138 * is currently pending, undefined otherwise.
139 */
140 uint16_t request_id;
141
142 /**
143 * Unique DNS request ID of a client if a query for this hostname/record_type
144 * is currently pending, undefined otherwise.
145 */
146 uint16_t dns_id;
147
91}; 148};
92 149
93 150
@@ -102,69 +159,117 @@ static struct ResolveCache *cache_head;
102static struct ResolveCache *cache_tail; 159static struct ResolveCache *cache_tail;
103 160
104/** 161/**
162 * Start of the linked list of active DNS lookups.
163 */
164static struct ActiveLookup *lookup_head;
165
166/**
167 * Tail of the linked list of active DNS lookups.
168 */
169static struct ActiveLookup *lookup_tail;
170
171/**
105 * context of dnsstub library 172 * context of dnsstub library
106 */ 173 */
107static struct GNUNET_DNSSTUB_Context *dnsstub_ctx; 174static struct GNUNET_DNSSTUB_Context *dnsstub_ctx;
108 175
176/**
177 * How many entries do we have in #cache_head DLL?
178 */
179static unsigned int cache_size;
109 180
110void free_cache_entry (struct ResolveCache *entry) 181/**
182 * Remove @a entry from cache.
183 *
184 * @param rc entry to free
185 */
186static void
187free_cache_entry (struct ResolveCache *rc)
111{ 188{
112 struct Record *pos; 189 struct RecordListEntry *pos;
113 struct Record *next; 190
114 191 while (NULL != (pos = rc->records_head))
115 next = entry->records_head;
116 while (NULL != (pos = next))
117 { 192 {
118 next = pos->next; 193 GNUNET_CONTAINER_DLL_remove (rc->records_head,
119 GNUNET_CONTAINER_DLL_remove (entry->records_head, 194 rc->records_tail,
120 entry->records_tail,
121 pos); 195 pos);
122 if (NULL != pos->record) 196 GNUNET_DNSPARSER_free_record (pos->record);
123 { 197 GNUNET_free (pos->record);
124 GNUNET_DNSPARSER_free_record (pos->record);
125 GNUNET_free (pos->record);
126 }
127 GNUNET_free (pos); 198 GNUNET_free (pos);
128 } 199 }
129 if (NULL != entry->resolve_handle) 200 GNUNET_free_non_null (rc->hostname);
201 GNUNET_CONTAINER_DLL_remove (cache_head,
202 cache_tail,
203 rc);
204 cache_size--;
205 GNUNET_free (rc);
206}
207
208
209/**
210 * Release resources associated with @a al
211 *
212 * @param al an active lookup
213 */
214static void
215free_active_lookup (struct ActiveLookup *al)
216{
217 GNUNET_CONTAINER_DLL_remove (lookup_head,
218 lookup_tail,
219 al);
220 if (NULL != al->resolve_handle)
130 { 221 {
131 GNUNET_DNSSTUB_resolve_cancel (entry->resolve_handle); 222 GNUNET_DNSSTUB_resolve_cancel (al->resolve_handle);
132 entry->resolve_handle = NULL; 223 al->resolve_handle = NULL;
133 } 224 }
134 if (NULL != entry->timeout_task) 225 if (NULL != al->timeout_task)
135 { 226 {
136 GNUNET_SCHEDULER_cancel (entry->timeout_task); 227 GNUNET_SCHEDULER_cancel (al->timeout_task);
137 entry->timeout_task = NULL; 228 al->timeout_task = NULL;
138 } 229 }
139 GNUNET_free_non_null (entry->request_id); 230 GNUNET_free_non_null (al->hostname);
140 GNUNET_free (entry); 231 GNUNET_free (al);
141} 232}
142 233
143 234
144static char* 235
145extract_dns_server (const char* line, size_t line_len) 236/**
237 * Find out if the configuration file line contains a string
238 * starting with "nameserver ", and if so, return a copy of
239 * the nameserver's IP.
240 *
241 * @param line line to parse
242 * @param line_len number of characters in @a line
243 * @return NULL if no nameserver is configured in this @a line
244 */
245static char *
246extract_dns_server (const char* line,
247 size_t line_len)
146{ 248{
147 if (0 == strncmp (line, "nameserver ", 11)) 249 if (0 == strncmp (line,
148 return GNUNET_strndup (line + 11, line_len - 11); 250 "nameserver ",
251 strlen ("nameserver ")))
252 return GNUNET_strndup (line + strlen ("nameserver "),
253 line_len - strlen ("nameserver "));
149 return NULL; 254 return NULL;
150} 255}
151 256
152 257
153/** 258/**
154 * reads the list of nameservers from /etc/resolve.conf 259 * Reads the list of nameservers from /etc/resolve.conf
155 * 260 *
156 * @param server_addrs[out] a list of null-terminated server address strings 261 * @param server_addrs[out] a list of null-terminated server address strings
157 * @return the number of server addresses in @server_addrs, -1 on error 262 * @return the number of server addresses in @server_addrs, -1 on error
158 */ 263 */
159static ssize_t 264static int
160lookup_dns_servers (char ***server_addrs) 265lookup_dns_servers (char ***server_addrs)
161{ 266{
162 struct GNUNET_DISK_FileHandle *fh; 267 struct GNUNET_DISK_FileHandle *fh;
163 char buf[2048]; 268 char buf[2048];
164 ssize_t bytes_read; 269 ssize_t bytes_read;
165 size_t read_offset = 0; 270 size_t read_offset;
166 unsigned int num_dns_servers = 0; 271 unsigned int num_dns_servers;
167 272
168 fh = GNUNET_DISK_file_open ("/etc/resolv.conf", 273 fh = GNUNET_DISK_file_open ("/etc/resolv.conf",
169 GNUNET_DISK_OPEN_READ, 274 GNUNET_DISK_OPEN_READ,
170 GNUNET_DISK_PERM_NONE); 275 GNUNET_DISK_PERM_NONE);
@@ -179,41 +284,51 @@ lookup_dns_servers (char ***server_addrs)
179 buf, 284 buf,
180 sizeof (buf)); 285 sizeof (buf));
181 *server_addrs = NULL; 286 *server_addrs = NULL;
287 read_offset = 0;
288 num_dns_servers = 0;
182 while (read_offset < bytes_read) 289 while (read_offset < bytes_read)
183 { 290 {
184 char *newline; 291 const char *newline;
185 size_t line_len; 292 size_t line_len;
186 char *dns_server; 293 char *dns_server;
187 294
188 newline = strchr (buf + read_offset, '\n'); 295 newline = strchr (buf + read_offset,
296 '\n');
189 if (NULL == newline) 297 if (NULL == newline)
190 {
191 break; 298 break;
192 }
193 line_len = newline - buf - read_offset; 299 line_len = newline - buf - read_offset;
194 dns_server = extract_dns_server (buf + read_offset, line_len); 300 dns_server = extract_dns_server (buf + read_offset,
301 line_len);
195 if (NULL != dns_server) 302 if (NULL != dns_server)
196 {
197 GNUNET_array_append (*server_addrs, 303 GNUNET_array_append (*server_addrs,
198 num_dns_servers, 304 num_dns_servers,
199 dns_server); 305 dns_server);
200 }
201 read_offset += line_len + 1; 306 read_offset += line_len + 1;
202 } 307 }
203 GNUNET_DISK_file_close (fh); 308 GNUNET_DISK_file_close (fh);
204 return num_dns_servers; 309 return (int) num_dns_servers;
205} 310}
206 311
207 312
313/**
314 * Compute name to use for DNS reverse lookups from @a ip.
315 *
316 * @param ip IP address to resolve, in binary format, network byte order
317 * @param af address family of @a ip, AF_INET or AF_INET6
318 */
208static char * 319static char *
209make_reverse_hostname (const void *ip, int af) 320make_reverse_hostname (const void *ip,
321 int af)
210{ 322{
211 char *buf = GNUNET_new_array (80, char); 323 char *buf = GNUNET_new_array (80,
324 char);
212 int pos = 0; 325 int pos = 0;
326
213 if (AF_INET == af) 327 if (AF_INET == af)
214 { 328 {
215 struct in_addr *addr = (struct in_addr *)ip; 329 struct in_addr *addr = (struct in_addr *)ip;
216 uint32_t ip_int = addr->s_addr; 330 uint32_t ip_int = addr->s_addr;
331
217 for (int i = 3; i >= 0; i--) 332 for (int i = 3; i >= 0; i--)
218 { 333 {
219 int n = GNUNET_snprintf (buf + pos, 334 int n = GNUNET_snprintf (buf + pos,
@@ -227,21 +342,29 @@ make_reverse_hostname (const void *ip, int af)
227 } 342 }
228 pos += n; 343 pos += n;
229 } 344 }
230 pos += GNUNET_snprintf (buf + pos, 80 - pos, "in-addr.arpa"); 345 pos += GNUNET_snprintf (buf + pos,
346 80 - pos,
347 "in-addr.arpa");
231 } 348 }
232 else if (AF_INET6 == af) 349 else if (AF_INET6 == af)
233 { 350 {
234 struct in6_addr *addr = (struct in6_addr *)ip; 351 struct in6_addr *addr = (struct in6_addr *)ip;
235 for (int i = 15; i >= 0; i--) 352 for (int i = 15; i >= 0; i--)
236 { 353 {
237 int n = GNUNET_snprintf (buf + pos, 80 - pos, "%x.", addr->s6_addr[i] & 0xf); 354 int n = GNUNET_snprintf (buf + pos,
355 80 - pos,
356 "%x.",
357 addr->s6_addr[i] & 0xf);
238 if (n < 0) 358 if (n < 0)
239 { 359 {
240 GNUNET_free (buf); 360 GNUNET_free (buf);
241 return NULL; 361 return NULL;
242 } 362 }
243 pos += n; 363 pos += n;
244 n = GNUNET_snprintf (buf + pos, 80 - pos, "%x.", addr->s6_addr[i] >> 4); 364 n = GNUNET_snprintf (buf + pos,
365 80 - pos,
366 "%x.",
367 addr->s6_addr[i] >> 4);
245 if (n < 0) 368 if (n < 0)
246 { 369 {
247 GNUNET_free (buf); 370 GNUNET_free (buf);
@@ -249,45 +372,70 @@ make_reverse_hostname (const void *ip, int af)
249 } 372 }
250 pos += n; 373 pos += n;
251 } 374 }
252 pos += GNUNET_snprintf (buf + pos, 80 - pos, "ip6.arpa"); 375 pos += GNUNET_snprintf (buf + pos,
376 80 - pos,
377 "ip6.arpa");
253 } 378 }
254 buf[pos] = '\0'; 379 buf[pos] = '\0';
255 return buf; 380 return buf;
256} 381}
257 382
258 383
259static void 384/**
385 * Send DNS @a record back to our @a client.
386 *
387 * @param record information to transmit
388 * @param record_type requested record type from client
389 * @param request_id to which request are we responding
390 * @param client where to send @a record
391 * @return #GNUNET_YES if we sent a reply,
392 * #GNUNET_NO if the record type is not understood or
393 * does not match @a record_type
394 */
395static int
260send_reply (struct GNUNET_DNSPARSER_Record *record, 396send_reply (struct GNUNET_DNSPARSER_Record *record,
397 uint16_t record_type,
261 uint16_t request_id, 398 uint16_t request_id,
262 struct GNUNET_SERVICE_Client *client) 399 struct GNUNET_SERVICE_Client *client)
263{ 400{
264 struct GNUNET_RESOLVER_ResponseMessage *msg; 401 struct GNUNET_RESOLVER_ResponseMessage *msg;
265 struct GNUNET_MQ_Envelope *env; 402 struct GNUNET_MQ_Envelope *env;
266 void *payload; 403 const void *payload;
267 size_t payload_len; 404 size_t payload_len;
268 405
269 switch (record->type) 406 switch (record->type)
270 { 407 {
271 case GNUNET_DNSPARSER_TYPE_PTR: 408 case GNUNET_DNSPARSER_TYPE_CNAME:
272 { 409 if (GNUNET_DNSPARSER_TYPE_CNAME != record_type)
273 char *hostname = record->data.hostname; 410 return GNUNET_NO;
274 payload = hostname; 411 payload = record->data.hostname;
275 payload_len = strlen (hostname) + 1; 412 payload_len = strlen (record->data.hostname) + 1;
276 break; 413 break;
277 } 414 case GNUNET_DNSPARSER_TYPE_PTR:
278 case GNUNET_DNSPARSER_TYPE_A: 415 if (GNUNET_DNSPARSER_TYPE_PTR != record_type)
279 case GNUNET_DNSPARSER_TYPE_AAAA: 416 return GNUNET_NO;
280 { 417 payload = record->data.hostname;
281 payload = record->data.raw.data; 418 payload_len = strlen (record->data.hostname) + 1;
282 payload_len = record->data.raw.data_len; 419 break;
283 break; 420 case GNUNET_DNSPARSER_TYPE_A:
284 } 421 if ( (GNUNET_DNSPARSER_TYPE_A != record_type) &&
285 default: 422 (GNUNET_DNSPARSER_TYPE_ALL != record_type) )
286 { 423 return GNUNET_NO;
287 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 424 payload = record->data.raw.data;
288 "Cannot handle DNS response type: unimplemented\n"); 425 payload_len = record->data.raw.data_len;
289 return; 426 break;
290 } 427 case GNUNET_DNSPARSER_TYPE_AAAA:
428 if ( (GNUNET_DNSPARSER_TYPE_AAAA != record_type) &&
429 (GNUNET_DNSPARSER_TYPE_ALL != record_type) )
430 return GNUNET_NO;
431 payload = record->data.raw.data;
432 payload_len = record->data.raw.data_len;
433 break;
434 default:
435 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
436 "Cannot handle DNS response type %u: not supported here\n",
437 record->type);
438 return GNUNET_NO;
291 } 439 }
292 env = GNUNET_MQ_msg_extra (msg, 440 env = GNUNET_MQ_msg_extra (msg,
293 payload_len, 441 payload_len,
@@ -298,9 +446,17 @@ send_reply (struct GNUNET_DNSPARSER_Record *record,
298 payload_len); 446 payload_len);
299 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), 447 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
300 env); 448 env);
449 return GNUNET_YES;
301} 450}
302 451
303 452
453/**
454 * Send message to @a client that we transmitted all
455 * responses for @a request_id
456 *
457 * @param request_id to which request are we responding
458 * @param client where to send @a record
459 */
304static void 460static void
305send_end_msg (uint16_t request_id, 461send_end_msg (uint16_t request_id,
306 struct GNUNET_SERVICE_Client *client) 462 struct GNUNET_SERVICE_Client *client)
@@ -309,7 +465,7 @@ send_end_msg (uint16_t request_id,
309 struct GNUNET_MQ_Envelope *env; 465 struct GNUNET_MQ_Envelope *env;
310 466
311 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 467 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
312 "Sending end message\n"); 468 "Sending END message\n");
313 env = GNUNET_MQ_msg (msg, 469 env = GNUNET_MQ_msg (msg,
314 GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE); 470 GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE);
315 msg->id = request_id; 471 msg->id = request_id;
@@ -318,102 +474,263 @@ send_end_msg (uint16_t request_id,
318} 474}
319 475
320 476
477/**
478 * Remove expired entries from @a rc
479 *
480 * @param rc entry in resolver cache
481 * @return #GNUNET_YES if @a rc was completely expired
482 * #GNUNET_NO if some entries are left
483 */
484static int
485remove_expired (struct ResolveCache *rc)
486{
487 struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
488 struct RecordListEntry *n;
489
490 for (struct RecordListEntry *pos = rc->records_head;
491 NULL != pos;
492 pos = n)
493 {
494 n = pos->next;
495 if (now.abs_value_us > pos->record->expiration_time.abs_value_us)
496 GNUNET_CONTAINER_DLL_remove (rc->records_head,
497 rc->records_tail,
498 pos);
499 }
500 if (NULL == rc->records_head)
501 {
502 free_cache_entry (rc);
503 return GNUNET_YES;
504 }
505 return GNUNET_NO;
506}
507
508
509/**
510 * Process DNS request for @a hostname with request ID @a request_id
511 * from @a client demanding records of type @a record_type.
512 *
513 * @param hostname DNS name to resolve
514 * @param record_type desired record type
515 * @param request_id client's request ID
516 * @param client who should get the result?
517 */
518static void
519process_get (const char *hostname,
520 uint16_t record_type,
521 uint16_t request_id,
522 struct GNUNET_SERVICE_Client *client);
523
524
525/**
526 * Get an IP address as a string (works for both IPv4 and IPv6). Note
527 * that the resolution happens asynchronously and that the first call
528 * may not immediately result in the FQN (but instead in a
529 * human-readable IP address).
530 *
531 * @param hostname what hostname was to be resolved
532 * @param record_type what type of record was requested
533 * @param request_id unique identification of the client's request
534 * @param client handle to the client making the request (for sending the reply)
535 */
536static int
537try_cache (const char *hostname,
538 uint16_t record_type,
539 uint16_t request_id,
540 struct GNUNET_SERVICE_Client *client)
541{
542 struct ResolveCache *pos;
543 struct ResolveCache *next;
544 int found;
545
546 next = cache_head;
547 for (pos = next; NULL != pos; pos = next)
548 {
549 next = pos->next;
550 if (GNUNET_YES == remove_expired (pos))
551 continue;
552 if (0 == strcmp (pos->hostname,
553 hostname))
554 break;
555 }
556 if (NULL == pos)
557 {
558 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
559 "No cache entry for '%s'\n",
560 hostname);
561 return GNUNET_NO;
562 }
563 if (cache_head != pos)
564 {
565 /* move result to head to achieve LRU for cache eviction */
566 GNUNET_CONTAINER_DLL_remove (cache_head,
567 cache_tail,
568 pos);
569 GNUNET_CONTAINER_DLL_insert (cache_head,
570 cache_tail,
571 pos);
572 }
573 found = GNUNET_NO;
574 for (struct RecordListEntry *rle = pos->records_head;
575 NULL != rle;
576 rle = rle->next)
577 {
578 const struct GNUNET_DNSPARSER_Record *record = rle->record;
579
580 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
581 "Found cache entry for '%s', record type '%u'\n",
582 hostname,
583 record_type);
584 if ( (GNUNET_DNSPARSER_TYPE_CNAME == record->type) &&
585 (GNUNET_DNSPARSER_TYPE_CNAME != record_type) &&
586 (GNUNET_NO == found) )
587 {
588 const char *hostname = record->data.hostname;
589
590 process_get (hostname,
591 record_type,
592 request_id,
593 client);
594 return GNUNET_YES; /* counts as a cache "hit" */
595 }
596 found |= send_reply (rle->record,
597 record_type,
598 request_id,
599 client);
600 }
601 if (GNUNET_NO == found)
602 return GNUNET_NO; /* had records, but none matched! */
603 send_end_msg (request_id,
604 client);
605 return GNUNET_YES;
606}
607
608
609/**
610 * We got a result from DNS. Add it to the cache and
611 * see if we can make our client happy...
612 *
613 * @param cls the `struct ActiveLookup`
614 * @param dns the DNS response
615 * @param dns_len number of bytes in @a dns
616 */
321static void 617static void
322handle_resolve_result (void *cls, 618handle_resolve_result (void *cls,
323 const struct GNUNET_TUN_DnsHeader *dns, 619 const struct GNUNET_TUN_DnsHeader *dns,
324 size_t dns_len) 620 size_t dns_len)
325{ 621{
326 struct ResolveCache *cache = cls; 622 struct ActiveLookup *al = cls;
327 struct GNUNET_DNSPARSER_Packet *parsed; 623 struct GNUNET_DNSPARSER_Packet *parsed;
328 uint16_t request_id = *cache->request_id; 624 struct ResolveCache *rc;
329 struct GNUNET_SERVICE_Client *client = cache->client;
330 625
331 parsed = GNUNET_DNSPARSER_parse ((const char *)dns, 626 parsed = GNUNET_DNSPARSER_parse ((const char *)dns,
332 dns_len); 627 dns_len);
333 if (NULL == parsed) 628 if (NULL == parsed)
334 { 629 {
335 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 630 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
336 "Failed to parse DNS reply (request ID %u\n", 631 "Failed to parse DNS reply (hostname %s, request ID %u)\n",
337 request_id); 632 al->hostname,
633 al->dns_id);
338 return; 634 return;
339 } 635 }
340 if (request_id != ntohs (parsed->id)) 636 if (al->dns_id != ntohs (parsed->id))
341 { 637 {
342 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 638 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
343 "Request ID in DNS reply does not match\n"); 639 "Request ID in DNS reply does not match\n");
640 GNUNET_DNSPARSER_free_packet (parsed);
344 return; 641 return;
345 } 642 }
346 else if (0 == parsed->num_answers) 643 if (0 == parsed->num_answers)
347 { 644 {
348 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 645 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
349 "DNS reply (request ID %u) contains no answers\n", 646 "DNS reply (hostname %s, request ID %u) contains no answers\n",
350 request_id); 647 al->hostname,
351 GNUNET_CONTAINER_DLL_remove (cache_head, 648 al->request_id);
352 cache_tail, 649 GNUNET_DNSPARSER_free_packet (parsed);
353 cache); 650 send_end_msg (al->request_id,
354 free_cache_entry (cache); 651 al->client);
355 cache = NULL; 652 free_active_lookup (al);
356 } 653 return;
357 else
358 {
359 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
360 "Got reply for request ID %u\n",
361 request_id);
362 for (unsigned int i = 0; i != parsed->num_answers; i++)
363 {
364 struct Record *cache_entry = GNUNET_new (struct Record);
365 struct GNUNET_DNSPARSER_Record *record = &parsed->answers[i];
366 cache_entry->record = GNUNET_DNSPARSER_duplicate_record (record);
367 GNUNET_CONTAINER_DLL_insert (cache->records_head,
368 cache->records_tail,
369 cache_entry);
370 send_reply (cache_entry->record,
371 request_id,
372 cache->client);
373 }
374 GNUNET_free_non_null (cache->request_id);
375 cache->request_id = NULL;
376 } 654 }
377 send_end_msg (request_id, 655 /* LRU-based cache eviction: we remove from tail */
378 client); 656 while (cache_size > MAX_CACHE)
379 if (NULL != cache) 657 free_cache_entry (cache_tail);
380 cache->client = NULL; 658
381 if (NULL != cache) 659 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
660 "Got reply for hostname %s and request ID %u\n",
661 al->hostname,
662 al->request_id);
663 /* add to cache */
664 for (unsigned int i = 0; i != parsed->num_answers; i++)
382 { 665 {
383 if (NULL != cache->timeout_task) 666 struct GNUNET_DNSPARSER_Record *record = &parsed->answers[i];
384 { 667 struct RecordListEntry *rle;
385 GNUNET_SCHEDULER_cancel (cache->timeout_task); 668
386 cache->timeout_task = NULL; 669 for (rc = cache_head; NULL != rc; rc = rc->next)
387 } 670 if (0 == strcasecmp (rc->hostname,
388 if (NULL != cache->resolve_handle) 671 record->name))
672 break;
673 if (NULL == rc)
389 { 674 {
390 GNUNET_DNSSTUB_resolve_cancel (cache->resolve_handle); 675 rc = GNUNET_new (struct ResolveCache);
391 cache->resolve_handle = NULL; 676 rc->hostname = GNUNET_strdup (record->name);
677 GNUNET_CONTAINER_DLL_insert (cache_head,
678 cache_tail,
679 rc);
680 cache_size++;
392 } 681 }
682 /* TODO: ought to check first if we have this exact record
683 already in the cache! */
684 rle = GNUNET_new (struct RecordListEntry);
685 rle->record = GNUNET_DNSPARSER_duplicate_record (record);
686 GNUNET_CONTAINER_DLL_insert (rc->records_head,
687 rc->records_tail,
688 rle);
393 } 689 }
690
691 /* resume by trying again from cache */
692 if (GNUNET_NO ==
693 try_cache (al->hostname,
694 al->record_type,
695 al->request_id,
696 al->client))
697 /* cache failed, tell client we could not get an answer */
698 send_end_msg (al->request_id,
699 al->client);
700 free_active_lookup (al);
394 GNUNET_DNSPARSER_free_packet (parsed); 701 GNUNET_DNSPARSER_free_packet (parsed);
395} 702}
396 703
397 704
705/**
706 * We encountered a timeout trying to perform a
707 * DNS lookup.
708 *
709 * @param cls a `struct ActiveLookup`
710 */
398static void 711static void
399handle_resolve_timeout (void *cls) 712handle_resolve_timeout (void *cls)
400{ 713{
401 struct ResolveCache *cache = cls; 714 struct ActiveLookup *al = cls;
402 715
403 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 716 al->timeout_task = NULL;
404 "timeout!\n"); 717 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
405 if (NULL != cache->resolve_handle) 718 "DNS lookup timeout!\n");
406 { 719 send_end_msg (al->request_id,
407 GNUNET_DNSSTUB_resolve_cancel (cache->resolve_handle); 720 al->client);
408 cache->resolve_handle = NULL; 721 free_active_lookup (al);
409 }
410 GNUNET_CONTAINER_DLL_remove (cache_head,
411 cache_tail,
412 cache);
413 free_cache_entry (cache);
414} 722}
415 723
416 724
725/**
726 * Initiate an active lookup, then cache the result and
727 * try to then complete the resolution.
728 *
729 * @param hostname DNS name to resolve
730 * @param record_type record type to locate
731 * @param request_id client request ID
732 * @param client handle to the client
733 */
417static int 734static int
418resolve_and_cache (const char* hostname, 735resolve_and_cache (const char* hostname,
419 uint16_t record_type, 736 uint16_t record_type,
@@ -424,10 +741,11 @@ resolve_and_cache (const char* hostname,
424 size_t packet_size; 741 size_t packet_size;
425 struct GNUNET_DNSPARSER_Query query; 742 struct GNUNET_DNSPARSER_Query query;
426 struct GNUNET_DNSPARSER_Packet packet; 743 struct GNUNET_DNSPARSER_Packet packet;
427 struct ResolveCache *cache; 744 struct ActiveLookup *al;
428 struct GNUNET_TIME_Relative timeout = 745 uint16_t dns_id;
429 GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5);
430 746
747 dns_id =(uint16_t) GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
748 UINT16_MAX);
431 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 749 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
432 "resolve_and_cache\n"); 750 "resolve_and_cache\n");
433 query.name = (char *)hostname; 751 query.name = (char *)hostname;
@@ -438,9 +756,9 @@ resolve_and_cache (const char* hostname,
438 sizeof (packet)); 756 sizeof (packet));
439 packet.num_queries = 1; 757 packet.num_queries = 1;
440 packet.queries = &query; 758 packet.queries = &query;
441 packet.id = htons (request_id); 759 packet.id = htons (dns_id);
442 packet.flags.recursion_desired = 1; 760 packet.flags.recursion_desired = 1;
443 if (GNUNET_OK != 761 if (GNUNET_OK !=
444 GNUNET_DNSPARSER_pack (&packet, 762 GNUNET_DNSPARSER_pack (&packet,
445 UINT16_MAX, 763 UINT16_MAX,
446 &packet_buf, 764 &packet_buf,
@@ -450,136 +768,67 @@ resolve_and_cache (const char* hostname,
450 "Failed to pack query for hostname `%s'\n", 768 "Failed to pack query for hostname `%s'\n",
451 hostname); 769 hostname);
452 return GNUNET_SYSERR; 770 return GNUNET_SYSERR;
453 771
454 } 772 }
455 cache = GNUNET_malloc (sizeof (struct ResolveCache)); 773 al = GNUNET_new (struct ActiveLookup);
456 cache->record_type = record_type; 774 al->hostname = GNUNET_strdup (hostname);
457 cache->request_id = GNUNET_memdup (&request_id, sizeof (request_id)); 775 al->record_type = record_type;
458 cache->client = client; 776 al->request_id = request_id;
459 cache->timeout_task = GNUNET_SCHEDULER_add_delayed (timeout, 777 al->dns_id = dns_id;
460 &handle_resolve_timeout, 778 al->client = client;
461 cache); 779 al->timeout_task = GNUNET_SCHEDULER_add_delayed (DNS_TIMEOUT,
462 cache->resolve_handle = 780 &handle_resolve_timeout,
781 al);
782 al->resolve_handle =
463 GNUNET_DNSSTUB_resolve (dnsstub_ctx, 783 GNUNET_DNSSTUB_resolve (dnsstub_ctx,
464 packet_buf, 784 packet_buf,
465 packet_size, 785 packet_size,
466 &handle_resolve_result, 786 &handle_resolve_result,
467 cache); 787 al);
468 GNUNET_CONTAINER_DLL_insert (cache_head, 788 GNUNET_free (packet_buf);
469 cache_tail, 789 GNUNET_CONTAINER_DLL_insert (lookup_head,
470 cache); 790 lookup_tail,
791 al);
471 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 792 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
472 "resolve %s, request_id = %u\n", 793 "Resolving %s, request_id = %u, dns_id = %u\n",
473 hostname, 794 hostname,
474 request_id); 795 (unsigned int) request_id,
475 GNUNET_free (packet_buf); 796 (unsigned int) dns_id);
476 return GNUNET_OK; 797 return GNUNET_OK;
477} 798}
478 799
479 800
480static const char *
481get_hostname (struct ResolveCache *cache_entry)
482{
483 if (NULL != cache_entry->records_head)
484 {
485 GNUNET_assert (NULL != cache_entry->records_head);
486 GNUNET_assert (NULL != cache_entry->records_head->record);
487 GNUNET_assert (NULL != cache_entry->records_head->record->name);
488 return cache_entry->records_head->record->name;
489 }
490 return NULL;
491}
492
493
494static const uint16_t *
495get_record_type (struct ResolveCache *cache_entry)
496{
497 if (NULL != cache_entry->records_head)
498 return &cache_entry->record_type;
499 return NULL;
500}
501
502
503static const struct GNUNET_TIME_Absolute *
504get_expiration_time (struct ResolveCache *cache_entry)
505{
506 if (NULL != cache_entry->records_head)
507 return &cache_entry->records_head->record->expiration_time;
508 return NULL;
509}
510
511
512static int
513remove_if_expired (struct ResolveCache *cache_entry)
514{
515 struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
516
517 if ( (NULL != cache_entry->records_head) &&
518 (now.abs_value_us > get_expiration_time (cache_entry)->abs_value_us) )
519 {
520 GNUNET_CONTAINER_DLL_remove (cache_head,
521 cache_tail,
522 cache_entry);
523 free_cache_entry (cache_entry);
524 return GNUNET_YES;
525 }
526 return GNUNET_NO;
527}
528
529
530/** 801/**
531 * Get an IP address as a string (works for both IPv4 and IPv6). Note 802 * Process DNS request for @a hostname with request ID @a request_id
532 * that the resolution happens asynchronously and that the first call 803 * from @a client demanding records of type @a record_type.
533 * may not immediately result in the FQN (but instead in a
534 * human-readable IP address).
535 * 804 *
536 * @param client handle to the client making the request (for sending the reply) 805 * @param hostname DNS name to resolve
537 * @param af AF_INET or AF_INET6 806 * @param record_type desired record type
538 * @param ip `struct in_addr` or `struct in6_addr` 807 * @param request_id client's request ID
808 * @param client who should get the result?
539 */ 809 */
540static int 810static void
541try_cache (const char *hostname, 811process_get (const char *hostname,
542 uint16_t record_type, 812 uint16_t record_type,
543 uint16_t request_id, 813 uint16_t request_id,
544 struct GNUNET_SERVICE_Client *client) 814 struct GNUNET_SERVICE_Client *client)
545{ 815{
546 struct ResolveCache *pos; 816 if (GNUNET_NO ==
547 struct ResolveCache *next; 817 try_cache (hostname,
548 818 record_type,
549 next = cache_head; 819 request_id,
550 while ( (NULL != (pos = next)) && 820 client))
551 ( (NULL == pos->records_head) ||
552 (0 != strcmp (get_hostname (pos), hostname)) ||
553 (*get_record_type (pos) != record_type) ) )
554 { 821 {
555 next = pos->next; 822 if (GNUNET_OK !=
556 remove_if_expired (pos); 823 resolve_and_cache (hostname,
557 } 824 record_type,
558 if (NULL != pos) 825 request_id,
559 { 826 client))
560 if (GNUNET_NO == remove_if_expired (pos))
561 { 827 {
562 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
563 "found cache entry for '%s', record type '%u'\n",
564 hostname,
565 record_type);
566 struct Record *cache_pos = pos->records_head;
567 while (NULL != cache_pos)
568 {
569 send_reply (cache_pos->record,
570 request_id,
571 client);
572 cache_pos = cache_pos->next;
573 }
574 send_end_msg (request_id, 828 send_end_msg (request_id,
575 client); 829 client);
576 return GNUNET_YES;
577 } 830 }
578 } 831 }
579 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
580 "no cache entry for '%s'\n",
581 hostname);
582 return GNUNET_NO;
583} 832}
584 833
585 834
@@ -639,23 +888,6 @@ check_get (void *cls,
639} 888}
640 889
641 890
642static void
643process_get (const char *hostname,
644 uint16_t record_type,
645 uint16_t request_id,
646 struct GNUNET_SERVICE_Client *client)
647{
648 if (GNUNET_NO == try_cache (hostname, record_type, request_id, client))
649 {
650 int result = resolve_and_cache (hostname,
651 record_type,
652 request_id,
653 client);
654 GNUNET_assert (GNUNET_OK == result);
655 }
656}
657
658
659/** 891/**
660 * Handle GET-message. 892 * Handle GET-message.
661 * 893 *
@@ -670,7 +902,7 @@ handle_get (void *cls,
670 int direction; 902 int direction;
671 int af; 903 int af;
672 uint16_t request_id; 904 uint16_t request_id;
673 const char *hostname; 905 char *hostname;
674 906
675 direction = ntohl (msg->direction); 907 direction = ntohl (msg->direction);
676 af = ntohl (msg->af); 908 af = ntohl (msg->af);
@@ -683,17 +915,26 @@ handle_get (void *cls,
683 { 915 {
684 case AF_UNSPEC: 916 case AF_UNSPEC:
685 { 917 {
686 process_get (hostname, GNUNET_DNSPARSER_TYPE_ALL, request_id, client); 918 process_get (hostname,
919 GNUNET_DNSPARSER_TYPE_ALL,
920 request_id,
921 client);
687 break; 922 break;
688 } 923 }
689 case AF_INET: 924 case AF_INET:
690 { 925 {
691 process_get (hostname, GNUNET_DNSPARSER_TYPE_A, request_id, client); 926 process_get (hostname,
927 GNUNET_DNSPARSER_TYPE_A,
928 request_id,
929 client);
692 break; 930 break;
693 } 931 }
694 case AF_INET6: 932 case AF_INET6:
695 { 933 {
696 process_get (hostname, GNUNET_DNSPARSER_TYPE_AAAA, request_id, client); 934 process_get (hostname,
935 GNUNET_DNSPARSER_TYPE_AAAA,
936 request_id,
937 client);
697 break; 938 break;
698 } 939 }
699 default: 940 default:
@@ -708,50 +949,63 @@ handle_get (void *cls,
708 else 949 else
709 { 950 {
710 /* hostname from IP */ 951 /* hostname from IP */
711 hostname = make_reverse_hostname (&msg[1], af); 952 hostname = make_reverse_hostname (&msg[1],
712 process_get (hostname, GNUNET_DNSPARSER_TYPE_PTR, request_id, client); 953 af);
954 process_get (hostname,
955 GNUNET_DNSPARSER_TYPE_PTR,
956 request_id,
957 client);
713 } 958 }
714 GNUNET_free_non_null ((char *)hostname); 959 GNUNET_free_non_null (hostname);
715 GNUNET_SERVICE_client_continue (client); 960 GNUNET_SERVICE_client_continue (client);
716} 961}
717 962
718 963
719static void 964/**
965 * Service is shutting down, clean up.
966 *
967 * @param cls NULL, unused
968 */
969static void
720shutdown_task (void *cls) 970shutdown_task (void *cls)
721{ 971{
722 (void) cls; 972 (void) cls;
723 struct ResolveCache *pos;
724 973
725 while (NULL != (pos = cache_head)) 974 while (NULL != lookup_head)
726 { 975 free_active_lookup (lookup_head);
727 GNUNET_CONTAINER_DLL_remove (cache_head, 976 while (NULL != cache_head)
728 cache_tail, 977 free_cache_entry (cache_head);
729 pos);
730 free_cache_entry (pos);
731 }
732 GNUNET_DNSSTUB_stop (dnsstub_ctx); 978 GNUNET_DNSSTUB_stop (dnsstub_ctx);
733} 979}
734 980
735 981
982/**
983 * Service is starting, initialize everything.
984 *
985 * @param cls NULL, unused
986 * @param cfg our configuration
987 * @param sh service handle
988 */
736static void 989static void
737init_cb (void *cls, 990init_cb (void *cls,
738 const struct GNUNET_CONFIGURATION_Handle *cfg, 991 const struct GNUNET_CONFIGURATION_Handle *cfg,
739 struct GNUNET_SERVICE_Handle *sh) 992 struct GNUNET_SERVICE_Handle *sh)
740{ 993{
994 char **dns_servers;
995 int num_dns_servers;
996
741 (void) cfg; 997 (void) cfg;
742 (void) sh; 998 (void) sh;
743
744 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 999 GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
745 cls); 1000 cls);
746 dnsstub_ctx = GNUNET_DNSSTUB_start (128); 1001 dnsstub_ctx = GNUNET_DNSSTUB_start (128);
747 char **dns_servers; 1002 num_dns_servers = lookup_dns_servers (&dns_servers);
748 ssize_t num_dns_servers = lookup_dns_servers (&dns_servers); 1003 if (0 >= num_dns_servers)
749 if (0 == num_dns_servers)
750 { 1004 {
751 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1005 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
752 "no DNS server available. DNS resolution will not be possible.\n"); 1006 _("No DNS server available. DNS resolution will not be possible.\n"));
753 } 1007 }
754 for (int i = 0; i != num_dns_servers; i++) 1008 for (int i = 0; i < num_dns_servers; i++)
755 { 1009 {
756 int result = GNUNET_DNSSTUB_add_dns_ip (dnsstub_ctx, dns_servers[i]); 1010 int result = GNUNET_DNSSTUB_add_dns_ip (dnsstub_ctx, dns_servers[i]);
757 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1011 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -796,18 +1050,19 @@ disconnect_cb (void *cls,
796 struct GNUNET_SERVICE_Client *c, 1050 struct GNUNET_SERVICE_Client *c,
797 void *internal_cls) 1051 void *internal_cls)
798{ 1052{
1053 struct ActiveLookup *n;
799 (void) cls; 1054 (void) cls;
800 struct ResolveCache *pos = cache_head;
801 1055
802 while (NULL != pos) 1056 GNUNET_assert (c == internal_cls);
1057 n = lookup_head;
1058 for (struct ActiveLookup *al = n;
1059 NULL != al;
1060 al = n)
803 { 1061 {
804 if (pos->client == c) 1062 n = al->next;
805 { 1063 if (al->client == c)
806 pos->client = NULL; 1064 free_active_lookup (al);
807 }
808 pos = pos->next;
809 } 1065 }
810 GNUNET_assert (c == internal_cls);
811} 1066}
812 1067
813 1068