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_statistics_service.h"
32 #include "gnunet_transport_service.h"
33 #include <curl/curl.h>
35 #define DEBUG_HOSTLIST_CLIENT GNUNET_YES
38 * Number of connections that we must have to NOT download
41 #define MIN_CONNECTIONS 4
46 static const struct GNUNET_CONFIGURATION_Handle *cfg;
51 static struct GNUNET_SCHEDULER_Handle *sched;
56 struct GNUNET_STATISTICS_Handle *stats;
61 struct GNUNET_TRANSPORT_Handle *transport;
64 * Proxy that we are using (can be NULL).
69 * Buffer for data downloaded via HTTP.
71 static char download_buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
74 * Number of bytes valid in 'download_buffer'.
76 static size_t download_pos;
79 * Current URL that we are using.
81 static char *current_url;
84 * Current CURL handle.
89 * Current multi-CURL handle.
94 * ID of the current task scheduled.
96 static GNUNET_SCHEDULER_TaskIdentifier current_task;
99 * Amount of time we wait between hostlist downloads.
101 static struct GNUNET_TIME_Relative hostlist_delay;
104 * Set to GNUNET_YES if the current URL had some problems.
106 static int bogus_url;
109 * Number of active connections (according to core service).
111 static unsigned int connection_count;
114 * At what time MUST the current hostlist request be done?
116 static struct GNUNET_TIME_Absolute end_time;
120 * Process downloaded bits by calling callback on each HELLO.
122 * @param ptr buffer with downloaded data
123 * @param size size of a record
124 * @param nmemb number of records downloaded
126 * @return number of bytes that were processed (always size*nmemb)
129 download_hostlist_processor (void *ptr,
134 const char * cbuf = ptr;
135 const struct GNUNET_MessageHeader *msg;
141 total = size * nmemb;
142 if ( (total == 0) || (bogus_url) )
144 return total; /* ok, no data or bogus data */
146 GNUNET_STATISTICS_update (stats,
147 gettext_noop ("# bytes downloaded from hostlist servers"),
151 while ( (left > 0) ||
154 cpy = GNUNET_MIN (left, GNUNET_SERVER_MAX_MESSAGE_SIZE - download_pos);
155 memcpy (&download_buffer[download_pos],
161 if (download_pos < sizeof(struct GNUNET_MessageHeader))
163 GNUNET_assert (left == 0);
166 msg = (const struct GNUNET_MessageHeader *) download_buffer;
167 msize = ntohs(msg->size);
168 if (msize < sizeof(struct GNUNET_MessageHeader))
170 GNUNET_STATISTICS_update (stats,
171 gettext_noop ("# invalid HELLOs downloaded from hostlist servers"),
174 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
175 _("Invalid `%s' message received from hostlist at `%s'\n"),
181 if (download_pos < msize)
183 GNUNET_assert (left == 0);
186 if (GNUNET_HELLO_size ((const struct GNUNET_HELLO_Message*)msg) == msize)
188 #if DEBUG_HOSTLIST_CLIENT
189 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
190 "Received valid `%s' message from hostlist server.\n",
193 GNUNET_STATISTICS_update (stats,
194 gettext_noop ("# valid HELLOs downloaded from hostlist servers"),
197 GNUNET_TRANSPORT_offer_hello (transport, msg);
201 GNUNET_STATISTICS_update (stats,
202 gettext_noop ("# invalid HELLOs downloaded from hostlist servers"),
205 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
206 _("Invalid `%s' message received from hostlist at `%s'\n"),
209 bogus_url = GNUNET_YES;
212 memmove (download_buffer,
213 &download_buffer[msize],
214 download_pos - msize);
215 download_pos -= msize;
222 * Obtain a hostlist URL that we should use.
224 * @return NULL if there is no URL available
235 GNUNET_CONFIGURATION_get_value_string (cfg,
240 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
241 _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
242 "SERVERS", "HOSTLIST");
247 if (strlen (servers) > 0)
250 pos = strlen (servers) - 1;
253 if (servers[pos] == ' ')
260 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
261 _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
262 "SERVERS", "HOSTLIST");
263 GNUNET_free (servers);
267 urls = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, urls) + 1;
268 pos = strlen (servers) - 1;
271 if (servers[pos] == ' ')
283 ret = GNUNET_strdup (&servers[pos]);
284 GNUNET_free (servers);
289 #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);
293 * Schedule the background task that will (possibly)
294 * download a hostlist.
297 schedule_hostlist_task (void);
301 * Clean up the state from the task that downloaded the
302 * hostlist and schedule the next task.
311 mret = curl_multi_remove_handle (multi, curl);
312 if (mret != CURLM_OK)
314 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
315 _("%s failed at %s:%d: `%s'\n"),
316 "curl_multi_remove_handle", __FILE__, __LINE__,
317 curl_multi_strerror (mret));
319 mret = curl_multi_cleanup (multi);
320 if (mret != CURLM_OK)
321 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
322 _("%s failed at %s:%d: `%s'\n"),
323 "curl_multi_cleanup", __FILE__, __LINE__,
324 curl_multi_strerror (mret));
329 curl_easy_cleanup (curl);
332 GNUNET_free_non_null (current_url);
334 schedule_hostlist_task ();
339 * Ask CURL for the select set and then schedule the
340 * receiving task with the scheduler.
347 * Task that is run when we are ready to receive more data from the hostlist
350 * @param cls closure, unused
351 * @param tc task context, unused
354 multi_ready (void *cls,
355 const struct GNUNET_SCHEDULER_TaskContext *tc)
361 if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
363 #if DEBUG_HOSTLIST_CLIENT
364 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
365 "Shutdown requested while trying to download hostlist from `%s'\n",
371 if (GNUNET_TIME_absolute_get_remaining (end_time).value == 0)
373 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
374 _("Timeout trying to download hostlist from `%s'\n"),
379 #if DEBUG_HOSTLIST_CLIENT
380 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
381 "Ready for processing hostlist client request\n");
386 mret = curl_multi_perform (multi, &running);
391 msg = curl_multi_info_read (multi, &running);
392 GNUNET_break (msg != NULL);
398 if ( (msg->data.result != CURLE_OK) &&
399 (msg->data.result != CURLE_GOT_NOTHING) )
400 GNUNET_log(GNUNET_ERROR_TYPE_INFO,
401 _("%s failed for `%s' at %s:%d: `%s'\n"),
402 "curl_multi_perform",
406 curl_easy_strerror (msg->data.result));
408 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
409 _("Download of hostlist `%s' completed.\n"),
420 while (mret == CURLM_CALL_MULTI_PERFORM);
421 if (mret != CURLM_OK)
423 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
424 _("%s failed at %s:%d: `%s'\n"),
425 "curl_multi_perform", __FILE__, __LINE__,
426 curl_multi_strerror (mret));
434 * Ask CURL for the select set and then schedule the
435 * receiving task with the scheduler.
445 struct GNUNET_NETWORK_FDSet *grs;
446 struct GNUNET_NETWORK_FDSet *gws;
448 struct GNUNET_TIME_Relative rtime;
454 mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
455 if (mret != CURLM_OK)
457 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
458 _("%s failed at %s:%d: `%s'\n"),
459 "curl_multi_fdset", __FILE__, __LINE__,
460 curl_multi_strerror (mret));
464 mret = curl_multi_timeout (multi, &timeout);
465 if (mret != CURLM_OK)
467 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
468 _("%s failed at %s:%d: `%s'\n"),
469 "curl_multi_timeout", __FILE__, __LINE__,
470 curl_multi_strerror (mret));
474 rtime = GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_remaining (end_time),
475 GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
477 grs = GNUNET_NETWORK_fdset_create ();
478 gws = GNUNET_NETWORK_fdset_create ();
479 GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
480 GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
481 #if DEBUG_HOSTLIST_CLIENT
482 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
483 "Scheduling task for hostlist download using cURL\n");
486 = GNUNET_SCHEDULER_add_select (sched,
487 GNUNET_SCHEDULER_PRIORITY_DEFAULT,
488 GNUNET_SCHEDULER_NO_TASK,
494 GNUNET_NETWORK_fdset_destroy (gws);
495 GNUNET_NETWORK_fdset_destroy (grs);
500 * Main function that will download a hostlist and process its
509 curl = curl_easy_init ();
517 current_url = get_url ();
518 GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
519 _("Bootstrapping using hostlist at `%s'.\n"),
521 GNUNET_STATISTICS_update (stats,
522 gettext_noop ("# hostlist downloads initiated"),
526 CURL_EASY_SETOPT (curl, CURLOPT_PROXY, proxy);
529 CURL_EASY_SETOPT (curl,
530 CURLOPT_WRITEFUNCTION,
531 &download_hostlist_processor);
537 CURL_EASY_SETOPT (curl,
545 CURL_EASY_SETOPT (curl, CURLOPT_FOLLOWLOCATION, 1);
546 CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 4);
547 /* no need to abort if the above failed */
548 CURL_EASY_SETOPT (curl,
556 CURL_EASY_SETOPT (curl,
560 CURL_EASY_SETOPT (curl,
564 CURL_EASY_SETOPT (curl,
566 GNUNET_SERVER_MAX_MESSAGE_SIZE);
567 if (0 == strncmp (current_url, "http", 4))
568 CURL_EASY_SETOPT (curl, CURLOPT_USERAGENT, "GNUnet");
569 CURL_EASY_SETOPT (curl,
570 CURLOPT_CONNECTTIMEOUT,
572 CURL_EASY_SETOPT (curl,
576 /* this should no longer be needed; we're now single-threaded! */
577 CURL_EASY_SETOPT (curl,
581 multi = curl_multi_init ();
588 mret = curl_multi_add_handle (multi, curl);
589 if (mret != CURLM_OK)
591 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
592 _("%s failed at %s:%d: `%s'\n"),
593 "curl_multi_add_handle", __FILE__, __LINE__,
594 curl_multi_strerror (mret));
595 mret = curl_multi_cleanup (multi);
596 if (mret != CURLM_OK)
597 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
598 _("%s failed at %s:%d: `%s'\n"),
599 "curl_multi_cleanup", __FILE__, __LINE__,
600 curl_multi_strerror (mret));
605 end_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES);
611 * Task that checks if we should try to download a hostlist.
612 * If so, we initiate the download, otherwise we schedule
613 * this task again for a later time.
616 check_task (void *cls,
617 const struct GNUNET_SCHEDULER_TaskContext *tc)
619 current_task = GNUNET_SCHEDULER_NO_TASK;
620 if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
622 if (connection_count < MIN_CONNECTIONS)
623 download_hostlist ();
625 schedule_hostlist_task ();
630 * Compute when we should check the next time about downloading
631 * a hostlist; then schedule the task accordingly.
634 schedule_hostlist_task ()
637 struct GNUNET_TIME_Relative delay;
641 curl_global_cleanup ();
642 return; /* in shutdown */
644 delay = hostlist_delay;
645 if (hostlist_delay.value == 0)
646 hostlist_delay = GNUNET_TIME_UNIT_SECONDS;
648 hostlist_delay = GNUNET_TIME_relative_multiply (hostlist_delay, 2);
649 if (hostlist_delay.value > GNUNET_TIME_UNIT_HOURS.value * (1 + connection_count))
650 hostlist_delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS,
651 (1 + connection_count));
652 GNUNET_STATISTICS_set (stats,
653 gettext_noop("# seconds between hostlist downloads"),
654 hostlist_delay.value,
658 delay = GNUNET_TIME_UNIT_ZERO;
661 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
662 _("Have %u/%u connections. Will consider downloading hostlist in %llums\n"),
665 (unsigned long long) delay.value);
666 current_task = GNUNET_SCHEDULER_add_delayed (sched,
674 * Method called whenever a given peer connects.
677 * @param peer peer identity this notification is about
678 * @param latency reported latency of the connection with 'other'
679 * @param distance reported distance (DV) to 'other'
682 connect_handler (void *cls,
684 GNUNET_PeerIdentity * peer,
685 struct GNUNET_TIME_Relative latency,
689 GNUNET_STATISTICS_update (stats,
690 gettext_noop ("# active connections"),
697 * Method called whenever a given peer disconnects.
700 * @param peer peer identity this notification is about
703 disconnect_handler (void *cls,
705 GNUNET_PeerIdentity * peer)
708 GNUNET_STATISTICS_update (stats,
709 gettext_noop ("# active connections"),
715 * Method called whenever an advertisement message arrives.
717 * @param cls closure (always NULL)
718 * @param client identification of the client
719 * @param message the actual message
720 * @return GNUNET_OK to keep the connection open,
721 * GNUNET_SYSERR to close it (signal serious error)
724 advertisement_handler (void *cls,
725 const struct GNUNET_PeerIdentity * peer,
726 const struct GNUNET_MessageHeader * message,
727 struct GNUNET_TIME_Relative latency,
730 int size = ntohs (message->size);
731 int type = ntohs (message->type);
732 if ( type != GNUNET_MESSAGE_TYPE_HOSTLIST_ADVERTISEMENT)
734 #if DEBUG_HOSTLIST_CLIENT
735 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
736 "Hostlist client recieved advertisement message, size %u, type %u\n",size,type);
743 * Continuation called by the statistics code once
744 * we go the stat. Initiates hostlist download scheduling.
747 * @param success GNUNET_OK if statistics were
748 * successfully obtained, GNUNET_SYSERR if not.
751 primary_task (void *cls, int success)
754 return; /* in shutdown */
755 #if DEBUG_HOSTLIST_CLIENT
756 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
757 "Statistics request done, scheduling hostlist download\n");
759 schedule_hostlist_task ();
764 process_stat (void *cls,
765 const char *subsystem,
770 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
771 _("Initial time between hostlist downloads is %llums\n"),
772 (unsigned long long) value);
773 hostlist_delay.value = value;
779 * Start downloading hostlists from hostlist servers as necessary.
782 GNUNET_HOSTLIST_client_start (const struct GNUNET_CONFIGURATION_Handle *c,
783 struct GNUNET_SCHEDULER_Handle *s,
784 struct GNUNET_STATISTICS_Handle *st,
785 GNUNET_CORE_ConnectEventHandler *ch,
786 GNUNET_CORE_DisconnectEventHandler *dh,
787 GNUNET_CORE_MessageCallback *msgh)
789 if (0 != curl_global_init (CURL_GLOBAL_WIN32))
792 return GNUNET_SYSERR;
794 transport = GNUNET_TRANSPORT_connect (s, c, NULL, NULL, NULL, NULL);
795 if (NULL == transport)
797 curl_global_cleanup ();
798 return GNUNET_SYSERR;
804 GNUNET_CONFIGURATION_get_value_string (cfg,
809 *ch = &connect_handler;
810 *dh = &disconnect_handler;
811 *msgh = &advertisement_handler;
812 GNUNET_STATISTICS_get (stats,
814 gettext_noop("# seconds between hostlist downloads"),
815 GNUNET_TIME_UNIT_MINUTES,
824 * Stop downloading hostlists from hostlist servers as necessary.
827 GNUNET_HOSTLIST_client_stop ()
829 #if DEBUG_HOSTLIST_CLIENT
830 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
831 "Hostlist client shutdown\n");
833 if (current_task != GNUNET_SCHEDULER_NO_TASK)
835 GNUNET_SCHEDULER_cancel (sched,
837 curl_global_cleanup ();
839 if (transport != NULL)
841 GNUNET_TRANSPORT_disconnect (transport);
844 GNUNET_assert (NULL == transport);
845 GNUNET_free_non_null (proxy);
851 /* end of hostlist-client.c */