9fcf2ded1b38ab5e4d4cd48a6f2b8643d1037d42
[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     {
136       return total;  /* ok, no data or bogus data */
137     }
138   left = total;
139   while (left > 0)
140     {
141       cpy = GNUNET_MIN (total, GNUNET_SERVER_MAX_MESSAGE_SIZE - download_pos);
142       GNUNET_assert (cpy > 0);
143       memcpy (&download_buffer[download_pos],
144               cbuf,
145               cpy);
146       cbuf += cpy;
147       download_pos += cpy;
148       left -= cpy;
149       if (download_pos < sizeof(struct GNUNET_MessageHeader))
150         break;
151       msg = (const struct GNUNET_MessageHeader *) download_buffer;
152       msize = ntohs(msg->size);
153       if (msize < sizeof(struct GNUNET_MessageHeader))
154         {        
155           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
156                       _("Invalid `%s' message received from hostlist at `%s'\n"),
157                       "HELLO",
158                       current_url); 
159           bogus_url = 1;
160           return total;
161         }
162       if (download_pos < msize)
163         break;
164       if (GNUNET_HELLO_size ((const struct GNUNET_HELLO_Message*)msg) == msize)
165         {
166           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
167                       "Received valid `%s' message from hostlist server.\n",
168                       "HELLO");
169           GNUNET_TRANSPORT_offer_hello (transport, msg);
170         }
171       else
172         {
173           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
174                       _("Invalid `%s' message received from hostlist at `%s'\n"),
175                       "HELLO",
176                       current_url);
177           bogus_url = 1;
178           return total;
179         }
180       memmove (download_buffer,
181                &download_buffer[msize],
182                download_pos - msize);
183       download_pos -= msize;
184     }
185   return total;
186 }
187
188
189 /**
190  * Obtain a hostlist URL that we should use.
191  *
192  * @return NULL if there is no URL available
193  */
194 static char *
195 get_url ()
196 {
197   char *servers;
198   char *ret;
199   size_t urls;
200   size_t pos;
201
202   if (GNUNET_OK != 
203       GNUNET_CONFIGURATION_get_value_string (cfg,
204                                              "HOSTLIST",
205                                              "SERVERS",
206                                              &servers))
207     {
208       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
209                   _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
210                   "SERVERS", "HOSTLIST");
211       return NULL;
212     }
213
214   urls = 0;
215   if (strlen (servers) > 0)
216     {
217       urls++;
218       pos = strlen (servers) - 1;
219       while (pos > 0)
220         {
221           if (servers[pos] == ' ')
222             urls++;
223           pos--;
224         }
225     }
226   if (urls == 0)
227     {
228       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
229                   _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
230                   "SERVERS", "HOSTLIST");
231       GNUNET_free (servers);
232       return NULL;
233     }
234
235   urls = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, urls) + 1;
236   pos = strlen (servers) - 1;
237   while (pos > 0)
238     {
239       if (servers[pos] == ' ')
240         {
241           urls--;
242           servers[pos] = '\0';
243         }
244       if (urls == 0)
245         {
246           pos++;
247           break;
248         }
249       pos--;    
250     }
251   ret = GNUNET_strdup (&servers[pos]);
252   GNUNET_free (servers);
253   return ret;
254 }
255
256
257 #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);
258
259
260 /**
261  * Schedule the background task that will (possibly)
262  * download a hostlist.
263  */
264 static void
265 schedule_hostlist_task (void);
266
267
268 /**
269  * Clean up the state from the task that downloaded the
270  * hostlist and schedule the next task.
271  */
272 static void 
273 clean_up ()
274 {
275   CURLMcode mret;
276
277   if (multi != NULL)
278     {
279       mret = curl_multi_remove_handle (multi, curl);
280       if (mret != CURLM_OK)
281         {
282           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
283                       _("%s failed at %s:%d: `%s'\n"),
284                       "curl_multi_remove_handle", __FILE__, __LINE__,
285                       curl_multi_strerror (mret));
286         }
287       mret = curl_multi_cleanup (multi);
288       if (mret != CURLM_OK)
289         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
290                     _("%s failed at %s:%d: `%s'\n"),
291                     "curl_multi_cleanup", __FILE__, __LINE__,
292                     curl_multi_strerror (mret));
293       multi = NULL;
294     }
295   if (curl != NULL)
296     {
297       curl_easy_cleanup (curl);
298       curl = 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_SHUTDOWN))
322     {
323       clean_up ();
324       return;
325     }
326   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_TIMEOUT))
327     {
328       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
329                   _("Timeout trying to download hostlist from `%s'\n"),
330                   current_url);
331       clean_up ();
332       return;
333     }
334   do 
335     {
336       running = 0;
337       mret = curl_multi_perform (multi, &running);
338       if (running == 0)
339         {
340           do
341             {
342               msg = curl_multi_info_read (multi, &running);
343               GNUNET_break (msg != NULL);
344               if (msg == NULL)
345                 break;
346               switch (msg->msg)
347                 {
348                 case CURLMSG_DONE:
349                   if (msg->data.result != CURLE_OK)
350                     GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
351                                _("%s failed at %s:%d: `%s'\n"),
352                                "curl_multi_perform", __FILE__,
353                                __LINE__,
354                                curl_easy_strerror (msg->data.result));
355                   break;
356                 default:
357                   break;
358                 }
359             }
360           while (running > 0);
361         }
362     }
363   while (mret == CURLM_CALL_MULTI_PERFORM);
364   if (mret != CURLM_OK)
365     {
366       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
367                   _("%s failed at %s:%d: `%s'\n"),
368                   "curl_multi_perform", __FILE__, __LINE__,
369                   curl_multi_strerror (mret));
370       clean_up ();
371       return;
372     }
373   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
374               _("Download of hostlist `%s' completed.\n"),
375               current_url);
376   clean_up ();
377 }
378
379
380 /**
381  * Ask CURL for the select set and then schedule the
382  * receiving task with the scheduler.
383  */
384 static void
385 run_multi () 
386 {
387   CURLMcode mret;
388   fd_set rs;
389   fd_set ws;
390   fd_set es;
391   int max;
392   struct GNUNET_NETWORK_FDSet *grs;
393   struct GNUNET_NETWORK_FDSet *gws;
394   
395   max = 0;
396   FD_ZERO (&rs);
397   FD_ZERO (&ws);
398   FD_ZERO (&es);
399   mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
400   if (mret != CURLM_OK)
401     {
402       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
403                   _("%s failed at %s:%d: `%s'\n"),
404                   "curl_multi_fdset", __FILE__, __LINE__,
405                   curl_multi_strerror (mret));
406       clean_up ();
407       return;
408     }
409   grs = GNUNET_NETWORK_fdset_create ();
410   gws = GNUNET_NETWORK_fdset_create ();
411   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max);
412   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max);
413   current_task 
414     = GNUNET_SCHEDULER_add_select (sched,
415                                    GNUNET_SCHEDULER_PRIORITY_DEFAULT,
416                                    GNUNET_SCHEDULER_NO_TASK,
417                                    GNUNET_TIME_UNIT_MINUTES,
418                                    grs,
419                                    gws,
420                                    &multi_ready,
421                                    multi);
422   GNUNET_NETWORK_fdset_destroy (gws);
423   GNUNET_NETWORK_fdset_destroy (grs);
424 }
425
426
427 /**
428  * Main function that will download a hostlist and process its
429  * data.
430  */
431 static void
432 download_hostlist () 
433 {
434   CURLcode ret;
435   CURLMcode mret;
436
437   curl = curl_easy_init ();
438   multi = NULL;
439   if (curl == NULL)
440     {
441       GNUNET_break (0);
442       clean_up ();
443       return;
444     }
445   transport = GNUNET_TRANSPORT_connect (sched, cfg, NULL, NULL, NULL, NULL);
446   current_url = get_url ();
447   GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
448               _("Bootstrapping using hostlist at `%s'.\n"), 
449               current_url);
450
451   if (proxy != NULL)
452     CURL_EASY_SETOPT (curl, CURLOPT_PROXY, proxy);    
453   download_pos = 0;
454   bogus_url = 0;
455   CURL_EASY_SETOPT (curl,
456                     CURLOPT_WRITEFUNCTION, 
457                     &download_hostlist_processor);
458   if (ret != CURLE_OK)
459     {
460       clean_up ();
461       return;
462     }
463   CURL_EASY_SETOPT (curl,
464                     CURLOPT_WRITEDATA, 
465                     NULL);
466   if (ret != CURLE_OK)
467     {
468       clean_up ();
469       return;
470     }
471   CURL_EASY_SETOPT (curl, CURLOPT_FOLLOWLOCATION, 1);
472   /* no need to abort if the above failed */
473   CURL_EASY_SETOPT (curl, 
474                     CURLOPT_URL, 
475                     current_url);
476   if (ret != CURLE_OK)
477     {
478       clean_up ();
479       return;
480     }
481   CURL_EASY_SETOPT (curl, 
482                     CURLOPT_FAILONERROR, 
483                     1);
484   CURL_EASY_SETOPT (curl, 
485                     CURLOPT_BUFFERSIZE, 
486                     GNUNET_SERVER_MAX_MESSAGE_SIZE);
487   if (0 == strncmp (current_url, "http", 4))
488     CURL_EASY_SETOPT (curl, CURLOPT_USERAGENT, "GNUnet");
489   CURL_EASY_SETOPT (curl, 
490                     CURLOPT_CONNECTTIMEOUT, 
491                     150L);
492   CURL_EASY_SETOPT (curl,
493                     CURLOPT_NOSIGNAL, 
494                     1);
495   multi = curl_multi_init ();
496   if (multi == NULL)
497     {
498       GNUNET_break (0);
499       clean_up ();
500       return;
501     }
502   mret = curl_multi_add_handle (multi, curl);
503   if (mret != CURLM_OK)
504     {
505       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
506                   _("%s failed at %s:%d: `%s'\n"),
507                   "curl_multi_add_handle", __FILE__, __LINE__,
508                   curl_multi_strerror (mret));
509       mret = curl_multi_cleanup (multi);
510       if (mret != CURLM_OK)
511         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
512                     _("%s failed at %s:%d: `%s'\n"),
513                     "curl_multi_cleanup", __FILE__, __LINE__,
514                     curl_multi_strerror (mret));
515       multi = NULL;
516       clean_up ();
517       return;
518     }
519   run_multi (multi);
520 }  
521
522
523 /**
524  * Task that checks if we should try to download a hostlist.
525  * If so, we initiate the download, otherwise we schedule
526  * this task again for a later time.
527  */
528 static void
529 check_task (void *cls,
530             const struct GNUNET_SCHEDULER_TaskContext *tc)
531 {
532   current_task = GNUNET_SCHEDULER_NO_TASK;
533   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
534     return;
535   if (connection_count < MIN_CONNECTIONS)
536     download_hostlist ();
537   else
538     schedule_hostlist_task ();
539 }
540
541
542 /**
543  * Compute when we should check the next time about downloading
544  * a hostlist; then schedule the task accordingly.
545  */
546 static void
547 schedule_hostlist_task ()
548 {
549   struct GNUNET_TIME_Relative delay;
550
551   if (stats == NULL)
552     {
553       curl_global_cleanup ();
554       return; /* in shutdown */
555     }
556   delay = hostlist_delay;
557   if (hostlist_delay.value == 0)
558     hostlist_delay = GNUNET_TIME_UNIT_SECONDS;
559   else
560     hostlist_delay = GNUNET_TIME_relative_multiply (hostlist_delay, 2);
561   if (hostlist_delay.value > GNUNET_TIME_UNIT_HOURS.value * connection_count)
562     hostlist_delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_DAYS,
563                                                     connection_count);
564   GNUNET_STATISTICS_set (stats,
565                          gettext_noop("Minimum time between hostlist downloads"),
566                          hostlist_delay.value,
567                          GNUNET_YES);
568   current_task = GNUNET_SCHEDULER_add_delayed (sched,
569                                                delay,
570                                                &check_task,
571                                                NULL);
572 }
573
574
575 /**
576  * Method called whenever a given peer connects.
577  *
578  * @param cls closure
579  * @param peer peer identity this notification is about
580  */
581 static void
582 connect_handler (void *cls,
583                  const struct
584                  GNUNET_PeerIdentity * peer)
585 {
586   connection_count++;
587 }
588
589
590 /**
591  * Method called whenever a given peer connects.
592  *
593  * @param cls closure
594  * @param peer peer identity this notification is about
595  */
596 static void
597 disconnect_handler (void *cls,
598                     const struct
599                     GNUNET_PeerIdentity * peer)
600 {
601   connection_count--;
602 }
603
604
605 /**
606  * Continuation called by the statistics code once 
607  * we go the stat.  Initiates hostlist download scheduling.
608  *
609  * @param cls closure
610  * @param success GNUNET_OK if statistics were
611  *        successfully obtained, GNUNET_SYSERR if not.
612  */
613 static void
614 primary_task (void *cls, int success)
615 {
616   if (stats == NULL)
617     return; /* in shutdown */
618   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
619               "Statistics request done, scheduling hostlist download\n");
620   schedule_hostlist_task ();
621 }
622
623
624 static int
625 process_stat (void *cls,
626               const char *subsystem,
627               const char *name,
628               uint64_t value,
629               int is_persistent)
630 {
631   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
632               _("Initial time between hostlist downloads is %llums\n"),
633               (unsigned long long) value);
634   hostlist_delay.value = value;
635   return GNUNET_OK;
636 }
637
638
639 /**
640  * Start downloading hostlists from hostlist servers as necessary.
641  */
642 int
643 GNUNET_HOSTLIST_client_start (const struct GNUNET_CONFIGURATION_Handle *c,
644                               struct GNUNET_SCHEDULER_Handle *s,
645                               struct GNUNET_STATISTICS_Handle *st,
646                               GNUNET_CORE_ClientEventHandler *ch,
647                               GNUNET_CORE_ClientEventHandler *dh)
648 {
649   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
650     {
651       GNUNET_break (0);
652       return GNUNET_SYSERR;
653     }
654   cfg = c;
655   sched = s;
656   stats = st;
657   if (GNUNET_OK !=
658       GNUNET_CONFIGURATION_get_value_string (cfg,
659                                              "HOSTLIST",
660                                              "HTTP-PROXY", 
661                                              &proxy))
662     proxy = NULL;
663   *ch = &connect_handler;
664   *dh = &disconnect_handler;
665   GNUNET_STATISTICS_get (stats,
666                          "hostlist",
667                          gettext_noop("Minimum time between hostlist downloads"),
668                          GNUNET_TIME_UNIT_MINUTES,
669                          &primary_task,
670                          &process_stat,
671                          NULL);
672   return GNUNET_OK;
673 }
674
675
676 /**
677  * Stop downloading hostlists from hostlist servers as necessary.
678  */
679 void
680 GNUNET_HOSTLIST_client_stop ()
681 {
682   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
683               "Hostlist client shutdown\n");
684   if (current_task != GNUNET_SCHEDULER_NO_TASK)
685     {
686       GNUNET_SCHEDULER_cancel (sched,
687                                current_task);
688       if (transport != NULL)
689         {
690           GNUNET_TRANSPORT_disconnect (transport);
691           transport = NULL;
692         }
693       curl_global_cleanup ();
694     }
695   GNUNET_assert (NULL == transport);
696   GNUNET_free_non_null (proxy);
697   proxy = NULL;
698   cfg = NULL;
699   sched = NULL;
700   stats = NULL;
701 }
702
703 /* end of hostlist-client.c */