2 This file is part of GNUnet.
3 (C) 2001, 2002, 2003, 2004, 2005, 2006, 2009, 2010 Christian Grothoff (and other contributing authors)
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.
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.
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.
22 * @file hostlist/hostlist-client.c
23 * @brief hostlist support. Downloads HELLOs via HTTP.
24 * @author Christian Grothoff
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>
34 #define DEBUG_HOSTLIST_CLIENT GNUNET_NO
37 * Number of connections that we must have to NOT download
40 #define MIN_CONNECTIONS 4
45 static const struct GNUNET_CONFIGURATION_Handle *cfg;
50 static struct GNUNET_SCHEDULER_Handle *sched;
55 struct GNUNET_STATISTICS_Handle *stats;
60 struct GNUNET_TRANSPORT_Handle *transport;
63 * Proxy that we are using (can be NULL).
68 * Buffer for data downloaded via HTTP.
70 static char download_buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
73 * Number of bytes valid in 'download_buffer'.
75 static size_t download_pos;
78 * Current URL that we are using.
80 static char *current_url;
83 * Current CURL handle.
88 * Current multi-CURL handle.
93 * ID of the current task scheduled.
95 static GNUNET_SCHEDULER_TaskIdentifier current_task;
98 * Amount of time we wait between hostlist downloads.
100 static struct GNUNET_TIME_Relative hostlist_delay;
103 * Set to GNUNET_YES if the current URL had some problems.
105 static int bogus_url;
108 * Number of active connections (according to core service).
110 static unsigned int connection_count;
113 * At what time MUST the current hostlist request be done?
115 static struct GNUNET_TIME_Absolute end_time;
119 * Process downloaded bits by calling callback on each HELLO.
121 * @param ptr buffer with downloaded data
122 * @param size size of a record
123 * @param nmemb number of records downloaded
125 * @return number of bytes that were processed (always size*nmemb)
128 download_hostlist_processor (void *ptr,
133 const char * cbuf = ptr;
134 const struct GNUNET_MessageHeader *msg;
140 total = size * nmemb;
141 if ( (total == 0) || (bogus_url) )
143 return total; /* ok, no data or bogus data */
148 cpy = GNUNET_MIN (total, GNUNET_SERVER_MAX_MESSAGE_SIZE - download_pos);
149 GNUNET_assert (cpy > 0);
150 memcpy (&download_buffer[download_pos],
156 if (download_pos < sizeof(struct GNUNET_MessageHeader))
158 msg = (const struct GNUNET_MessageHeader *) download_buffer;
159 msize = ntohs(msg->size);
160 if (msize < sizeof(struct GNUNET_MessageHeader))
162 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
163 _("Invalid `%s' message received from hostlist at `%s'\n"),
169 if (download_pos < msize)
171 if (GNUNET_HELLO_size ((const struct GNUNET_HELLO_Message*)msg) == msize)
173 #if DEBUG_HOSTLIST_CLIENT
174 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
175 "Received valid `%s' message from hostlist server.\n",
178 GNUNET_TRANSPORT_offer_hello (transport, msg);
182 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
183 _("Invalid `%s' message received from hostlist at `%s'\n"),
189 memmove (download_buffer,
190 &download_buffer[msize],
191 download_pos - msize);
192 download_pos -= msize;
199 * Obtain a hostlist URL that we should use.
201 * @return NULL if there is no URL available
212 GNUNET_CONFIGURATION_get_value_string (cfg,
217 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
218 _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
219 "SERVERS", "HOSTLIST");
224 if (strlen (servers) > 0)
227 pos = strlen (servers) - 1;
230 if (servers[pos] == ' ')
237 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
238 _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
239 "SERVERS", "HOSTLIST");
240 GNUNET_free (servers);
244 urls = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, urls) + 1;
245 pos = strlen (servers) - 1;
248 if (servers[pos] == ' ')
260 ret = GNUNET_strdup (&servers[pos]);
261 GNUNET_free (servers);
266 #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);
270 * Schedule the background task that will (possibly)
271 * download a hostlist.
274 schedule_hostlist_task (void);
278 * Clean up the state from the task that downloaded the
279 * hostlist and schedule the next task.
288 mret = curl_multi_remove_handle (multi, curl);
289 if (mret != CURLM_OK)
291 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
292 _("%s failed at %s:%d: `%s'\n"),
293 "curl_multi_remove_handle", __FILE__, __LINE__,
294 curl_multi_strerror (mret));
296 mret = curl_multi_cleanup (multi);
297 if (mret != CURLM_OK)
298 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
299 _("%s failed at %s:%d: `%s'\n"),
300 "curl_multi_cleanup", __FILE__, __LINE__,
301 curl_multi_strerror (mret));
306 curl_easy_cleanup (curl);
309 GNUNET_free_non_null (current_url);
311 schedule_hostlist_task ();
316 * Ask CURL for the select set and then schedule the
317 * receiving task with the scheduler.
324 * Task that is run when we are ready to receive more data from the hostlist
327 * @param cls closure, unused
328 * @param tc task context, unused
331 multi_ready (void *cls,
332 const struct GNUNET_SCHEDULER_TaskContext *tc)
338 if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
340 #if DEBUG_HOSTLIST_CLIENT
341 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
342 "Shutdown requested while trying to download hostlist from `%s'\n",
348 if (GNUNET_TIME_absolute_get_remaining (end_time).value == 0)
350 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
351 _("Timeout trying to download hostlist from `%s'\n"),
356 #if DEBUG_HOSTLIST_CLIENT
357 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
358 "Ready for processing hostlist client request\n");
363 mret = curl_multi_perform (multi, &running);
368 msg = curl_multi_info_read (multi, &running);
369 GNUNET_break (msg != NULL);
375 if ( (msg->data.result != CURLE_OK) &&
376 (msg->data.result != CURLE_GOT_NOTHING) )
377 GNUNET_log(GNUNET_ERROR_TYPE_INFO,
378 _("%s failed for `%s' at %s:%d: `%s'\n"),
379 "curl_multi_perform",
383 curl_easy_strerror (msg->data.result));
385 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
386 _("Download of hostlist `%s' completed.\n"),
397 while (mret == CURLM_CALL_MULTI_PERFORM);
398 if (mret != CURLM_OK)
400 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
401 _("%s failed at %s:%d: `%s'\n"),
402 "curl_multi_perform", __FILE__, __LINE__,
403 curl_multi_strerror (mret));
411 * Ask CURL for the select set and then schedule the
412 * receiving task with the scheduler.
422 struct GNUNET_NETWORK_FDSet *grs;
423 struct GNUNET_NETWORK_FDSet *gws;
425 struct GNUNET_TIME_Relative rtime;
431 mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
432 if (mret != CURLM_OK)
434 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
435 _("%s failed at %s:%d: `%s'\n"),
436 "curl_multi_fdset", __FILE__, __LINE__,
437 curl_multi_strerror (mret));
441 mret = curl_multi_timeout (multi, &timeout);
442 if (mret != CURLM_OK)
444 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
445 _("%s failed at %s:%d: `%s'\n"),
446 "curl_multi_timeout", __FILE__, __LINE__,
447 curl_multi_strerror (mret));
451 rtime = GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_remaining (end_time),
452 GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
454 grs = GNUNET_NETWORK_fdset_create ();
455 gws = GNUNET_NETWORK_fdset_create ();
456 GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
457 GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
458 #if DEBUG_HOSTLIST_CLIENT
459 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
460 "Scheduling task for hostlist download using cURL\n");
463 = GNUNET_SCHEDULER_add_select (sched,
464 GNUNET_SCHEDULER_PRIORITY_DEFAULT,
465 GNUNET_SCHEDULER_NO_TASK,
471 GNUNET_NETWORK_fdset_destroy (gws);
472 GNUNET_NETWORK_fdset_destroy (grs);
477 * Main function that will download a hostlist and process its
486 curl = curl_easy_init ();
494 current_url = get_url ();
495 GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
496 _("Bootstrapping using hostlist at `%s'.\n"),
500 CURL_EASY_SETOPT (curl, CURLOPT_PROXY, proxy);
503 CURL_EASY_SETOPT (curl,
504 CURLOPT_WRITEFUNCTION,
505 &download_hostlist_processor);
511 CURL_EASY_SETOPT (curl,
519 CURL_EASY_SETOPT (curl, CURLOPT_FOLLOWLOCATION, 1);
520 CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 4);
521 /* no need to abort if the above failed */
522 CURL_EASY_SETOPT (curl,
530 CURL_EASY_SETOPT (curl,
534 CURL_EASY_SETOPT (curl,
538 CURL_EASY_SETOPT (curl,
540 GNUNET_SERVER_MAX_MESSAGE_SIZE);
541 if (0 == strncmp (current_url, "http", 4))
542 CURL_EASY_SETOPT (curl, CURLOPT_USERAGENT, "GNUnet");
543 CURL_EASY_SETOPT (curl,
544 CURLOPT_CONNECTTIMEOUT,
546 CURL_EASY_SETOPT (curl,
550 /* this should no longer be needed; we're now single-threaded! */
551 CURL_EASY_SETOPT (curl,
555 multi = curl_multi_init ();
562 mret = curl_multi_add_handle (multi, curl);
563 if (mret != CURLM_OK)
565 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
566 _("%s failed at %s:%d: `%s'\n"),
567 "curl_multi_add_handle", __FILE__, __LINE__,
568 curl_multi_strerror (mret));
569 mret = curl_multi_cleanup (multi);
570 if (mret != CURLM_OK)
571 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
572 _("%s failed at %s:%d: `%s'\n"),
573 "curl_multi_cleanup", __FILE__, __LINE__,
574 curl_multi_strerror (mret));
579 end_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES);
585 * Task that checks if we should try to download a hostlist.
586 * If so, we initiate the download, otherwise we schedule
587 * this task again for a later time.
590 check_task (void *cls,
591 const struct GNUNET_SCHEDULER_TaskContext *tc)
593 current_task = GNUNET_SCHEDULER_NO_TASK;
594 if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
596 if (connection_count < MIN_CONNECTIONS)
597 download_hostlist ();
599 schedule_hostlist_task ();
604 * Compute when we should check the next time about downloading
605 * a hostlist; then schedule the task accordingly.
608 schedule_hostlist_task ()
610 struct GNUNET_TIME_Relative delay;
614 curl_global_cleanup ();
615 return; /* in shutdown */
617 delay = hostlist_delay;
618 if (hostlist_delay.value == 0)
619 hostlist_delay = GNUNET_TIME_UNIT_SECONDS;
621 hostlist_delay = GNUNET_TIME_relative_multiply (hostlist_delay, 2);
622 if (hostlist_delay.value > GNUNET_TIME_UNIT_HOURS.value * (1 + connection_count))
623 hostlist_delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS,
624 (1 + connection_count));
625 GNUNET_STATISTICS_set (stats,
626 gettext_noop("Minimum time between hostlist downloads"),
627 hostlist_delay.value,
629 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
630 _("Have %u/%u connections. Will consider downloading hostlist in %llums\n"),
633 (unsigned long long) delay.value);
634 current_task = GNUNET_SCHEDULER_add_delayed (sched,
642 * Method called whenever a given peer connects.
645 * @param peer peer identity this notification is about
646 * @param latency reported latency of the connection with 'other'
647 * @param distance reported distance (DV) to 'other'
650 connect_handler (void *cls,
652 GNUNET_PeerIdentity * peer,
653 struct GNUNET_TIME_Relative latency,
661 * Method called whenever a given peer connects.
664 * @param peer peer identity this notification is about
667 disconnect_handler (void *cls,
669 GNUNET_PeerIdentity * peer)
676 * Continuation called by the statistics code once
677 * we go the stat. Initiates hostlist download scheduling.
680 * @param success GNUNET_OK if statistics were
681 * successfully obtained, GNUNET_SYSERR if not.
684 primary_task (void *cls, int success)
687 return; /* in shutdown */
688 #if DEBUG_HOSTLIST_CLIENT
689 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
690 "Statistics request done, scheduling hostlist download\n");
692 schedule_hostlist_task ();
697 process_stat (void *cls,
698 const char *subsystem,
703 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
704 _("Initial time between hostlist downloads is %llums\n"),
705 (unsigned long long) value);
706 hostlist_delay.value = value;
712 * Start downloading hostlists from hostlist servers as necessary.
715 GNUNET_HOSTLIST_client_start (const struct GNUNET_CONFIGURATION_Handle *c,
716 struct GNUNET_SCHEDULER_Handle *s,
717 struct GNUNET_STATISTICS_Handle *st,
718 GNUNET_CORE_ConnectEventHandler *ch,
719 GNUNET_CORE_DisconnectEventHandler *dh)
721 if (0 != curl_global_init (CURL_GLOBAL_WIN32))
724 return GNUNET_SYSERR;
726 transport = GNUNET_TRANSPORT_connect (s, c, NULL, NULL, NULL, NULL);
727 if (NULL == transport)
729 curl_global_cleanup ();
730 return GNUNET_SYSERR;
736 GNUNET_CONFIGURATION_get_value_string (cfg,
741 *ch = &connect_handler;
742 *dh = &disconnect_handler;
743 GNUNET_STATISTICS_get (stats,
745 gettext_noop("Minimum time between hostlist downloads"),
746 GNUNET_TIME_UNIT_MINUTES,
755 * Stop downloading hostlists from hostlist servers as necessary.
758 GNUNET_HOSTLIST_client_stop ()
760 #if DEBUG_HOSTLIST_CLIENT
761 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
762 "Hostlist client shutdown\n");
764 if (current_task != GNUNET_SCHEDULER_NO_TASK)
766 GNUNET_SCHEDULER_cancel (sched,
768 curl_global_cleanup ();
770 if (transport != NULL)
772 GNUNET_TRANSPORT_disconnect (transport);
775 GNUNET_assert (NULL == transport);
776 GNUNET_free_non_null (proxy);
783 /* end of hostlist-client.c */