some doxygen fixes
[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 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  */
26
27 #include "platform.h"
28 #include "hostlist-client.h"
29 #include "gnunet_core_service.h"
30 #include "gnunet_hello_lib.h"
31 #include "gnunet_transport_service.h"
32 #include <curl/curl.h>
33
34 /**
35  * Number of connections that we must have to NOT download
36  * hostlists anymore.
37  */
38 #define MIN_CONNECTIONS 4
39
40 /**
41  * Our configuration.
42  */
43 static const struct GNUNET_CONFIGURATION_Handle *cfg;
44
45 /**
46  * Our scheduler.
47  */
48 static struct GNUNET_SCHEDULER_Handle *sched;
49
50 /**
51  * Statistics handle.
52  */
53 struct GNUNET_STATISTICS_Handle *stats; 
54
55 /**
56  * Transport handle.
57  */
58 struct GNUNET_TRANSPORT_Handle *transport;
59                        
60 /**
61  * Proxy that we are using (can be NULL).
62  */
63 static char *proxy;
64
65 /**
66  * Buffer for data downloaded via HTTP.
67  */
68 static char download_buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
69
70 /**
71  * Number of bytes valid in 'download_buffer'.
72  */
73 static size_t download_pos;
74
75 /**
76  * Current URL that we are using.
77  */
78 static char *current_url;
79
80 /**
81  * Current CURL handle.
82  */
83 static CURL *curl;
84
85 /**
86  * Current multi-CURL handle.
87  */
88 static CURLM *multi;
89
90 /**
91  * ID of the current task scheduled.
92  */
93 static GNUNET_SCHEDULER_TaskIdentifier current_task;
94
95 /**
96  * Amount of time we wait between hostlist downloads.
97  */
98 static struct GNUNET_TIME_Relative hostlist_delay;
99
100 /**
101  * Set to GNUNET_YES if the current URL had some problems.
102  */ 
103 static int bogus_url;
104
105 /**
106  * Number of active connections (according to core service).
107  */
108 static unsigned int connection_count;
109
110
111 /**
112  * Process downloaded bits by calling callback on each HELLO.
113  *
114  * @param ptr buffer with downloaded data
115  * @param size size of a record
116  * @param nmemb number of records downloaded
117  * @param ctx unused
118  * @return number of bytes that were processed (always size*nmemb)
119  */
120 static size_t
121 download_hostlist_processor (void *ptr, 
122                              size_t size, 
123                              size_t nmemb, 
124                              void *ctx)
125 {
126   const char * cbuf = ptr;
127   const struct GNUNET_MessageHeader *msg;
128   size_t total;
129   size_t cpy;
130   size_t left;
131   uint16_t msize;
132
133   total = size * nmemb;
134   if ( (total == 0) || (bogus_url) )
135     return total;  /* ok, no data or bogus data */
136   left = total;
137   while (left > 0)
138     {
139       cpy = GNUNET_MIN (total, GNUNET_SERVER_MAX_MESSAGE_SIZE - download_pos);
140       GNUNET_assert (cpy > 0);
141       memcpy (&download_buffer[download_pos],
142               cbuf,
143               cpy);
144       cbuf += cpy;
145       download_pos += cpy;
146       left -= cpy;
147       if (download_pos < sizeof(struct GNUNET_MessageHeader))
148         break;
149       msg = (const struct GNUNET_MessageHeader *) download_buffer;
150       msize = ntohs(msg->size);
151       if (msize < sizeof(struct GNUNET_MessageHeader))
152         {        
153           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
154                       _("Invalid `%s' message received from hostlist at `%s'\n"),
155                       "HELLO",
156                       current_url); 
157           bogus_url = 1;
158           return total;
159         }
160       if (download_pos < msize)
161         break;
162       if (GNUNET_HELLO_size ((const struct GNUNET_HELLO_Message*)msg) == msize)
163         {
164           GNUNET_TRANSPORT_offer_hello (transport, msg);
165         }
166       else
167         {
168           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
169                       _("Invalid `%s' message received from hostlist at `%s'\n"),
170                       "HELLO",
171                       current_url);
172           bogus_url = 1;
173           return total;
174         }
175       memmove (download_buffer,
176                &download_buffer[msize],
177                download_pos - msize);
178       download_pos -= msize;
179     }
180   return total;
181 }
182
183
184 /**
185  * Obtain a hostlist URL that we should use.
186  *
187  * @return NULL if there is no URL available
188  */
189 static char *
190 get_url ()
191 {
192   char *servers;
193   char *ret;
194   size_t urls;
195   size_t pos;
196
197   if (GNUNET_OK != 
198       GNUNET_CONFIGURATION_get_value_string (cfg,
199                                              "HOSTLIST",
200                                              "SERVERS",
201                                              &servers))
202     {
203       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
204                   _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
205                   "SERVERS", "HOSTLIST");
206       return NULL;
207     }
208
209   urls = 0;
210   if (strlen (servers) > 0)
211     {
212       urls++;
213       pos = strlen (servers) - 1;
214       while (pos > 0)
215         {
216           if (servers[pos] == ' ')
217             urls++;
218           pos--;
219         }
220     }
221   if (urls == 0)
222     {
223       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
224                   _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
225                   "SERVERS", "HOSTLIST");
226       GNUNET_free (servers);
227       return NULL;
228     }
229
230   urls = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, urls) + 1;
231   pos = strlen (servers) - 1;
232   while (pos > 0)
233     {
234       if (servers[pos] == ' ')
235         {
236           urls--;
237           servers[pos] = '\0';
238         }
239       if (urls == 0)
240         {
241           pos++;
242           break;
243         }
244       pos--;    
245     }
246   ret = GNUNET_strdup (&servers[pos]);
247   GNUNET_free (servers);
248   return ret;
249 }
250
251
252 #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);
253
254
255 /**
256  * Schedule the background task that will (possibly)
257  * download a hostlist.
258  */
259 static void
260 schedule_hostlist_task (void);
261
262
263 /**
264  * Clean up the state from the task that downloaded the
265  * hostlist and schedule the next task.
266  */
267 static void 
268 clean_up ()
269 {
270   CURLMcode mret;
271
272   if (multi != NULL)
273     {
274       mret = curl_multi_remove_handle (multi, curl);
275       if (mret != CURLM_OK)
276         {
277           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
278                       _("%s failed at %s:%d: `%s'\n"),
279                       "curl_multi_remove_handle", __FILE__, __LINE__,
280                       curl_multi_strerror (mret));
281         }
282       mret = curl_multi_cleanup (multi);
283       if (mret != CURLM_OK)
284         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
285                     _("%s failed at %s:%d: `%s'\n"),
286                     "curl_multi_cleanup", __FILE__, __LINE__,
287                     curl_multi_strerror (mret));
288       multi = NULL;
289     }
290   if (curl != NULL)
291     {
292       curl_easy_cleanup (curl);
293       curl = NULL;
294     }  
295   if (transport != NULL)
296     {
297       GNUNET_TRANSPORT_disconnect (transport);
298       transport = NULL;
299     }
300   GNUNET_free_non_null (current_url);
301   current_url = NULL;
302   schedule_hostlist_task ();
303 }
304
305
306 /**
307  * Task that is run when we are ready to receive more data from the hostlist
308  * server. 
309  *
310  * @param cls closure, unused
311  * @param tc task context, unused
312  */
313 static void
314 multi_ready (void *cls,
315              const struct GNUNET_SCHEDULER_TaskContext *tc)
316 {
317   int running;
318   struct CURLMsg *msg;
319   CURLMcode mret;
320
321   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_TIMEOUT))
322     {
323       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
324                   _("Timeout trying to download hostlist from `%s'\n"),
325                   current_url);
326       clean_up ();
327       return;
328     }
329   do 
330     {
331       running = 0;
332       mret = curl_multi_perform (multi, &running);
333       if (running == 0)
334         {
335           do
336             {
337               msg = curl_multi_info_read (multi, &running);
338               GNUNET_break (msg != NULL);
339               if (msg == NULL)
340                 break;
341               switch (msg->msg)
342                 {
343                 case CURLMSG_DONE:
344                   if (msg->data.result != CURLE_OK)
345                     GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
346                                _("%s failed at %s:%d: `%s'\n"),
347                                "curl_multi_perform", __FILE__,
348                                __LINE__,
349                                curl_easy_strerror (msg->data.result));
350                   break;
351                 default:
352                   break;
353                 }
354             }
355           while (running > 0);
356         }
357     }
358   while (mret == CURLM_CALL_MULTI_PERFORM);
359   if (mret != CURLM_OK)
360     {
361       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
362                   _("%s failed at %s:%d: `%s'\n"),
363                   "curl_multi_perform", __FILE__, __LINE__,
364                   curl_multi_strerror (mret));
365       clean_up ();
366       return;
367     }
368   clean_up ();
369 }
370
371
372 /**
373  * Ask CURL for the select set and then schedule the
374  * receiving task with the scheduler.
375  */
376 static void
377 run_multi () 
378 {
379   CURLMcode mret;
380   fd_set rs;
381   fd_set ws;
382   fd_set es;
383   int max;
384   struct GNUNET_NETWORK_FDSet *grs;
385   struct GNUNET_NETWORK_FDSet *gws;
386   struct GNUNET_NETWORK_FDSet *ges;
387   
388   max = 0;
389   FD_ZERO (&rs);
390   FD_ZERO (&ws);
391   FD_ZERO (&es);
392   grs = GNUNET_NETWORK_fdset_create ();
393   gws = GNUNET_NETWORK_fdset_create ();
394   ges = GNUNET_NETWORK_fdset_create ();
395   mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
396   if (mret != CURLM_OK)
397     {
398       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
399                   _("%s failed at %s:%d: `%s'\n"),
400                   "curl_multi_fdset", __FILE__, __LINE__,
401                   curl_multi_strerror (mret));
402       clean_up ();
403       return;
404     }
405   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max);
406   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max);
407   GNUNET_NETWORK_fdset_copy_native (ges, &es, max);
408   current_task 
409     = GNUNET_SCHEDULER_add_select (sched,
410                                    GNUNET_NO,
411                                    GNUNET_SCHEDULER_PRIORITY_DEFAULT,
412                                    GNUNET_SCHEDULER_NO_TASK,
413                                    GNUNET_TIME_UNIT_MINUTES,
414                                    grs,
415                                    gws,
416                                    &multi_ready,
417                                    multi);
418   GNUNET_NETWORK_fdset_destroy (ges);
419   GNUNET_NETWORK_fdset_destroy (gws);
420   GNUNET_NETWORK_fdset_destroy (grs);
421 }
422
423
424 /**
425  * Main function that will download a hostlist and process its
426  * data.
427  */
428 static void
429 download_hostlist () 
430 {
431   CURLcode ret;
432   CURLMcode mret;
433
434   curl = curl_easy_init ();
435   multi = NULL;
436   if (curl == NULL)
437     {
438       GNUNET_break (0);
439       clean_up ();
440       return;
441     }
442   transport = GNUNET_TRANSPORT_connect (sched, cfg, NULL, NULL, NULL, NULL);
443   current_url = get_url ();
444   GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
445               _("Bootstrapping using hostlist at `%s'.\n"), 
446               current_url);
447
448   if (proxy != NULL)
449     CURL_EASY_SETOPT (curl, CURLOPT_PROXY, proxy);    
450   download_pos = 0;
451   bogus_url = 0;
452   CURL_EASY_SETOPT (curl,
453                     CURLOPT_WRITEFUNCTION, 
454                     &download_hostlist_processor);
455   if (ret != CURLE_OK)
456     {
457       clean_up ();
458       return;
459     }
460   CURL_EASY_SETOPT (curl,
461                     CURLOPT_WRITEDATA, 
462                     NULL);
463   if (ret != CURLE_OK)
464     {
465       clean_up ();
466       return;
467     }
468   CURL_EASY_SETOPT (curl, 
469                     CURLOPT_URL, 
470                     current_url);
471   if (ret != CURLE_OK)
472     {
473       clean_up ();
474       return;
475     }
476   CURL_EASY_SETOPT (curl, 
477                     CURLOPT_FAILONERROR, 
478                     1);
479   CURL_EASY_SETOPT (curl, 
480                     CURLOPT_BUFFERSIZE, 
481                     GNUNET_SERVER_MAX_MESSAGE_SIZE);
482   if (0 == strncmp (current_url, "http", 4))
483     CURL_EASY_SETOPT (curl, CURLOPT_USERAGENT, "GNUnet");
484   CURL_EASY_SETOPT (curl, 
485                     CURLOPT_CONNECTTIMEOUT, 
486                     150L);
487   CURL_EASY_SETOPT (curl,
488                     CURLOPT_NOSIGNAL, 
489                     1);
490   multi = curl_multi_init ();
491   if (multi == NULL)
492     {
493       GNUNET_break (0);
494       clean_up ();
495       return;
496     }
497   mret = curl_multi_add_handle (multi, curl);
498   if (mret != CURLM_OK)
499     {
500       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
501                   _("%s failed at %s:%d: `%s'\n"),
502                   "curl_multi_add_handle", __FILE__, __LINE__,
503                   curl_multi_strerror (mret));
504       mret = curl_multi_cleanup (multi);
505       if (mret != CURLM_OK)
506         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
507                     _("%s failed at %s:%d: `%s'\n"),
508                     "curl_multi_cleanup", __FILE__, __LINE__,
509                     curl_multi_strerror (mret));
510       multi = NULL;
511       clean_up ();
512       return;
513     }
514   run_multi (multi);
515 }  
516
517
518 /**
519  * Task that checks if we should try to download a hostlist.
520  * If so, we initiate the download, otherwise we schedule
521  * this task again for a later time.
522  */
523 static void
524 check_task (void *cls,
525             const struct GNUNET_SCHEDULER_TaskContext *tc)
526 {
527   if (connection_count < MIN_CONNECTIONS)
528     download_hostlist ();
529   else
530     schedule_hostlist_task ();
531 }
532
533
534 /**
535  * Compute when we should check the next time about downloading
536  * a hostlist; then schedule the task accordingly.
537  */
538 static void
539 schedule_hostlist_task ()
540 {
541   struct GNUNET_TIME_Relative delay;
542
543   delay = hostlist_delay;
544   if (hostlist_delay.value == 0)
545     hostlist_delay = GNUNET_TIME_UNIT_SECONDS;
546   else
547     hostlist_delay = GNUNET_TIME_relative_multiply (hostlist_delay, 2);
548   if (hostlist_delay.value > GNUNET_TIME_UNIT_HOURS.value * connection_count)
549     hostlist_delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_DAYS,
550                                                     connection_count);
551   GNUNET_STATISTICS_set (stats,
552                          gettext_noop("Minimum time between hostlist downloads"),
553                          hostlist_delay.value,
554                          GNUNET_YES);
555   current_task = GNUNET_SCHEDULER_add_delayed (sched,
556                                                GNUNET_NO,
557                                                GNUNET_SCHEDULER_PRIORITY_IDLE,
558                                                GNUNET_SCHEDULER_NO_TASK,
559                                                delay,
560                                                &check_task,
561                                                NULL);
562 }
563
564
565 /**
566  * Method called whenever a given peer connects.
567  *
568  * @param cls closure
569  * @param peer peer identity this notification is about
570  */
571 static void
572 connect_handler (void *cls,
573                  const struct
574                  GNUNET_PeerIdentity * peer)
575 {
576   connection_count++;
577 }
578
579
580 /**
581  * Method called whenever a given peer connects.
582  *
583  * @param cls closure
584  * @param peer peer identity this notification is about
585  */
586 static void
587 disconnect_handler (void *cls,
588                     const struct
589                     GNUNET_PeerIdentity * peer)
590 {
591   connection_count--;
592 }
593
594
595 /**
596  * Continuation called by the statistics code once 
597  * we go the stat.  Initiates hostlist download scheduling.
598  *
599  * @param cls closure
600  * @param success GNUNET_OK if statistics were
601  *        successfully obtained, GNUNET_SYSERR if not.
602  */
603 static void
604 primary_task (void *cls, int success)
605 {
606   schedule_hostlist_task ();
607 }
608
609
610 static int
611 process_stat (void *cls,
612               const char *subsystem,
613               const char *name,
614               unsigned long long value,
615               int is_persistent)
616 {
617   hostlist_delay.value = (uint64_t) value;
618   return GNUNET_OK;
619 }
620
621
622 /**
623  * Start downloading hostlists from hostlist servers as necessary.
624  */
625 int
626 GNUNET_HOSTLIST_client_start (const struct GNUNET_CONFIGURATION_Handle *c,
627                               struct GNUNET_SCHEDULER_Handle *s,
628                               struct GNUNET_STATISTICS_Handle *st,
629                               GNUNET_CORE_ClientEventHandler *ch,
630                               GNUNET_CORE_ClientEventHandler *dh)
631 {
632   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
633     {
634       GNUNET_break (0);
635       return GNUNET_SYSERR;
636     }
637   cfg = c;
638   sched = s;
639   stats = st;
640   if (GNUNET_OK !=
641       GNUNET_CONFIGURATION_get_value_string (cfg,
642                                              "HOSTLIST",
643                                              "HTTP-PROXY", 
644                                              &proxy))
645     proxy = NULL;
646   *ch = &connect_handler;
647   *dh = &disconnect_handler;
648   GNUNET_STATISTICS_get (stats,
649                          "hostlist",
650                          gettext_noop("Minimum time between hostlist downloads"),
651                          GNUNET_TIME_UNIT_MINUTES,
652                          &primary_task,
653                          &process_stat,
654                          NULL);
655   return GNUNET_OK;
656 }
657
658
659 /**
660  * Stop downloading hostlists from hostlist servers as necessary.
661  */
662 void
663 GNUNET_HOSTLIST_client_stop ()
664 {
665   GNUNET_SCHEDULER_cancel (sched,
666                            current_task);
667   GNUNET_free_non_null (proxy);
668   proxy = NULL;
669   if (sched != NULL)
670     curl_global_cleanup ();
671   cfg = NULL;
672   sched = NULL;
673 }
674
675 /* end of hostlist-client.c */