fixing shutdown
[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  */
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 #define DEBUG_HOSTLIST_CLIENT GNUNET_NO
35
36 /**
37  * Number of connections that we must have to NOT download
38  * hostlists anymore.
39  */
40 #define MIN_CONNECTIONS 4
41
42 /**
43  * Our configuration.
44  */
45 static const struct GNUNET_CONFIGURATION_Handle *cfg;
46
47 /**
48  * Our scheduler.
49  */
50 static struct GNUNET_SCHEDULER_Handle *sched;
51
52 /**
53  * Statistics handle.
54  */
55 struct GNUNET_STATISTICS_Handle *stats; 
56
57 /**
58  * Transport handle.
59  */
60 struct GNUNET_TRANSPORT_Handle *transport;
61                        
62 /**
63  * Proxy that we are using (can be NULL).
64  */
65 static char *proxy;
66
67 /**
68  * Buffer for data downloaded via HTTP.
69  */
70 static char download_buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
71
72 /**
73  * Number of bytes valid in 'download_buffer'.
74  */
75 static size_t download_pos;
76
77 /**
78  * Current URL that we are using.
79  */
80 static char *current_url;
81
82 /**
83  * Current CURL handle.
84  */
85 static CURL *curl;
86
87 /**
88  * Current multi-CURL handle.
89  */
90 static CURLM *multi;
91
92 /**
93  * ID of the current task scheduled.
94  */
95 static GNUNET_SCHEDULER_TaskIdentifier current_task;
96
97 /**
98  * Amount of time we wait between hostlist downloads.
99  */
100 static struct GNUNET_TIME_Relative hostlist_delay;
101
102 /**
103  * Set to GNUNET_YES if the current URL had some problems.
104  */ 
105 static int bogus_url;
106
107 /**
108  * Number of active connections (according to core service).
109  */
110 static unsigned int connection_count;
111
112 /**
113  * At what time MUST the current hostlist request be done?
114  */
115 static struct GNUNET_TIME_Absolute end_time;
116
117
118 /**
119  * Process downloaded bits by calling callback on each HELLO.
120  *
121  * @param ptr buffer with downloaded data
122  * @param size size of a record
123  * @param nmemb number of records downloaded
124  * @param ctx unused
125  * @return number of bytes that were processed (always size*nmemb)
126  */
127 static size_t
128 download_hostlist_processor (void *ptr, 
129                              size_t size, 
130                              size_t nmemb, 
131                              void *ctx)
132 {
133   const char * cbuf = ptr;
134   const struct GNUNET_MessageHeader *msg;
135   size_t total;
136   size_t cpy;
137   size_t left;
138   uint16_t msize;
139
140   total = size * nmemb;
141   if ( (total == 0) || (bogus_url) )
142     {
143       return total;  /* ok, no data or bogus data */
144     }
145   left = total;
146   while (left > 0)
147     {
148       cpy = GNUNET_MIN (total, GNUNET_SERVER_MAX_MESSAGE_SIZE - download_pos);
149       GNUNET_assert (cpy > 0);
150       memcpy (&download_buffer[download_pos],
151               cbuf,
152               cpy);
153       cbuf += cpy;
154       download_pos += cpy;
155       left -= cpy;
156       if (download_pos < sizeof(struct GNUNET_MessageHeader))
157         break;
158       msg = (const struct GNUNET_MessageHeader *) download_buffer;
159       msize = ntohs(msg->size);
160       if (msize < sizeof(struct GNUNET_MessageHeader))
161         {        
162           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
163                       _("Invalid `%s' message received from hostlist at `%s'\n"),
164                       "HELLO",
165                       current_url); 
166           bogus_url = 1;
167           return total;
168         }
169       if (download_pos < msize)
170         break;
171       if (GNUNET_HELLO_size ((const struct GNUNET_HELLO_Message*)msg) == msize)
172         {
173 #if DEBUG_HOSTLIST_CLIENT
174           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
175                       "Received valid `%s' message from hostlist server.\n",
176                       "HELLO");
177 #endif
178           GNUNET_TRANSPORT_offer_hello (transport, msg);
179         }
180       else
181         {
182           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
183                       _("Invalid `%s' message received from hostlist at `%s'\n"),
184                       "HELLO",
185                       current_url);
186           bogus_url = 1;
187           return total;
188         }
189       memmove (download_buffer,
190                &download_buffer[msize],
191                download_pos - msize);
192       download_pos -= msize;
193     }
194   return total;
195 }
196
197
198 /**
199  * Obtain a hostlist URL that we should use.
200  *
201  * @return NULL if there is no URL available
202  */
203 static char *
204 get_url ()
205 {
206   char *servers;
207   char *ret;
208   size_t urls;
209   size_t pos;
210
211   if (GNUNET_OK != 
212       GNUNET_CONFIGURATION_get_value_string (cfg,
213                                              "HOSTLIST",
214                                              "SERVERS",
215                                              &servers))
216     {
217       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
218                   _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
219                   "SERVERS", "HOSTLIST");
220       return NULL;
221     }
222
223   urls = 0;
224   if (strlen (servers) > 0)
225     {
226       urls++;
227       pos = strlen (servers) - 1;
228       while (pos > 0)
229         {
230           if (servers[pos] == ' ')
231             urls++;
232           pos--;
233         }
234     }
235   if (urls == 0)
236     {
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);
241       return NULL;
242     }
243
244   urls = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, urls) + 1;
245   pos = strlen (servers) - 1;
246   while (pos > 0)
247     {
248       if (servers[pos] == ' ')
249         {
250           urls--;
251           servers[pos] = '\0';
252         }
253       if (urls == 0)
254         {
255           pos++;
256           break;
257         }
258       pos--;    
259     }
260   ret = GNUNET_strdup (&servers[pos]);
261   GNUNET_free (servers);
262   return ret;
263 }
264
265
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);
267
268
269 /**
270  * Schedule the background task that will (possibly)
271  * download a hostlist.
272  */
273 static void
274 schedule_hostlist_task (void);
275
276
277 /**
278  * Clean up the state from the task that downloaded the
279  * hostlist and schedule the next task.
280  */
281 static void 
282 clean_up ()
283 {
284   CURLMcode mret;
285
286   if (multi != NULL)
287     {
288       mret = curl_multi_remove_handle (multi, curl);
289       if (mret != CURLM_OK)
290         {
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));
295         }
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));
302       multi = NULL;
303     }
304   if (curl != NULL)
305     {
306       curl_easy_cleanup (curl);
307       curl = NULL;
308     }  
309   GNUNET_free_non_null (current_url);
310   current_url = NULL;
311   schedule_hostlist_task ();
312 }
313
314
315 /**
316  * Ask CURL for the select set and then schedule the
317  * receiving task with the scheduler.
318  */
319 static void
320 run_multi ();
321
322
323 /**
324  * Task that is run when we are ready to receive more data from the hostlist
325  * server. 
326  *
327  * @param cls closure, unused
328  * @param tc task context, unused
329  */
330 static void
331 multi_ready (void *cls,
332              const struct GNUNET_SCHEDULER_TaskContext *tc)
333 {
334   int running;
335   struct CURLMsg *msg;
336   CURLMcode mret;
337   
338   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
339     {
340 #if DEBUG_HOSTLIST_CLIENT
341       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
342                   "Shutdown requested while trying to download hostlist from `%s'\n",
343                   current_url);
344 #endif
345       clean_up ();
346       return;
347     }
348   if (GNUNET_TIME_absolute_get_remaining (end_time).value == 0)
349     {
350       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
351                   _("Timeout trying to download hostlist from `%s'\n"),
352                   current_url);
353       clean_up ();
354       return;
355     }
356 #if DEBUG_HOSTLIST_CLIENT
357   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
358               "Ready for processing hostlist client request\n");
359 #endif
360   do 
361     {
362       running = 0;
363       mret = curl_multi_perform (multi, &running);
364       if (running == 0)
365         {
366           do
367             {
368               msg = curl_multi_info_read (multi, &running);
369               GNUNET_break (msg != NULL);
370               if (msg == NULL)
371                 break;
372               switch (msg->msg)
373                 {
374                 case CURLMSG_DONE:
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", 
380                                current_url,
381                                __FILE__,
382                                __LINE__,
383                                curl_easy_strerror (msg->data.result));            
384                   else
385                     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
386                                 _("Download of hostlist `%s' completed.\n"),
387                                 current_url);
388                   clean_up ();
389                   return;
390                 default:
391                   break;
392                 }
393             }
394           while (running > 0);
395         }
396     }
397   while (mret == CURLM_CALL_MULTI_PERFORM);
398   if (mret != CURLM_OK)
399     {
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));
404       clean_up ();
405     }
406   run_multi ();
407 }
408
409
410 /**
411  * Ask CURL for the select set and then schedule the
412  * receiving task with the scheduler.
413  */
414 static void
415 run_multi () 
416 {
417   CURLMcode mret;
418   fd_set rs;
419   fd_set ws;
420   fd_set es;
421   int max;
422   struct GNUNET_NETWORK_FDSet *grs;
423   struct GNUNET_NETWORK_FDSet *gws;
424   long timeout;
425   struct GNUNET_TIME_Relative rtime;
426   
427   max = -1;
428   FD_ZERO (&rs);
429   FD_ZERO (&ws);
430   FD_ZERO (&es);
431   mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
432   if (mret != CURLM_OK)
433     {
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));
438       clean_up ();
439       return;
440     }
441   mret = curl_multi_timeout (multi, &timeout);
442   if (mret != CURLM_OK)
443     {
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));
448       clean_up ();
449       return;
450     }
451   rtime = GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_remaining (end_time),
452                                     GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
453                                                                    timeout));
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");
461 #endif
462   current_task 
463     = GNUNET_SCHEDULER_add_select (sched,
464                                    GNUNET_SCHEDULER_PRIORITY_DEFAULT,
465                                    GNUNET_SCHEDULER_NO_TASK,
466                                    rtime,
467                                    grs,
468                                    gws,
469                                    &multi_ready,
470                                    multi);
471   GNUNET_NETWORK_fdset_destroy (gws);
472   GNUNET_NETWORK_fdset_destroy (grs);
473 }
474
475
476 /**
477  * Main function that will download a hostlist and process its
478  * data.
479  */
480 static void
481 download_hostlist () 
482 {
483   CURLcode ret;
484   CURLMcode mret;
485
486   curl = curl_easy_init ();
487   multi = NULL;
488   if (curl == NULL)
489     {
490       GNUNET_break (0);
491       clean_up ();
492       return;
493     }
494   current_url = get_url ();
495   GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
496               _("Bootstrapping using hostlist at `%s'.\n"), 
497               current_url);
498
499   if (proxy != NULL)
500     CURL_EASY_SETOPT (curl, CURLOPT_PROXY, proxy);    
501   download_pos = 0;
502   bogus_url = 0;
503   CURL_EASY_SETOPT (curl,
504                     CURLOPT_WRITEFUNCTION, 
505                     &download_hostlist_processor);
506   if (ret != CURLE_OK)
507     {
508       clean_up ();
509       return;
510     }
511   CURL_EASY_SETOPT (curl,
512                     CURLOPT_WRITEDATA, 
513                     NULL);
514   if (ret != CURLE_OK)
515     {
516       clean_up ();
517       return;
518     }
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, 
523                     CURLOPT_URL, 
524                     current_url);
525   if (ret != CURLE_OK)
526     {
527       clean_up ();
528       return;
529     }
530   CURL_EASY_SETOPT (curl, 
531                     CURLOPT_FAILONERROR, 
532                     1);
533 #if 0
534   CURL_EASY_SETOPT (curl, 
535                     CURLOPT_VERBOSE, 
536                     1);
537 #endif
538   CURL_EASY_SETOPT (curl, 
539                     CURLOPT_BUFFERSIZE, 
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, 
545                     60L);
546   CURL_EASY_SETOPT (curl, 
547                     CURLOPT_TIMEOUT, 
548                     60L);
549 #if 0
550   /* this should no longer be needed; we're now single-threaded! */
551   CURL_EASY_SETOPT (curl,
552                     CURLOPT_NOSIGNAL, 
553                     1);
554 #endif
555   multi = curl_multi_init ();
556   if (multi == NULL)
557     {
558       GNUNET_break (0);
559       clean_up ();
560       return;
561     }
562   mret = curl_multi_add_handle (multi, curl);
563   if (mret != CURLM_OK)
564     {
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));
575       multi = NULL;
576       clean_up ();
577       return;
578     }
579   end_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES);
580   run_multi ();
581 }  
582
583
584 /**
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.
588  */
589 static void
590 check_task (void *cls,
591             const struct GNUNET_SCHEDULER_TaskContext *tc)
592 {
593   current_task = GNUNET_SCHEDULER_NO_TASK;
594   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
595     return;
596   if (connection_count < MIN_CONNECTIONS)
597     download_hostlist ();
598   else
599     schedule_hostlist_task ();
600 }
601
602
603 /**
604  * Compute when we should check the next time about downloading
605  * a hostlist; then schedule the task accordingly.
606  */
607 static void
608 schedule_hostlist_task ()
609 {
610   struct GNUNET_TIME_Relative delay;
611
612   if (stats == NULL)
613     {
614       curl_global_cleanup ();
615       return; /* in shutdown */
616     }
617   delay = hostlist_delay;
618   if (hostlist_delay.value == 0)
619     hostlist_delay = GNUNET_TIME_UNIT_SECONDS;
620   else
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,
628                          GNUNET_YES);
629   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
630               _("Have %u/%u connections.  Will consider downloading hostlist in %llums\n"),
631               connection_count,
632               MIN_CONNECTIONS,
633               (unsigned long long) delay.value);
634   current_task = GNUNET_SCHEDULER_add_delayed (sched,
635                                                delay,
636                                                &check_task,
637                                                NULL);
638 }
639
640
641 /**
642  * Method called whenever a given peer connects.
643  *
644  * @param cls closure
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' 
648  */
649 static void
650 connect_handler (void *cls,
651                  const struct
652                  GNUNET_PeerIdentity * peer,
653                  struct GNUNET_TIME_Relative latency,
654                  uint32_t distance)
655 {
656   connection_count++;
657 }
658
659
660 /**
661  * Method called whenever a given peer connects.
662  *
663  * @param cls closure
664  * @param peer peer identity this notification is about
665  */
666 static void
667 disconnect_handler (void *cls,
668                     const struct
669                     GNUNET_PeerIdentity * peer)
670 {
671   connection_count--;
672 }
673
674
675 /**
676  * Continuation called by the statistics code once 
677  * we go the stat.  Initiates hostlist download scheduling.
678  *
679  * @param cls closure
680  * @param success GNUNET_OK if statistics were
681  *        successfully obtained, GNUNET_SYSERR if not.
682  */
683 static void
684 primary_task (void *cls, int success)
685 {
686   if (stats == NULL)
687     return; /* in shutdown */
688 #if DEBUG_HOSTLIST_CLIENT
689   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
690               "Statistics request done, scheduling hostlist download\n");
691 #endif
692   schedule_hostlist_task ();
693 }
694
695
696 static int
697 process_stat (void *cls,
698               const char *subsystem,
699               const char *name,
700               uint64_t value,
701               int is_persistent)
702 {
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;
707   return GNUNET_OK;
708 }
709
710
711 /**
712  * Start downloading hostlists from hostlist servers as necessary.
713  */
714 int
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)
720 {
721   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
722     {
723       GNUNET_break (0);
724       return GNUNET_SYSERR;
725     }
726   transport = GNUNET_TRANSPORT_connect (s, c, NULL, NULL, NULL, NULL);
727   if (NULL == transport)
728     {
729       curl_global_cleanup ();
730       return GNUNET_SYSERR;
731     }
732   cfg = c;
733   sched = s;
734   stats = st;
735   if (GNUNET_OK !=
736       GNUNET_CONFIGURATION_get_value_string (cfg,
737                                              "HOSTLIST",
738                                              "HTTP-PROXY", 
739                                              &proxy))
740     proxy = NULL;
741   *ch = &connect_handler;
742   *dh = &disconnect_handler;
743   GNUNET_STATISTICS_get (stats,
744                          "hostlist",
745                          gettext_noop("Minimum time between hostlist downloads"),
746                          GNUNET_TIME_UNIT_MINUTES,
747                          &primary_task,
748                          &process_stat,
749                          NULL);
750   return GNUNET_OK;
751 }
752
753
754 /**
755  * Stop downloading hostlists from hostlist servers as necessary.
756  */
757 void
758 GNUNET_HOSTLIST_client_stop ()
759 {
760 #if DEBUG_HOSTLIST_CLIENT
761   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
762               "Hostlist client shutdown\n");
763 #endif
764   if (current_task != GNUNET_SCHEDULER_NO_TASK)
765     {
766       GNUNET_SCHEDULER_cancel (sched,
767                                current_task);
768       curl_global_cleanup ();
769     }
770   if (transport != NULL)
771     {
772       GNUNET_TRANSPORT_disconnect (transport);
773       transport = NULL;
774     }
775   GNUNET_assert (NULL == transport);
776   GNUNET_free_non_null (proxy);
777   proxy = NULL;
778   cfg = NULL;
779   sched = NULL;
780   stats = NULL;
781 }
782
783 /* end of hostlist-client.c */