added code to update hostlist statistics
[oweals/gnunet.git] / src / hostlist / hostlist-client.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 2003, 2004, 2005, 2006, 2009, 2010 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      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      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file hostlist/hostlist-client.c
23  * @brief hostlist support.  Downloads HELLOs via HTTP.
24  * @author Christian Grothoff
25  * @author Matthias Wachs
26  */
27
28 #include "platform.h"
29 #include "hostlist-client.h"
30 #include "gnunet_core_service.h"
31 #include "gnunet_hello_lib.h"
32 #include "gnunet_statistics_service.h"
33 #include "gnunet_transport_service.h"
34 #include "gnunet-daemon-hostlist.h"
35 #include <curl/curl.h>
36 #include "gnunet_common.h"
37 #include "gnunet_bio_lib.h"
38
39 #define DEBUG_HOSTLIST_CLIENT GNUNET_YES
40
41 #define MAX_URL_LEN 1000
42
43 /**
44  * Number of connections that we must have to NOT download
45  * hostlists anymore.
46  */
47 #define MIN_CONNECTIONS 4
48
49 /**
50  * A single hostlist obtained by hostlist advertisements
51  */
52 struct Hostlist
53 {
54   /**
55    * previous entry, used to manage entries in a double linked list
56    */
57   struct Hostlist * prev;
58
59   /**
60    * next entry, used to manage entries in a double linked list
61    */
62   struct Hostlist * next;
63
64   /**
65    * URI where hostlist can be obtained
66    */
67   const char *hostlist_uri;
68
69   /**
70    * Value describing the quality of the hostlist, the bigger the better but (should) never < 0
71    * used for deciding which hostlist is replaced if MAX_NUMBER_HOSTLISTS in data structure is reached
72    * intial value = HOSTLIST_INITIAL
73    * increased every successful download by HOSTLIST_SUCCESSFULL_DOWNLOAD
74    * increased every successful download by number of obtained HELLO messages
75    * decreased every failed download by HOSTLIST_SUCCESSFULL_DOWNLOAD
76    */
77   uint64_t quality;
78
79   /**
80    * Time the hostlist advertisement was recieved and the entry was created
81    */
82   struct GNUNET_TIME_Absolute time_creation;
83
84   /**
85    * Last time the hostlist was obtained
86    */
87   struct GNUNET_TIME_Absolute time_last_usage;
88
89   /**
90    * Number of HELLO messages obtained during last download
91    */
92   uint32_t hello_count;
93
94   /**
95    * Number of times the hostlist was successfully obtained
96    */
97   uint32_t times_used;
98
99 };
100
101
102 /**
103  * Our configuration.
104  */
105 static const struct GNUNET_CONFIGURATION_Handle *cfg;
106
107 /**
108  * Our scheduler.
109  */
110 static struct GNUNET_SCHEDULER_Handle *sched;
111
112 /**
113  * Statistics handle.
114  */
115 struct GNUNET_STATISTICS_Handle *stats; 
116
117 /**
118  * Transport handle.
119  */
120 struct GNUNET_TRANSPORT_Handle *transport;
121                        
122 /**
123  * Proxy that we are using (can be NULL).
124  */
125 static char *proxy;
126
127 /**
128  * Buffer for data downloaded via HTTP.
129  */
130 static char download_buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
131
132 /**
133  * Number of bytes valid in 'download_buffer'.
134  */
135 static size_t download_pos;
136
137 /**
138  * Current URL that we are using.
139  */
140 static char *current_url;
141
142 /**
143  * Current CURL handle.
144  */
145 static CURL *curl;
146
147 /**
148  * Current multi-CURL handle.
149  */
150 static CURLM *multi;
151
152 /**
153  * ID of the current task scheduled.
154  */
155 static GNUNET_SCHEDULER_TaskIdentifier current_task;
156
157 /**
158  * ID of the current hostlist saving task scheduled.
159  */
160 static GNUNET_SCHEDULER_TaskIdentifier saving_task;
161
162 /**
163  * Amount of time we wait between hostlist downloads.
164  */
165 static struct GNUNET_TIME_Relative hostlist_delay;
166
167 /**
168  * Set to GNUNET_YES if the current URL had some problems.
169  */ 
170 static int bogus_url;
171
172 /**
173  * Number of active connections (according to core service).
174  */
175 static unsigned int connection_count;
176
177 /**
178  * At what time MUST the current hostlist request be done?
179  */
180 static struct GNUNET_TIME_Absolute end_time;
181
182 /**
183  * Head of the linked list used to store hostlists
184  */
185 static struct Hostlist * linked_list_head;
186
187 /**
188  *  Tail of the linked list used to store hostlists
189  */
190 static struct Hostlist * linked_list_tail;
191
192 /**
193  *  Current hostlist used for downloading
194  */
195 static struct Hostlist * current_hostlist;
196
197
198 /*
199  *  Size of the linke list  used to store hostlists
200  */
201 static unsigned int linked_list_size;
202
203 /**
204  * Value saying if preconfigured  is used
205  */
206 static unsigned int use_preconfigured_list;
207
208 /**
209  * Set if we are allowed to learn new hostlists and use them
210  */
211 static int learning;
212
213 /**
214  * Value saying how many valid HELLO messages were obtained during download
215  */
216 static unsigned int hellos_obtained;
217
218 /**
219  * Value saying how many valid HELLO messages were obtained during download
220  */
221 static unsigned int download_successful;
222
223 /**
224  * Process downloaded bits by calling callback on each HELLO.
225  *
226  * @param ptr buffer with downloaded data
227  * @param size size of a record
228  * @param nmemb number of records downloaded
229  * @param ctx unused
230  * @return number of bytes that were processed (always size*nmemb)
231  */
232 static size_t
233 download_hostlist_processor (void *ptr, 
234                              size_t size, 
235                              size_t nmemb, 
236                              void *ctx)
237 {
238   const char * cbuf = ptr;
239   const struct GNUNET_MessageHeader *msg;
240   size_t total;
241   size_t cpy;
242   size_t left;
243   uint16_t msize;
244
245   total = size * nmemb;
246   if ( (total == 0) || (bogus_url) )
247     {
248       return total;  /* ok, no data or bogus data */
249     }
250   GNUNET_STATISTICS_update (stats, 
251                             gettext_noop ("# bytes downloaded from hostlist servers"), 
252                             (int64_t) total, 
253                             GNUNET_NO);  
254   left = total;
255   while ( (left > 0) ||
256           (download_pos > 0) )
257     {
258       cpy = GNUNET_MIN (left, GNUNET_SERVER_MAX_MESSAGE_SIZE - download_pos);
259       memcpy (&download_buffer[download_pos],
260               cbuf,
261               cpy);      
262       cbuf += cpy;
263       download_pos += cpy;
264       left -= cpy;
265       if (download_pos < sizeof(struct GNUNET_MessageHeader))
266         {
267           GNUNET_assert (left == 0);
268           break;
269         }
270       msg = (const struct GNUNET_MessageHeader *) download_buffer;
271       msize = ntohs(msg->size);
272       if (msize < sizeof(struct GNUNET_MessageHeader))
273         {        
274           GNUNET_STATISTICS_update (stats, 
275                                     gettext_noop ("# invalid HELLOs downloaded from hostlist servers"), 
276                                     1, 
277                                     GNUNET_NO);  
278           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
279                       _("Invalid `%s' message received from hostlist at `%s'\n"),
280                       "HELLO",
281                       current_url); 
282           bogus_url = 1;
283           return total;
284         }
285       if (download_pos < msize)
286         {
287           GNUNET_assert (left == 0);
288           break;
289         }
290       if (GNUNET_HELLO_size ((const struct GNUNET_HELLO_Message*)msg) == msize)
291         {
292 #if DEBUG_HOSTLIST_CLIENT
293           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
294                       "Received valid `%s' message from hostlist server.\n",
295                       "HELLO");
296 #endif
297           GNUNET_STATISTICS_update (stats, 
298                                     gettext_noop ("# valid HELLOs downloaded from hostlist servers"), 
299                                     1, 
300                                     GNUNET_NO);
301           hellos_obtained++;
302           GNUNET_TRANSPORT_offer_hello (transport, msg);
303         }
304       else
305         {
306           GNUNET_STATISTICS_update (stats, 
307                                     gettext_noop ("# invalid HELLOs downloaded from hostlist servers"), 
308                                     1, 
309                                     GNUNET_NO);  
310           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
311                       _("Invalid `%s' message received from hostlist at `%s'\n"),
312                       "HELLO",
313                       current_url);
314           bogus_url = GNUNET_YES;
315           return total;
316         }
317       memmove (download_buffer,
318                &download_buffer[msize],
319                download_pos - msize);
320       download_pos -= msize;
321     }
322   return total;
323 }
324
325
326 /**
327  * Obtain a hostlist URL that we should use.
328  *
329  * @return NULL if there is no URL available
330  */
331 static char *
332 get_bootstrap_url ()
333 {
334   char *servers;
335   char *ret;
336   size_t urls;
337   size_t pos;
338
339   if (GNUNET_OK != 
340       GNUNET_CONFIGURATION_get_value_string (cfg,
341                                              "HOSTLIST",
342                                              "SERVERS",
343                                              &servers))
344     {
345       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
346                   _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
347                   "SERVERS", "HOSTLIST");
348       return NULL;
349     }
350
351   urls = 0;
352   if (strlen (servers) > 0)
353     {
354       urls++;
355       pos = strlen (servers) - 1;
356       while (pos > 0)
357         {
358           if (servers[pos] == ' ')
359             urls++;
360           pos--;
361         }
362     }
363   if (urls == 0)
364     {
365       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
366                   _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
367                   "SERVERS", "HOSTLIST");
368       GNUNET_free (servers);
369       return NULL;
370     }
371
372   urls = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, urls) + 1;
373   pos = strlen (servers) - 1;
374   while (pos > 0)
375     {
376       if (servers[pos] == ' ')
377         {
378           urls--;
379           servers[pos] = '\0';
380         }
381       if (urls == 0)
382         {
383           pos++;
384           break;
385         }
386       pos--;    
387     }
388   ret = GNUNET_strdup (&servers[pos]);
389   GNUNET_free (servers);
390   return ret;
391 }
392
393 /**
394  * Method deciding if a preconfigured or advertisied hostlist is used on a 50:50 ratio
395  * @return uri to use, NULL if there is no URL available
396  */
397 static char *
398 get_list_url ()
399 {
400   uint32_t index;
401   unsigned int counter;
402   struct Hostlist * pos;
403
404   if ( GNUNET_NO == learning)
405   {
406     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
407                 "Using preconfigured bootstrap server\n");
408     current_hostlist = NULL;
409     return get_bootstrap_url();
410   }
411   if ( (GNUNET_YES == use_preconfigured_list) ||
412        (linked_list_size == 0) )
413   {
414     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
415                 "Using preconfigured bootstrap server\n");
416     current_hostlist = NULL;
417     return get_bootstrap_url();
418   }
419   index = GNUNET_CRYPTO_random_u32 ( GNUNET_CRYPTO_QUALITY_WEAK, linked_list_size);
420   counter = 0;
421   pos = linked_list_head;
422   while ( counter < index )
423     {
424       pos = pos->next;
425       counter ++;
426     }
427   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
428               "Using learned hostlist `%s'\n", pos->hostlist_uri);
429   current_hostlist = pos;
430   return strdup(pos->hostlist_uri);
431 }
432
433
434 #define CURL_EASY_SETOPT(c, a, b) do { ret = curl_easy_setopt(c, a, b); if (ret != CURLE_OK) GNUNET_log(GNUNET_ERROR_TYPE_WARNING, _("%s failed at %s:%d: `%s'\n"), "curl_easy_setopt", __FILE__, __LINE__, curl_easy_strerror(ret)); } while (0);
435
436
437 /**
438  * Schedule the background task that will (possibly)
439  * download a hostlist.
440  */
441 static void
442 schedule_hostlist_task (void);
443
444 /**
445  * Method to load persistent hostlist file during hostlist client shutdown
446  * @param shutdown set if called because of shutdown, entries in linked list will be destroyed
447  */
448 static void save_hostlist_file ( int shutdown );
449
450 /**
451  * add val2 to val1 with overflow check
452  * return = val1 + val2
453  * @val1 value 1
454  * @val2 value 2
455  * @return result
456  */
457 static uint64_t checked_add (uint64_t val1, uint64_t val2)
458 {
459   static uint64_t temp;
460   static uint64_t maxv;
461
462   maxv = 0;
463   maxv--;
464
465   temp = val1+val2;
466   if ( temp < val1)
467     return maxv;
468   else
469     return temp;
470 }
471
472 /**
473  * substract val2 from val1 with underflow check
474  * return = val1 - val2
475  * @val1 value 1
476  * @val2 value 2
477  * @return result
478  */
479 static uint64_t checked_sub (uint64_t val1, uint64_t val2)
480 {
481   if ( val1 <= val2)
482     return 0;
483   else
484     return (val1-val2);
485 }
486
487
488 /**
489  * Method updating hostlist statistics
490  */
491 static void update_hostlist ( )
492 {
493   if ( (use_preconfigured_list == GNUNET_NO) && ( NULL != current_hostlist ) )
494   {
495     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
496                 "Updating hostlist statics for URI `%s'\n",current_hostlist->hostlist_uri );
497      current_hostlist->hello_count = hellos_obtained;
498      current_hostlist->time_last_usage = GNUNET_TIME_absolute_get();
499      current_hostlist->quality = checked_add ( current_hostlist->quality, (hellos_obtained * HOSTLIST_SUCCESSFUL_HELLO));
500      if ( GNUNET_YES == download_successful )
501      {
502        current_hostlist->times_used++;
503        current_hostlist->quality = checked_add( current_hostlist->quality, HOSTLIST_SUCCESSFUL_DOWNLOAD);
504      }
505      else
506        current_hostlist->quality = checked_sub ( current_hostlist->quality, HOSTLIST_FAILED_DOWNLOAD );
507   }
508   current_hostlist = NULL;
509   /* Alternating the usage of preconfigured and learned hostlists */
510
511   if ( GNUNET_YES == learning)
512     {
513     if (use_preconfigured_list == GNUNET_YES)
514       use_preconfigured_list = GNUNET_NO;
515     else
516       use_preconfigured_list = GNUNET_YES;
517     }
518   else
519     use_preconfigured_list = GNUNET_YES;
520 }
521
522 /**
523  * Clean up the state from the task that downloaded the
524  * hostlist and schedule the next task.
525  */
526 static void 
527 clean_up ()
528 {
529   CURLMcode mret;
530
531   if (multi != NULL)
532     {
533       mret = curl_multi_remove_handle (multi, curl);
534       if (mret != CURLM_OK)
535         {
536           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
537                       _("%s failed at %s:%d: `%s'\n"),
538                       "curl_multi_remove_handle", __FILE__, __LINE__,
539                       curl_multi_strerror (mret));
540         }
541       mret = curl_multi_cleanup (multi);
542       if (mret != CURLM_OK)
543         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
544                     _("%s failed at %s:%d: `%s'\n"),
545                     "curl_multi_cleanup", __FILE__, __LINE__,
546                     curl_multi_strerror (mret));
547       multi = NULL;
548     }
549   if (curl != NULL)
550     {
551       curl_easy_cleanup (curl);
552       curl = NULL;
553     }  
554   GNUNET_free_non_null (current_url);
555   current_url = NULL;
556
557   schedule_hostlist_task ();
558 }
559
560
561 /**
562  * Ask CURL for the select set and then schedule the
563  * receiving task with the scheduler.
564  */
565 static void
566 run_multi (void);
567
568
569 /**
570  * Task that is run when we are ready to receive more data from the hostlist
571  * server. 
572  *
573  * @param cls closure, unused
574  * @param tc task context, unused
575  */
576 static void
577 multi_ready (void *cls,
578              const struct GNUNET_SCHEDULER_TaskContext *tc)
579 {
580   int running;
581   struct CURLMsg *msg;
582   CURLMcode mret;
583   
584   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
585     {
586 #if DEBUG_HOSTLIST_CLIENT
587       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
588                   "Shutdown requested while trying to download hostlist from `%s'\n",
589                   current_url);
590 #endif
591       update_hostlist();
592       clean_up ();
593       return;
594     }
595   if (GNUNET_TIME_absolute_get_remaining (end_time).value == 0)
596     {
597       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
598                   _("Timeout trying to download hostlist from `%s'\n"),
599                   current_url);
600       update_hostlist();
601       clean_up ();
602       return;
603     }
604 #if DEBUG_HOSTLIST_CLIENT
605   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
606               "Ready for processing hostlist client request\n");
607 #endif
608   do 
609     {
610       running = 0;
611       mret = curl_multi_perform (multi, &running);
612       if (running == 0)
613         {
614           do
615             {
616               msg = curl_multi_info_read (multi, &running);
617               GNUNET_break (msg != NULL);
618               if (msg == NULL)
619                 break;
620               switch (msg->msg)
621                 {
622                 case CURLMSG_DONE:
623                   if ( (msg->data.result != CURLE_OK) &&
624                        (msg->data.result != CURLE_GOT_NOTHING) )                       
625                     GNUNET_log(GNUNET_ERROR_TYPE_INFO,
626                                _("%s failed for `%s' at %s:%d: `%s'\n"),
627                                "curl_multi_perform", 
628                                current_url,
629                                __FILE__,
630                                __LINE__,
631                                curl_easy_strerror (msg->data.result));            
632                   else
633                     {
634                     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
635                                 _("Download of hostlist `%s' completed.\n"),
636                                 current_url);
637                     download_successful = GNUNET_YES;
638                     update_hostlist();
639                     }
640                   clean_up ();
641                   return;
642                 default:
643                   break;
644                 }
645             }
646           while (running > 0);
647         }
648     }
649   while (mret == CURLM_CALL_MULTI_PERFORM);
650   if (mret != CURLM_OK)
651     {
652       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
653                   _("%s failed at %s:%d: `%s'\n"),
654                   "curl_multi_perform", __FILE__, __LINE__,
655                   curl_multi_strerror (mret));
656       clean_up ();
657     }
658   run_multi ();
659 }
660
661
662 /**
663  * Ask CURL for the select set and then schedule the
664  * receiving task with the scheduler.
665  */
666 static void
667 run_multi () 
668 {
669   CURLMcode mret;
670   fd_set rs;
671   fd_set ws;
672   fd_set es;
673   int max;
674   struct GNUNET_NETWORK_FDSet *grs;
675   struct GNUNET_NETWORK_FDSet *gws;
676   long timeout;
677   struct GNUNET_TIME_Relative rtime;
678   
679   max = -1;
680   FD_ZERO (&rs);
681   FD_ZERO (&ws);
682   FD_ZERO (&es);
683   mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
684   if (mret != CURLM_OK)
685     {
686       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
687                   _("%s failed at %s:%d: `%s'\n"),
688                   "curl_multi_fdset", __FILE__, __LINE__,
689                   curl_multi_strerror (mret));
690       clean_up ();
691       return;
692     }
693   mret = curl_multi_timeout (multi, &timeout);
694   if (mret != CURLM_OK)
695     {
696       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
697                   _("%s failed at %s:%d: `%s'\n"),
698                   "curl_multi_timeout", __FILE__, __LINE__,
699                   curl_multi_strerror (mret));
700       clean_up ();
701       return;
702     }
703   rtime = GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_remaining (end_time),
704                                     GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
705                                                                    timeout));
706   grs = GNUNET_NETWORK_fdset_create ();
707   gws = GNUNET_NETWORK_fdset_create ();
708   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
709   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);  
710 #if DEBUG_HOSTLIST_CLIENT
711   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
712               "Scheduling task for hostlist download using cURL\n");
713 #endif
714   current_task 
715     = GNUNET_SCHEDULER_add_select (sched,
716                                    GNUNET_SCHEDULER_PRIORITY_DEFAULT,
717                                    GNUNET_SCHEDULER_NO_TASK,
718                                    rtime,
719                                    grs,
720                                    gws,
721                                    &multi_ready,
722                                    multi);
723   GNUNET_NETWORK_fdset_destroy (gws);
724   GNUNET_NETWORK_fdset_destroy (grs);
725 }
726
727
728 /**
729  * Main function that will download a hostlist and process its
730  * data.
731  */
732 static void
733 download_hostlist () 
734 {
735   CURLcode ret;
736   CURLMcode mret;
737
738   current_url = get_list_url ();
739   if (current_url == NULL)
740     return;
741   curl = curl_easy_init ();
742   multi = NULL;
743   if (curl == NULL)
744     {
745       GNUNET_break (0);
746       clean_up ();
747       return;
748     }
749   GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
750               _("Bootstrapping using hostlist at `%s'.\n"), 
751               current_url);
752   hellos_obtained = 0;
753   download_successful = GNUNET_NO;
754   GNUNET_STATISTICS_update (stats, 
755                             gettext_noop ("# hostlist downloads initiated"), 
756                             1, 
757                             GNUNET_NO);  
758   if (proxy != NULL)
759     CURL_EASY_SETOPT (curl, CURLOPT_PROXY, proxy);    
760   download_pos = 0;
761   bogus_url = 0;
762   CURL_EASY_SETOPT (curl,
763                     CURLOPT_WRITEFUNCTION, 
764                     &download_hostlist_processor);
765   if (ret != CURLE_OK)
766     {
767       clean_up ();
768       return;
769     }
770   CURL_EASY_SETOPT (curl,
771                     CURLOPT_WRITEDATA, 
772                     NULL);
773   if (ret != CURLE_OK)
774     {
775       clean_up ();
776       return;
777     }
778   CURL_EASY_SETOPT (curl, CURLOPT_FOLLOWLOCATION, 1);
779   CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 4);
780   /* no need to abort if the above failed */
781   CURL_EASY_SETOPT (curl, 
782                     CURLOPT_URL, 
783                     current_url);
784   if (ret != CURLE_OK)
785     {
786       clean_up ();
787       return;
788     }
789   CURL_EASY_SETOPT (curl, 
790                     CURLOPT_FAILONERROR, 
791                     1);
792 #if 0
793   CURL_EASY_SETOPT (curl, 
794                     CURLOPT_VERBOSE, 
795                     1);
796 #endif
797   CURL_EASY_SETOPT (curl, 
798                     CURLOPT_BUFFERSIZE, 
799                     GNUNET_SERVER_MAX_MESSAGE_SIZE);
800   if (0 == strncmp (current_url, "http", 4))
801     CURL_EASY_SETOPT (curl, CURLOPT_USERAGENT, "GNUnet");
802   CURL_EASY_SETOPT (curl, 
803                     CURLOPT_CONNECTTIMEOUT, 
804                     60L);
805   CURL_EASY_SETOPT (curl, 
806                     CURLOPT_TIMEOUT, 
807                     60L);
808 #if 0
809   /* this should no longer be needed; we're now single-threaded! */
810   CURL_EASY_SETOPT (curl,
811                     CURLOPT_NOSIGNAL, 
812                     1);
813 #endif
814   multi = curl_multi_init ();
815   if (multi == NULL)
816     {
817       GNUNET_break (0);
818       clean_up ();
819       return;
820     }
821   mret = curl_multi_add_handle (multi, curl);
822   if (mret != CURLM_OK)
823     {
824       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
825                   _("%s failed at %s:%d: `%s'\n"),
826                   "curl_multi_add_handle", __FILE__, __LINE__,
827                   curl_multi_strerror (mret));
828       mret = curl_multi_cleanup (multi);
829       if (mret != CURLM_OK)
830         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
831                     _("%s failed at %s:%d: `%s'\n"),
832                     "curl_multi_cleanup", __FILE__, __LINE__,
833                     curl_multi_strerror (mret));
834       multi = NULL;
835       clean_up ();
836       return;
837     }
838   end_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES);
839   run_multi ();
840 }  
841
842
843 /**
844  * Task that checks if we should try to download a hostlist.
845  * If so, we initiate the download, otherwise we schedule
846  * this task again for a later time.
847  */
848 static void
849 check_task (void *cls,
850             const struct GNUNET_SCHEDULER_TaskContext *tc)
851 {
852   current_task = GNUNET_SCHEDULER_NO_TASK;
853   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
854     return;
855   if (connection_count < MIN_CONNECTIONS)
856     download_hostlist ();
857   else
858     schedule_hostlist_task ();
859 }
860
861
862 /**
863  * Compute when we should check the next time about downloading
864  * a hostlist; then schedule the task accordingly.
865  */
866 static void
867 schedule_hostlist_task ()
868 {
869   static int once;
870   struct GNUNET_TIME_Relative delay;
871
872   if (stats == NULL)
873     {
874       curl_global_cleanup ();
875       return; /* in shutdown */
876     }
877   delay = hostlist_delay;
878   if (hostlist_delay.value == 0)
879     hostlist_delay = GNUNET_TIME_UNIT_SECONDS;
880   else
881     hostlist_delay = GNUNET_TIME_relative_multiply (hostlist_delay, 2);
882   if (hostlist_delay.value > GNUNET_TIME_UNIT_HOURS.value * (1 + connection_count))
883     hostlist_delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS,
884                                                     (1 + connection_count));
885   GNUNET_STATISTICS_set (stats,
886                          gettext_noop("# seconds between hostlist downloads"),
887                          hostlist_delay.value,
888                          GNUNET_YES);
889   if (0 == once)
890     {
891       delay = GNUNET_TIME_UNIT_ZERO;
892       once = 1;
893     }  
894   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
895               _("Have %u/%u connections.  Will consider downloading hostlist in %llums\n"),
896               connection_count,
897               MIN_CONNECTIONS,
898               (unsigned long long) delay.value);
899   current_task = GNUNET_SCHEDULER_add_delayed (sched,
900                                                delay,
901                                                &check_task,
902                                                NULL);
903 }
904
905 /**
906  * Task that writes hostlist entries to a file on a regular base
907  * cls closure
908  * tc TaskContext
909  */
910 static void
911 hostlist_saving_task (void *cls,
912             const struct GNUNET_SCHEDULER_TaskContext *tc)
913 {
914   saving_task = GNUNET_SCHEDULER_NO_TASK;
915   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
916     return;
917   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
918               _("Scheduled saving of hostlists\n"));
919   save_hostlist_file ( GNUNET_NO );
920
921   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
922               _("Hostlists will be saved to file again in %llums\n"),
923               (unsigned long long) SAVING_INTERVALL.value);
924   saving_task = GNUNET_SCHEDULER_add_delayed (sched,
925                                                SAVING_INTERVALL,
926                                                &hostlist_saving_task,
927                                                NULL);
928 }
929
930 /**
931  * Method called whenever a given peer connects.
932  *
933  * @param cls closure
934  * @param peer peer identity this notification is about
935  * @param latency reported latency of the connection with 'other'
936  * @param distance reported distance (DV) to 'other' 
937  */
938 static void
939 connect_handler (void *cls,
940                  const struct
941                  GNUNET_PeerIdentity * peer,
942                  struct GNUNET_TIME_Relative latency,
943                  uint32_t distance)
944 {
945   connection_count++;
946   GNUNET_STATISTICS_update (stats, 
947                             gettext_noop ("# active connections"), 
948                             1, 
949                             GNUNET_NO);  
950 }
951
952
953 /**
954  * Method called whenever a given peer disconnects.
955  *
956  * @param cls closure
957  * @param peer peer identity this notification is about
958  */
959 static void
960 disconnect_handler (void *cls,
961                     const struct
962                     GNUNET_PeerIdentity * peer)
963 {
964   connection_count--;
965   GNUNET_STATISTICS_update (stats, 
966                             gettext_noop ("# active connections"), 
967                             -1, 
968                             GNUNET_NO);  
969 }
970
971
972 /**
973  * Method to check if URI is in hostlist linked list
974  * @param uri uri to check
975  * @return GNUNET_YES if existing in linked list, GNUNET_NO if not
976  */
977 static int 
978 linked_list_contains (const char * uri)
979 {
980   struct Hostlist * pos;
981
982   pos = linked_list_head;
983   while (pos != NULL)
984     {
985       if (0 == strcmp(pos->hostlist_uri, uri) ) 
986         return GNUNET_YES;
987       pos = pos->next;
988     }
989   return GNUNET_NO;
990 }
991
992
993 /* linked_list_? */
994 static struct Hostlist *
995 linked_list_get_lowest_quality ( )
996 {
997   struct Hostlist * pos;
998   struct Hostlist * lowest;
999
1000   if (linked_list_size == 0)
1001     return NULL;
1002   lowest = linked_list_head;
1003   pos = linked_list_head->next;
1004   while (pos != NULL)
1005     {
1006       if (pos->quality < lowest->quality) 
1007         lowest = pos;
1008       pos = pos->next;
1009     }
1010   return lowest;
1011 }
1012
1013
1014 /**
1015  * Method called whenever an advertisement message arrives.
1016  *
1017  * @param cls closure (always NULL)
1018  * @param client identification of the client
1019  * @param message the actual message
1020  * @return GNUNET_OK to keep the connection open,
1021  *         GNUNET_SYSERR to close it (signal serious error)
1022  */
1023 static int
1024 advertisement_handler (void *cls,
1025     const struct GNUNET_PeerIdentity * peer,
1026     const struct GNUNET_MessageHeader * message,
1027     struct GNUNET_TIME_Relative latency,
1028     uint32_t distance)
1029 {
1030   size_t size;
1031   size_t uri_size;
1032   const struct GNUNET_MessageHeader * incoming;
1033   const char *uri;
1034   struct Hostlist * hostlist;
1035
1036   GNUNET_assert (ntohs (message->type) == GNUNET_MESSAGE_TYPE_HOSTLIST_ADVERTISEMENT);
1037   size = ntohs (message->size);
1038   if (size <= sizeof(struct GNUNET_MessageHeader))
1039     {
1040       GNUNET_break_op (0);
1041       return GNUNET_SYSERR;
1042     }
1043   incoming = (const struct GNUNET_MessageHeader *) message;
1044   uri = (const char*) &incoming[1];
1045   uri_size = size - sizeof (struct GNUNET_MessageHeader);
1046   if (uri [uri_size - 1] != '\0')
1047     {
1048       GNUNET_break_op (0);
1049       return GNUNET_SYSERR;
1050     }
1051   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1052               "Hostlist client recieved advertisement from `%s' containing URI `%s'\n", 
1053               GNUNET_i2s (peer), 
1054               uri);
1055   if (GNUNET_NO != linked_list_contains (uri))
1056     {
1057       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1058                 "URI `%s' is already known\n",
1059                 uri);
1060       return GNUNET_OK;
1061     }
1062   hostlist = GNUNET_malloc (sizeof (struct Hostlist) + uri_size);
1063   hostlist->hostlist_uri = (const char*) &hostlist[1];
1064   memcpy (&hostlist[1], uri, uri_size);
1065   hostlist->time_creation = GNUNET_TIME_absolute_get();
1066   hostlist->time_last_usage = GNUNET_TIME_absolute_get_zero();
1067   hostlist->quality = HOSTLIST_INITIAL;  
1068
1069   GNUNET_CONTAINER_DLL_insert(linked_list_head, linked_list_tail, hostlist);
1070   linked_list_size++;
1071   
1072   if (MAX_NUMBER_HOSTLISTS >= linked_list_size)
1073     return GNUNET_OK;
1074
1075   /* No free entries available, replace existing entry  */
1076   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1077               "Removing lowest quality entry\n" );  
1078   struct Hostlist * lowest_quality = linked_list_get_lowest_quality();
1079   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1080               "Hostlist with URI `%s' has the worst quality of all with value %llu\n", 
1081               lowest_quality->hostlist_uri,
1082               (unsigned long long) lowest_quality->quality);
1083   GNUNET_CONTAINER_DLL_remove (linked_list_head, linked_list_tail, lowest_quality);
1084   linked_list_size--;
1085   GNUNET_free (lowest_quality);
1086   return GNUNET_OK;
1087 }
1088
1089
1090 /**
1091  * Continuation called by the statistics code once 
1092  * we go the stat.  Initiates hostlist download scheduling.
1093  *
1094  * @param cls closure
1095  * @param success GNUNET_OK if statistics were
1096  *        successfully obtained, GNUNET_SYSERR if not.
1097  */
1098 static void
1099 primary_task (void *cls, int success)
1100 {
1101   if (stats == NULL)
1102     return; /* in shutdown */
1103 #if DEBUG_HOSTLIST_CLIENT
1104   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1105               "Statistics request done, scheduling hostlist download\n");
1106 #endif
1107   schedule_hostlist_task ();
1108 }
1109
1110
1111 static int
1112 process_stat (void *cls,
1113               const char *subsystem,
1114               const char *name,
1115               uint64_t value,
1116               int is_persistent)
1117 {
1118   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1119               _("Initial time between hostlist downloads is %llums\n"),
1120               (unsigned long long) value);
1121   hostlist_delay.value = value;
1122   return GNUNET_OK;
1123 }
1124
1125 /**
1126  * Method to load persistent hostlist file during hostlist client startup
1127  */
1128 static void 
1129 load_hostlist_file ()
1130 {
1131   char *filename;
1132   char *uri;
1133   char *emsg;
1134   struct Hostlist * hostlist;
1135   uri = NULL;
1136   uint32_t times_used;
1137   uint32_t hellos_returned;
1138   uint64_t quality;
1139   uint64_t last_used;
1140   uint64_t created;
1141   uint32_t counter;
1142
1143   if (GNUNET_OK !=
1144       GNUNET_CONFIGURATION_get_value_string (cfg,
1145                                              "HOSTLIST",
1146                                              "HOSTLISTFILE",
1147                                              &filename))
1148     {
1149       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1150                   _("No `%s' specified in `%s' configuration, cannot load hostlists from file.\n"),
1151                   "HOSTLISTFILE", "HOSTLIST");
1152       return;
1153     }
1154
1155   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1156               _("Loading saved hostlist entries from file `%s' \n"), filename);
1157
1158   struct GNUNET_BIO_ReadHandle * rh = GNUNET_BIO_read_open (filename);
1159   if (NULL == rh)
1160     {
1161       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1162                   _("Could not open file `%s' for reading to load hostlists: %s\n"), 
1163                   filename,
1164                   STRERROR (errno));
1165       GNUNET_free (filename);
1166       return;
1167     }
1168
1169   counter = 0;
1170   while ( (GNUNET_OK == GNUNET_BIO_read_string (rh, "url" , &uri, MAX_URL_LEN)) &&
1171           (GNUNET_OK == GNUNET_BIO_read_int32 (rh, &times_used)) &&
1172           (GNUNET_OK == GNUNET_BIO_read_int64 (rh, &quality)) &&
1173           (GNUNET_OK == GNUNET_BIO_read_int64 (rh, &last_used)) &&
1174           (GNUNET_OK == GNUNET_BIO_read_int64 (rh, &created)) &&
1175           (GNUNET_OK == GNUNET_BIO_read_int32 (rh, &hellos_returned)) )
1176     {
1177       hostlist = GNUNET_malloc (sizeof (struct Hostlist) + strlen (uri) + 1);
1178       hostlist->hello_count = hellos_returned;
1179       hostlist->hostlist_uri = (const char *) &hostlist[1];
1180       memcpy (&hostlist[1], uri, strlen(uri)+1);
1181       hostlist->quality = quality;
1182       hostlist->time_creation.value = created;
1183       hostlist->time_last_usage.value = last_used;
1184       GNUNET_CONTAINER_DLL_insert(linked_list_head, linked_list_tail, hostlist);
1185       linked_list_size++;
1186       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1187                   "Added hostlist entry eith URI `%s' \n", hostlist->hostlist_uri);
1188       GNUNET_free (uri);
1189       uri = NULL;
1190       counter++;
1191       if ( counter >= MAX_NUMBER_HOSTLISTS ) break;
1192     }
1193
1194   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1195               _("%u hostlist URIs loaded from file\n"), counter);
1196   GNUNET_STATISTICS_set (stats,
1197                          gettext_noop("# hostlis URIs read from file"),
1198                          counter,
1199                          GNUNET_YES);
1200
1201   GNUNET_free_non_null (uri);
1202   emsg = NULL;
1203   GNUNET_BIO_read_close (rh, &emsg);
1204   if (emsg != NULL)
1205     GNUNET_free (emsg);
1206   GNUNET_free (filename);
1207 }
1208
1209
1210 /**
1211  * Method to load persistent hostlist file during hostlist client shutdown
1212  * @param shutdown set if called because of shutdown, entries in linked list will be destroyed
1213  */
1214 static void save_hostlist_file ( int shutdown )
1215 {
1216   char *filename;
1217   struct Hostlist *pos;
1218   struct GNUNET_BIO_WriteHandle * wh;
1219   int ok;
1220   uint32_t counter;
1221
1222   if (GNUNET_OK !=
1223       GNUNET_CONFIGURATION_get_value_string (cfg,
1224                                              "HOSTLIST",
1225                                              "HOSTLISTFILE",
1226                                              &filename))
1227     {
1228       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1229                   _("No `%s' specified in `%s' configuration, cannot save hostlists to file.\n"),
1230                   "HOSTLISTFILE", "HOSTLIST");
1231                   GNUNET_free (filename);
1232       return;
1233     }
1234   wh = GNUNET_BIO_write_open (filename);
1235   if ( NULL == wh)
1236     {
1237       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1238                   _("Could not open file `%s' for writing to save hostlists: %s\n"),
1239                   filename,
1240                   STRERROR (errno));
1241                   GNUNET_free (filename);
1242       return;
1243     }
1244   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1245               _("Writing %u hostlist URIs to `%s'\n" ),
1246               linked_list_size, filename);
1247
1248   /* add code to write hostlists to file using bio */
1249   ok = GNUNET_YES;
1250   counter = 0;
1251   while (NULL != (pos = linked_list_head))
1252     {
1253       if ( GNUNET_YES == shutdown)
1254       {
1255         GNUNET_CONTAINER_DLL_remove (linked_list_head, linked_list_tail, pos);
1256         linked_list_size--;
1257       }
1258       if (GNUNET_YES == ok)
1259         {
1260           if ( (GNUNET_OK !=
1261                 GNUNET_BIO_write_string (wh, pos->hostlist_uri)) ||
1262                (GNUNET_OK !=
1263                 GNUNET_BIO_write_int32 (wh, pos->times_used)) ||
1264                (GNUNET_OK !=
1265                 GNUNET_BIO_write_int64 (wh, pos->quality)) ||
1266                (GNUNET_OK !=
1267                 GNUNET_BIO_write_int64 (wh, pos->time_last_usage.value)) ||
1268                (GNUNET_OK !=
1269                 GNUNET_BIO_write_int64 (wh, pos->time_creation.value)) ||
1270                (GNUNET_OK !=
1271                 GNUNET_BIO_write_int32 (wh, pos->hello_count)))
1272             {
1273               GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1274                           _("Error writing hostlist URIs to file `%s'\n"),
1275                           filename);
1276               ok = GNUNET_NO;
1277             }
1278         }
1279
1280       if ( GNUNET_YES == shutdown)
1281         GNUNET_free (pos);
1282       counter ++;
1283       if ( counter >= MAX_NUMBER_HOSTLISTS) break;
1284     }  
1285   GNUNET_STATISTICS_set (stats,
1286                          gettext_noop("# hostlist URIs written to file"),
1287                          counter,
1288                          GNUNET_YES);
1289
1290   if ( GNUNET_OK != GNUNET_BIO_write_close ( wh ) )
1291     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1292                 _("Error writing hostlist URIs to file `%s'\n"),
1293                 filename);
1294   GNUNET_free (filename);
1295 }
1296
1297 /**
1298  * Start downloading hostlists from hostlist servers as necessary.
1299  */
1300 int
1301 GNUNET_HOSTLIST_client_start (const struct GNUNET_CONFIGURATION_Handle *c,
1302                               struct GNUNET_SCHEDULER_Handle *s,
1303                               struct GNUNET_STATISTICS_Handle *st,
1304                               GNUNET_CORE_ConnectEventHandler *ch,
1305                               GNUNET_CORE_DisconnectEventHandler *dh,
1306                               GNUNET_CORE_MessageCallback *msgh,
1307                               int learn)
1308 {
1309   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
1310     {
1311       GNUNET_break (0);
1312       return GNUNET_SYSERR;
1313     }
1314   transport = GNUNET_TRANSPORT_connect (s, c, NULL, NULL, NULL, NULL);
1315   if (NULL == transport)
1316     {
1317       curl_global_cleanup ();
1318       return GNUNET_SYSERR;
1319     }
1320   cfg = c;
1321   sched = s;
1322   stats = st;
1323   if (GNUNET_OK !=
1324       GNUNET_CONFIGURATION_get_value_string (cfg,
1325                                              "HOSTLIST",
1326                                              "HTTP-PROXY", 
1327                                              &proxy))
1328     proxy = NULL;
1329   *ch = &connect_handler;
1330   *dh = &disconnect_handler;
1331   if (learn)
1332     *msgh = &advertisement_handler;
1333   else
1334     *msgh = NULL;
1335   linked_list_head = NULL;
1336   linked_list_tail = NULL;
1337   use_preconfigured_list = GNUNET_YES;
1338   learning = learn;
1339   if ( GNUNET_YES == learning )
1340   {
1341     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1342               _("Learning is enabled on this peer\n"));
1343     load_hostlist_file ();
1344     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1345               _("Hostlists will be saved to file again in  %llums\n"),
1346               (unsigned long long) SAVING_INTERVALL.value);
1347     saving_task = GNUNET_SCHEDULER_add_delayed (sched,
1348                                                SAVING_INTERVALL,
1349                                                &hostlist_saving_task,
1350                                                NULL);
1351   }
1352   else
1353   {
1354     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1355               _("Learning is not enabled on this peer\n"));
1356   }
1357   GNUNET_STATISTICS_get (stats,
1358                          "hostlist",
1359                          gettext_noop("# seconds between hostlist downloads"),
1360                          GNUNET_TIME_UNIT_MINUTES,
1361                          &primary_task,
1362                          &process_stat,
1363                          NULL);
1364   return GNUNET_OK;
1365 }
1366
1367
1368 /**
1369  * Stop downloading hostlists from hostlist servers as necessary.
1370  */
1371 void
1372 GNUNET_HOSTLIST_client_stop ()
1373 {
1374 #if DEBUG_HOSTLIST_CLIENT
1375   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1376               "Hostlist client shutdown\n");
1377 #endif
1378   save_hostlist_file ( GNUNET_YES );
1379
1380   if (current_task != GNUNET_SCHEDULER_NO_TASK)
1381     {
1382       GNUNET_SCHEDULER_cancel (sched,
1383                                current_task);
1384       curl_global_cleanup ();
1385     }
1386   if (transport != NULL)
1387     {
1388       GNUNET_TRANSPORT_disconnect (transport);
1389       transport = NULL;
1390     }
1391   GNUNET_assert (NULL == transport);
1392   GNUNET_free_non_null (proxy);
1393   proxy = NULL;
1394   cfg = NULL;
1395   sched = NULL;
1396 }
1397
1398 /* end of hostlist-client.c */