longer timeout to make test work on slower machines, but the problem is still that...
[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                   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
376                               _("Download of hostlist `%s' completed.\n"),
377                               current_url);
378                   if ( (msg->data.result != CURLE_OK) &&
379                        (msg->data.result != CURLE_GOT_NOTHING) )                       
380                     GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
381                                _("%s failed at %s:%d: `%s'\n"),
382                                "curl_multi_perform", __FILE__,
383                                __LINE__,
384                                curl_easy_strerror (msg->data.result));            
385                   clean_up ();
386                   return;
387                 default:
388                   break;
389                 }
390             }
391           while (running > 0);
392         }
393     }
394   while (mret == CURLM_CALL_MULTI_PERFORM);
395   if (mret != CURLM_OK)
396     {
397       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
398                   _("%s failed at %s:%d: `%s'\n"),
399                   "curl_multi_perform", __FILE__, __LINE__,
400                   curl_multi_strerror (mret));
401       clean_up ();
402     }
403   run_multi ();
404 }
405
406
407 /**
408  * Ask CURL for the select set and then schedule the
409  * receiving task with the scheduler.
410  */
411 static void
412 run_multi () 
413 {
414   CURLMcode mret;
415   fd_set rs;
416   fd_set ws;
417   fd_set es;
418   int max;
419   struct GNUNET_NETWORK_FDSet *grs;
420   struct GNUNET_NETWORK_FDSet *gws;
421   long timeout;
422   struct GNUNET_TIME_Relative rtime;
423   
424   max = -1;
425   FD_ZERO (&rs);
426   FD_ZERO (&ws);
427   FD_ZERO (&es);
428   mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
429   if (mret != CURLM_OK)
430     {
431       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
432                   _("%s failed at %s:%d: `%s'\n"),
433                   "curl_multi_fdset", __FILE__, __LINE__,
434                   curl_multi_strerror (mret));
435       clean_up ();
436       return;
437     }
438   mret = curl_multi_timeout (multi, &timeout);
439   if (mret != CURLM_OK)
440     {
441       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
442                   _("%s failed at %s:%d: `%s'\n"),
443                   "curl_multi_timeout", __FILE__, __LINE__,
444                   curl_multi_strerror (mret));
445       clean_up ();
446       return;
447     }
448   rtime = GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_remaining (end_time),
449                                     GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
450                                                                    timeout));
451   grs = GNUNET_NETWORK_fdset_create ();
452   gws = GNUNET_NETWORK_fdset_create ();
453   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
454   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);  
455 #if DEBUG_HOSTLIST_CLIENT
456   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
457               "Scheduling task for hostlist download using cURL\n");
458 #endif
459   current_task 
460     = GNUNET_SCHEDULER_add_select (sched,
461                                    GNUNET_SCHEDULER_PRIORITY_DEFAULT,
462                                    GNUNET_SCHEDULER_NO_TASK,
463                                    rtime,
464                                    grs,
465                                    gws,
466                                    &multi_ready,
467                                    multi);
468   GNUNET_NETWORK_fdset_destroy (gws);
469   GNUNET_NETWORK_fdset_destroy (grs);
470 }
471
472
473 /**
474  * Main function that will download a hostlist and process its
475  * data.
476  */
477 static void
478 download_hostlist () 
479 {
480   CURLcode ret;
481   CURLMcode mret;
482
483   curl = curl_easy_init ();
484   multi = NULL;
485   if (curl == NULL)
486     {
487       GNUNET_break (0);
488       clean_up ();
489       return;
490     }
491   current_url = get_url ();
492   GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
493               _("Bootstrapping using hostlist at `%s'.\n"), 
494               current_url);
495
496   if (proxy != NULL)
497     CURL_EASY_SETOPT (curl, CURLOPT_PROXY, proxy);    
498   download_pos = 0;
499   bogus_url = 0;
500   CURL_EASY_SETOPT (curl,
501                     CURLOPT_WRITEFUNCTION, 
502                     &download_hostlist_processor);
503   if (ret != CURLE_OK)
504     {
505       clean_up ();
506       return;
507     }
508   CURL_EASY_SETOPT (curl,
509                     CURLOPT_WRITEDATA, 
510                     NULL);
511   if (ret != CURLE_OK)
512     {
513       clean_up ();
514       return;
515     }
516   CURL_EASY_SETOPT (curl, CURLOPT_FOLLOWLOCATION, 1);
517   CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 4);
518   /* no need to abort if the above failed */
519   CURL_EASY_SETOPT (curl, 
520                     CURLOPT_URL, 
521                     current_url);
522   if (ret != CURLE_OK)
523     {
524       clean_up ();
525       return;
526     }
527   CURL_EASY_SETOPT (curl, 
528                     CURLOPT_FAILONERROR, 
529                     1);
530 #if 0
531   CURL_EASY_SETOPT (curl, 
532                     CURLOPT_VERBOSE, 
533                     1);
534 #endif
535   CURL_EASY_SETOPT (curl, 
536                     CURLOPT_BUFFERSIZE, 
537                     GNUNET_SERVER_MAX_MESSAGE_SIZE);
538   if (0 == strncmp (current_url, "http", 4))
539     CURL_EASY_SETOPT (curl, CURLOPT_USERAGENT, "GNUnet");
540   CURL_EASY_SETOPT (curl, 
541                     CURLOPT_CONNECTTIMEOUT, 
542                     60L);
543   CURL_EASY_SETOPT (curl, 
544                     CURLOPT_TIMEOUT, 
545                     60L);
546 #if 0
547   /* this should no longer be needed; we're now single-threaded! */
548   CURL_EASY_SETOPT (curl,
549                     CURLOPT_NOSIGNAL, 
550                     1);
551 #endif
552   multi = curl_multi_init ();
553   if (multi == NULL)
554     {
555       GNUNET_break (0);
556       clean_up ();
557       return;
558     }
559   mret = curl_multi_add_handle (multi, curl);
560   if (mret != CURLM_OK)
561     {
562       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
563                   _("%s failed at %s:%d: `%s'\n"),
564                   "curl_multi_add_handle", __FILE__, __LINE__,
565                   curl_multi_strerror (mret));
566       mret = curl_multi_cleanup (multi);
567       if (mret != CURLM_OK)
568         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
569                     _("%s failed at %s:%d: `%s'\n"),
570                     "curl_multi_cleanup", __FILE__, __LINE__,
571                     curl_multi_strerror (mret));
572       multi = NULL;
573       clean_up ();
574       return;
575     }
576   end_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES);
577   run_multi ();
578 }  
579
580
581 /**
582  * Task that checks if we should try to download a hostlist.
583  * If so, we initiate the download, otherwise we schedule
584  * this task again for a later time.
585  */
586 static void
587 check_task (void *cls,
588             const struct GNUNET_SCHEDULER_TaskContext *tc)
589 {
590   current_task = GNUNET_SCHEDULER_NO_TASK;
591   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
592     return;
593   if (connection_count < MIN_CONNECTIONS)
594     download_hostlist ();
595   else
596     schedule_hostlist_task ();
597 }
598
599
600 /**
601  * Compute when we should check the next time about downloading
602  * a hostlist; then schedule the task accordingly.
603  */
604 static void
605 schedule_hostlist_task ()
606 {
607   struct GNUNET_TIME_Relative delay;
608
609   if (stats == NULL)
610     {
611       curl_global_cleanup ();
612       return; /* in shutdown */
613     }
614   delay = hostlist_delay;
615   if (hostlist_delay.value == 0)
616     hostlist_delay = GNUNET_TIME_UNIT_SECONDS;
617   else
618     hostlist_delay = GNUNET_TIME_relative_multiply (hostlist_delay, 2);
619   if (hostlist_delay.value > GNUNET_TIME_UNIT_HOURS.value * (1 + connection_count))
620     hostlist_delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS,
621                                                     (1 + connection_count));
622   GNUNET_STATISTICS_set (stats,
623                          gettext_noop("Minimum time between hostlist downloads"),
624                          hostlist_delay.value,
625                          GNUNET_YES);
626   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
627               _("Will consider downloading hostlist in %llums\n"),
628               (unsigned long long) delay.value);
629   current_task = GNUNET_SCHEDULER_add_delayed (sched,
630                                                delay,
631                                                &check_task,
632                                                NULL);
633 }
634
635
636 /**
637  * Method called whenever a given peer connects.
638  *
639  * @param cls closure
640  * @param peer peer identity this notification is about
641  * @param latency reported latency of the connection with 'other'
642  * @param distance reported distance (DV) to 'other' 
643  */
644 static void
645 connect_handler (void *cls,
646                  const struct
647                  GNUNET_PeerIdentity * peer,
648                  struct GNUNET_TIME_Relative latency,
649                  uint32_t distance)
650 {
651   connection_count++;
652 }
653
654
655 /**
656  * Method called whenever a given peer connects.
657  *
658  * @param cls closure
659  * @param peer peer identity this notification is about
660  */
661 static void
662 disconnect_handler (void *cls,
663                     const struct
664                     GNUNET_PeerIdentity * peer)
665 {
666   connection_count--;
667 }
668
669
670 /**
671  * Continuation called by the statistics code once 
672  * we go the stat.  Initiates hostlist download scheduling.
673  *
674  * @param cls closure
675  * @param success GNUNET_OK if statistics were
676  *        successfully obtained, GNUNET_SYSERR if not.
677  */
678 static void
679 primary_task (void *cls, int success)
680 {
681   if (stats == NULL)
682     return; /* in shutdown */
683 #if DEBUG_HOSTLIST_CLIENT
684   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
685               "Statistics request done, scheduling hostlist download\n");
686 #endif
687   schedule_hostlist_task ();
688 }
689
690
691 static int
692 process_stat (void *cls,
693               const char *subsystem,
694               const char *name,
695               uint64_t value,
696               int is_persistent)
697 {
698   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
699               _("Initial time between hostlist downloads is %llums\n"),
700               (unsigned long long) value);
701   hostlist_delay.value = value;
702   return GNUNET_OK;
703 }
704
705
706 /**
707  * Start downloading hostlists from hostlist servers as necessary.
708  */
709 int
710 GNUNET_HOSTLIST_client_start (const struct GNUNET_CONFIGURATION_Handle *c,
711                               struct GNUNET_SCHEDULER_Handle *s,
712                               struct GNUNET_STATISTICS_Handle *st,
713                               GNUNET_CORE_ConnectEventHandler *ch,
714                               GNUNET_CORE_DisconnectEventHandler *dh)
715 {
716   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
717     {
718       GNUNET_break (0);
719       return GNUNET_SYSERR;
720     }
721   transport = GNUNET_TRANSPORT_connect (s, c, NULL, NULL, NULL, NULL);
722   if (NULL == transport)
723     {
724       curl_global_cleanup ();
725       return GNUNET_SYSERR;
726     }
727   cfg = c;
728   sched = s;
729   stats = st;
730   if (GNUNET_OK !=
731       GNUNET_CONFIGURATION_get_value_string (cfg,
732                                              "HOSTLIST",
733                                              "HTTP-PROXY", 
734                                              &proxy))
735     proxy = NULL;
736   *ch = &connect_handler;
737   *dh = &disconnect_handler;
738   GNUNET_STATISTICS_get (stats,
739                          "hostlist",
740                          gettext_noop("Minimum time between hostlist downloads"),
741                          GNUNET_TIME_UNIT_MINUTES,
742                          &primary_task,
743                          &process_stat,
744                          NULL);
745   return GNUNET_OK;
746 }
747
748
749 /**
750  * Stop downloading hostlists from hostlist servers as necessary.
751  */
752 void
753 GNUNET_HOSTLIST_client_stop ()
754 {
755 #if DEBUG_HOSTLIST_CLIENT
756   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
757               "Hostlist client shutdown\n");
758 #endif
759   if (current_task != GNUNET_SCHEDULER_NO_TASK)
760     {
761       GNUNET_SCHEDULER_cancel (sched,
762                                current_task);
763       curl_global_cleanup ();
764     }
765   if (transport != NULL)
766     {
767       GNUNET_TRANSPORT_disconnect (transport);
768       transport = NULL;
769     }
770   GNUNET_assert (NULL == transport);
771   GNUNET_free_non_null (proxy);
772   proxy = NULL;
773   cfg = NULL;
774   sched = NULL;
775   stats = NULL;
776 }
777
778 /* end of hostlist-client.c */