merge
[oweals/gnunet.git] / src / util / gnunet-service-resolver.c
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
19 /**
20  * @file util/gnunet-service-resolver.c
21  * @brief code to do DNS resolution
22  * @author Christian Grothoff
23  */
24 #include "platform.h"
25 #include "gnunet_util_lib.h"
26 #include "gnunet_protocols.h"
27 #include "gnunet_statistics_service.h"
28 #include "resolver.h"
29
30
31 struct Record
32 {
33   struct Record *next;
34
35   struct Record *prev;
36
37   struct GNUNET_DNSPARSER_Record *record;
38 };
39
40 /**
41  * A cached DNS lookup result.
42  */
43 struct ResolveCache
44 {
45   /**
46    * This is a doubly linked list.
47    */
48   struct ResolveCache *next;
49
50   /**
51    * This is a doubly linked list.
52    */
53   struct ResolveCache *prev;
54
55   /**
56    * type of queried DNS record
57    */
58   uint16_t record_type;
59
60   /**
61    * a pointer to the request_id if a query for this hostname/record_type
62    * is currently pending, NULL otherwise.
63    */
64   int16_t *request_id;
65
66   /**
67    * The client that queried the records contained in this cache entry.
68    */
69   struct GNUNET_SERVICE_Client *client;
70
71   /**
72    * head of a double linked list containing the lookup results
73    */
74   struct Record *records_head;
75
76   /**
77    * tail of a double linked list containing the lookup results
78    */
79   struct Record *records_tail;
80
81   /**
82    * handle for cancelling a request
83    */
84   struct GNUNET_DNSSTUB_RequestSocket *resolve_handle;
85
86   /**
87    * handle for the resolution timeout task
88    */
89   struct GNUNET_SCHEDULER_Task *timeout_task;
90
91 };
92
93
94 /**
95  * Start of the linked list of cached DNS lookup results.
96  */
97 static struct ResolveCache *cache_head;
98
99 /**
100  * Tail of the linked list of cached DNS lookup results.
101  */
102 static struct ResolveCache *cache_tail;
103
104 /**
105  * context of dnsstub library
106  */
107 static struct GNUNET_DNSSTUB_Context *dnsstub_ctx;
108
109
110 void free_cache_entry (struct ResolveCache *entry)
111 {
112   struct Record *pos;
113   struct Record *next;
114  
115   next = entry->records_head;
116   while (NULL != (pos = next))
117   {
118     next = pos->next;
119     GNUNET_CONTAINER_DLL_remove (entry->records_head,
120                                  entry->records_tail,
121                                  pos);
122     if (NULL != pos->record)
123     {
124       GNUNET_DNSPARSER_free_record (pos->record);
125       GNUNET_free (pos->record);
126     }
127     GNUNET_free (pos);
128   }
129   if (NULL != entry->resolve_handle)
130   {
131     GNUNET_DNSSTUB_resolve_cancel (entry->resolve_handle);
132     entry->resolve_handle = NULL;
133   }
134   if (NULL != entry->timeout_task)
135   {
136     GNUNET_SCHEDULER_cancel (entry->timeout_task);
137     entry->timeout_task = NULL;
138   }
139   GNUNET_free_non_null (entry->request_id);
140   GNUNET_free (entry);
141 }
142
143
144 static char*
145 extract_dns_server (const char* line, size_t line_len)
146 {
147   if (0 == strncmp (line, "nameserver ", 11))
148     return GNUNET_strndup (line + 11, line_len - 11);
149   return NULL;
150 }
151
152  
153 /**
154  * reads the list of nameservers from /etc/resolve.conf
155  *
156  * @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
158  */
159 static ssize_t
160 lookup_dns_servers (char ***server_addrs)
161 {
162   struct GNUNET_DISK_FileHandle *fh;
163   char buf[2048];
164   ssize_t bytes_read;
165   size_t read_offset = 0;
166   unsigned int num_dns_servers = 0;
167     
168   fh = GNUNET_DISK_file_open ("/etc/resolv.conf",
169                               GNUNET_DISK_OPEN_READ,
170                               GNUNET_DISK_PERM_NONE);
171   if (NULL == fh)
172   {
173     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
174                 "Could not open /etc/resolv.conf. "
175                 "DNS resolution will not be possible.\n");
176     return -1;
177   }
178   bytes_read = GNUNET_DISK_file_read (fh,
179                                       buf,
180                                       sizeof (buf));
181   *server_addrs = NULL;
182   while (read_offset < bytes_read)
183   {
184     char *newline;
185     size_t line_len;
186     char *dns_server;
187     
188     newline = strchr (buf + read_offset, '\n');
189     if (NULL == newline)
190     {
191       break;
192     }
193     line_len = newline - buf - read_offset;
194     dns_server = extract_dns_server (buf + read_offset, line_len);
195     if (NULL != dns_server)
196     {
197       GNUNET_array_append (*server_addrs,
198                            num_dns_servers,
199                            dns_server);
200     }
201     read_offset += line_len + 1;
202   }
203   GNUNET_DISK_file_close (fh);
204   return num_dns_servers;
205 }
206
207
208 static char *
209 make_reverse_hostname (const void *ip, int af)
210 {
211   char *buf = GNUNET_new_array (80, char);
212   int pos = 0;
213   if (AF_INET == af)
214   {
215     struct in_addr *addr = (struct in_addr *)ip;
216     uint32_t ip_int = addr->s_addr;
217     for (int i = 3; i >= 0; i--)
218     {
219       int n = GNUNET_snprintf (buf + pos,
220                                80 - pos,
221                                "%u.",
222                                ((uint8_t *)&ip_int)[i]);
223       if (n < 0)
224       {
225         GNUNET_free (buf);
226         return NULL;
227       }
228       pos += n;
229     }
230     pos += GNUNET_snprintf (buf + pos, 80 - pos, "in-addr.arpa");
231   }
232   else if (AF_INET6 == af)
233   {
234     struct in6_addr *addr = (struct in6_addr *)ip;
235     for (int i = 15; i >= 0; i--)
236     {
237       int n = GNUNET_snprintf (buf + pos, 80 - pos, "%x.", addr->s6_addr[i] & 0xf);
238       if (n < 0)
239       {
240         GNUNET_free (buf);
241         return NULL;
242       }
243       pos += n;
244       n = GNUNET_snprintf (buf + pos, 80 - pos, "%x.", addr->s6_addr[i] >> 4);
245       if (n < 0)
246       {
247         GNUNET_free (buf);
248         return NULL;
249       }
250       pos += n;
251     }
252     pos += GNUNET_snprintf (buf + pos, 80 - pos, "ip6.arpa");
253   }
254   buf[pos] = '\0';
255   return buf;
256 }
257
258
259 static void
260 send_reply (struct GNUNET_DNSPARSER_Record *record,
261             uint16_t request_id,
262             struct GNUNET_SERVICE_Client *client)
263 {
264   struct GNUNET_RESOLVER_ResponseMessage *msg;
265   struct GNUNET_MQ_Envelope *env;
266   void *payload;
267   size_t payload_len;
268
269   switch (record->type)
270   {
271     case GNUNET_DNSPARSER_TYPE_PTR:
272     {
273       char *hostname = record->data.hostname;
274       payload = hostname;
275       payload_len = strlen (hostname) + 1;
276       break;
277     }
278     case GNUNET_DNSPARSER_TYPE_A:
279     case GNUNET_DNSPARSER_TYPE_AAAA:
280     {
281       payload = record->data.raw.data;
282       payload_len = record->data.raw.data_len;
283       break;         
284     }
285     default:
286     {
287       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
288                   "Cannot handle DNS response type: unimplemented\n");
289       return;
290     }
291   }
292   env = GNUNET_MQ_msg_extra (msg,
293                              payload_len,
294                              GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE);
295   msg->id = request_id;
296   GNUNET_memcpy (&msg[1],
297                  payload,
298                  payload_len);
299   GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
300                   env);
301 }
302
303
304 static void
305 send_end_msg (uint16_t request_id,
306               struct GNUNET_SERVICE_Client *client)
307 {
308   struct GNUNET_RESOLVER_ResponseMessage *msg;
309   struct GNUNET_MQ_Envelope *env;
310
311   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
312               "Sending end message\n");
313   env = GNUNET_MQ_msg (msg,
314                        GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE);
315   msg->id = request_id;
316   GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
317                   env);
318 }
319
320
321 static void
322 handle_resolve_result (void *cls,
323                        const struct GNUNET_TUN_DnsHeader *dns,
324                        size_t dns_len)
325 {
326   struct ResolveCache *cache = cls;
327   struct GNUNET_DNSPARSER_Packet *parsed;
328   uint16_t request_id = *cache->request_id;
329   struct GNUNET_SERVICE_Client *client = cache->client;
330
331   parsed = GNUNET_DNSPARSER_parse ((const char *)dns,
332                                    dns_len);
333   if (NULL == parsed)
334   {
335     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
336                 "Failed to parse DNS reply (request ID %u\n",
337                 request_id);
338     return;
339   }
340   if (request_id != ntohs (parsed->id))
341   {
342     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
343                 "Request ID in DNS reply does not match\n");
344     return;
345   }
346   else if (0 == parsed->num_answers)
347   {
348     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
349                 "DNS reply (request ID %u) contains no answers\n",
350                 request_id);
351     GNUNET_CONTAINER_DLL_remove (cache_head,
352                                  cache_tail,
353                                  cache);
354     free_cache_entry (cache);
355     cache = NULL;
356   }
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   }
377   send_end_msg (request_id,
378                 client);
379   if (NULL != cache)
380     cache->client = NULL;
381   if (NULL != cache)
382   {
383     if (NULL != cache->timeout_task)
384     { 
385       GNUNET_SCHEDULER_cancel (cache->timeout_task);
386       cache->timeout_task = NULL;
387     }
388     if (NULL != cache->resolve_handle)
389     {
390       GNUNET_DNSSTUB_resolve_cancel (cache->resolve_handle);
391       cache->resolve_handle = NULL;
392     }
393   }
394   GNUNET_DNSPARSER_free_packet (parsed);
395 }
396
397
398 static void
399 handle_resolve_timeout (void *cls)
400 {
401   struct ResolveCache *cache = cls;
402
403   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 
404               "timeout!\n");
405   if (NULL != cache->resolve_handle)
406   {
407     GNUNET_DNSSTUB_resolve_cancel (cache->resolve_handle);
408     cache->resolve_handle = NULL;
409   }
410   GNUNET_CONTAINER_DLL_remove (cache_head,
411                                cache_tail,
412                                cache);
413   free_cache_entry (cache);
414 }
415
416
417 static int
418 resolve_and_cache (const char* hostname,
419                    uint16_t record_type,
420                    uint16_t request_id,
421                    struct GNUNET_SERVICE_Client *client)
422 {
423   char *packet_buf;
424   size_t packet_size;
425   struct GNUNET_DNSPARSER_Query query;
426   struct GNUNET_DNSPARSER_Packet packet;
427   struct ResolveCache *cache;
428   struct GNUNET_TIME_Relative timeout =
429     GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5);
430
431   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
432               "resolve_and_cache\n");
433   query.name = (char *)hostname;
434   query.type = record_type;
435   query.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET;
436   memset (&packet,
437           0,
438           sizeof (packet));
439   packet.num_queries = 1;
440   packet.queries = &query;
441   packet.id = htons (request_id);
442   packet.flags.recursion_desired = 1;
443   if (GNUNET_OK != 
444       GNUNET_DNSPARSER_pack (&packet,
445                              UINT16_MAX,
446                              &packet_buf,
447                              &packet_size))
448   {
449     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
450                 "Failed to pack query for hostname `%s'\n",
451                 hostname);
452     return GNUNET_SYSERR;
453  
454   }
455   cache = GNUNET_malloc (sizeof (struct ResolveCache));
456   cache->record_type = record_type;
457   cache->request_id = GNUNET_memdup (&request_id, sizeof (request_id));
458   cache->client = client;
459   cache->timeout_task = GNUNET_SCHEDULER_add_delayed (timeout,
460                                                       &handle_resolve_timeout,
461                                                       cache);
462   cache->resolve_handle = 
463     GNUNET_DNSSTUB_resolve (dnsstub_ctx,
464                             packet_buf,
465                             packet_size,
466                             &handle_resolve_result,
467                             cache);
468   GNUNET_CONTAINER_DLL_insert (cache_head,
469                                cache_tail,
470                                cache);
471   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
472               "resolve %s, request_id = %u\n",
473               hostname,
474               request_id);
475   GNUNET_free (packet_buf);
476   return GNUNET_OK;
477 }
478
479
480 static const char *
481 get_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
494 static const uint16_t *
495 get_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
503 static const struct GNUNET_TIME_Absolute *
504 get_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
512 static int
513 remove_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 /**
531  * Get an IP address as a string (works for both IPv4 and IPv6).  Note
532  * that the resolution happens asynchronously and that the first call
533  * may not immediately result in the FQN (but instead in a
534  * human-readable IP address).
535  *
536  * @param client handle to the client making the request (for sending the reply)
537  * @param af AF_INET or AF_INET6
538  * @param ip `struct in_addr` or `struct in6_addr`
539  */
540 static int
541 try_cache (const char *hostname,
542            uint16_t record_type,
543            uint16_t request_id,
544            struct GNUNET_SERVICE_Client *client)
545 {
546   struct ResolveCache *pos;
547   struct ResolveCache *next;
548
549   next = cache_head;
550   while ( (NULL != (pos = next)) &&
551           ( (NULL == pos->records_head) ||
552             (0 != strcmp (get_hostname (pos), hostname)) ||
553             (*get_record_type (pos) != record_type) ) )
554   {
555     next = pos->next;
556     remove_if_expired (pos);
557   }
558   if (NULL != pos)
559   {
560     if (GNUNET_NO == remove_if_expired (pos))
561     {
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,
575                     client);
576       return GNUNET_YES;
577     }
578   }
579   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
580               "no cache entry for '%s'\n",
581               hostname);
582   return GNUNET_NO;
583 }
584
585
586 /**
587  * Verify well-formedness of GET-message.
588  *
589  * @param cls closure, unused
590  * @param get the actual message
591  * @return #GNUNET_OK if @a get is well-formed
592  */
593 static int
594 check_get (void *cls,
595            const struct GNUNET_RESOLVER_GetMessage *get)
596 {
597   uint16_t size;
598   int direction;
599   int af;
600
601   (void) cls;
602   size = ntohs (get->header.size) - sizeof (*get);
603   direction = ntohl (get->direction);
604   if (GNUNET_NO == direction)
605   {
606     /* IP from hostname */
607     const char *hostname;
608
609     hostname = (const char *) &get[1];
610     if (hostname[size - 1] != '\0')
611     {
612       GNUNET_break (0);
613       return GNUNET_SYSERR;
614     }
615     return GNUNET_OK;
616   }
617   af = ntohl (get->af);
618   switch (af)
619   {
620   case AF_INET:
621     if (size != sizeof (struct in_addr))
622     {
623       GNUNET_break (0);
624       return GNUNET_SYSERR;
625     }
626     break;
627   case AF_INET6:
628     if (size != sizeof (struct in6_addr))
629     {
630       GNUNET_break (0);
631       return GNUNET_SYSERR;
632     }
633     break;
634   default:
635     GNUNET_break (0);
636     return GNUNET_SYSERR;
637   }
638   return GNUNET_OK;
639 }
640
641
642 static void
643 process_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 /**
660  * Handle GET-message.
661  *
662  * @param cls identification of the client
663  * @param msg the actual message
664  */
665 static void
666 handle_get (void *cls,
667             const struct GNUNET_RESOLVER_GetMessage *msg)
668 {
669   struct GNUNET_SERVICE_Client *client = cls;
670   int direction;
671   int af;
672   uint16_t request_id;
673   const char *hostname;
674
675   direction = ntohl (msg->direction);
676   af = ntohl (msg->af);
677   request_id = ntohs (msg->id);
678   if (GNUNET_NO == direction)
679   {
680     /* IP from hostname */
681     hostname = GNUNET_strdup ((const char *) &msg[1]);
682     switch (af)
683     {
684       case AF_UNSPEC:
685       {
686         process_get (hostname, GNUNET_DNSPARSER_TYPE_ALL, request_id, client);
687         break;
688       }
689       case AF_INET:
690       {
691         process_get (hostname, GNUNET_DNSPARSER_TYPE_A, request_id, client);
692         break;
693       }
694       case AF_INET6:
695       {
696         process_get (hostname, GNUNET_DNSPARSER_TYPE_AAAA, request_id, client);
697         break;
698       }
699       default:
700       {
701         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
702                   "got invalid af: %d\n",
703                   af);
704         GNUNET_assert (0);
705       }
706     }
707   }
708   else
709   {
710     /* hostname from IP */
711     hostname = make_reverse_hostname (&msg[1], af); 
712     process_get (hostname, GNUNET_DNSPARSER_TYPE_PTR, request_id, client);
713   }
714   GNUNET_free_non_null ((char *)hostname);
715   GNUNET_SERVICE_client_continue (client);
716 }
717
718
719 static void 
720 shutdown_task (void *cls)
721 {
722   (void) cls;
723   struct ResolveCache *pos;
724
725   while (NULL != (pos = cache_head))
726   {
727     GNUNET_CONTAINER_DLL_remove (cache_head,
728                                  cache_tail,
729                                  pos);
730     free_cache_entry (pos);
731   }
732   GNUNET_DNSSTUB_stop (dnsstub_ctx);
733 }
734
735
736 static void
737 init_cb (void *cls,
738          const struct GNUNET_CONFIGURATION_Handle *cfg,
739          struct GNUNET_SERVICE_Handle *sh)
740 {
741   (void) cfg;
742   (void) sh;
743
744   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
745                                  cls);
746   dnsstub_ctx = GNUNET_DNSSTUB_start (128);
747   char **dns_servers;
748   ssize_t num_dns_servers = lookup_dns_servers (&dns_servers);
749   if (0 == num_dns_servers)
750   {
751     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
752                 "no DNS server available. DNS resolution will not be possible.\n");
753   }
754   for (int i = 0; i != num_dns_servers; i++)
755   {
756     int result = GNUNET_DNSSTUB_add_dns_ip (dnsstub_ctx, dns_servers[i]);
757     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
758                 "Adding DNS server '%s': %s\n",
759                 dns_servers[i],
760                 GNUNET_OK == result ? "success" : "failure");
761     GNUNET_free (dns_servers[i]);
762   }
763   GNUNET_free_non_null (dns_servers);
764 }
765
766
767 /**
768  * Callback called when a client connects to the service.
769  *
770  * @param cls closure for the service, unused
771  * @param c the new client that connected to the service
772  * @param mq the message queue used to send messages to the client
773  * @return @a c
774  */
775 static void *
776 connect_cb (void *cls,
777             struct GNUNET_SERVICE_Client *c,
778             struct GNUNET_MQ_Handle *mq)
779 {
780   (void) cls;
781   (void) mq;
782
783   return c;
784 }
785
786
787 /**
788  * Callback called when a client disconnected from the service
789  *
790  * @param cls closure for the service
791  * @param c the client that disconnected
792  * @param internal_cls should be equal to @a c
793  */
794 static void
795 disconnect_cb (void *cls,
796                struct GNUNET_SERVICE_Client *c,
797                void *internal_cls)
798 {
799   (void) cls;
800   struct ResolveCache *pos = cache_head; 
801
802   while (NULL != pos)
803   {
804     if (pos->client == c)
805     {
806       pos->client = NULL;
807     }
808     pos = pos->next;
809   }
810   GNUNET_assert (c == internal_cls);
811 }
812
813
814 /**
815  * Define "main" method using service macro.
816  */
817 GNUNET_SERVICE_MAIN
818 ("resolver",
819  GNUNET_SERVICE_OPTION_NONE,
820  &init_cb,
821  &connect_cb,
822  &disconnect_cb,
823  NULL,
824  GNUNET_MQ_hd_var_size (get,
825                         GNUNET_MESSAGE_TYPE_RESOLVER_REQUEST,
826                         struct GNUNET_RESOLVER_GetMessage,
827                         NULL),
828  GNUNET_MQ_handler_end ());
829
830
831 #if defined(LINUX) && defined(__GLIBC__)
832 #include <malloc.h>
833
834 /**
835  * MINIMIZE heap size (way below 128k) since this process doesn't need much.
836  */
837 void __attribute__ ((constructor))
838 GNUNET_RESOLVER_memory_init ()
839 {
840   mallopt (M_TRIM_THRESHOLD, 4 * 1024);
841   mallopt (M_TOP_PAD, 1 * 1024);
842   malloc_trim (0);
843 }
844 #endif
845
846
847 /* end of gnunet-service-resolver.c */