-do not export set_incoming_quota past file, it is not used beyond the scope
[oweals/gnunet.git] / src / transport / plugin_transport_http_client.c
1 /*
2      This file is part of GNUnet
3      Copyright (C) 2002-2014 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 3, 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., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20
21 /**
22  * @file transport/plugin_transport_http_client.c
23  * @brief HTTP/S client transport plugin
24  * @author Matthias Wachs
25  * @author Christian Grothoff
26  */
27
28 #if BUILD_HTTPS
29 #define PLUGIN_NAME "https_client"
30 #define HTTP_STAT_STR_CONNECTIONS "# HTTPS client connections"
31 #define LIBGNUNET_PLUGIN_TRANSPORT_INIT libgnunet_plugin_transport_https_client_init
32 #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_https_client_done
33 #else
34 #define PLUGIN_NAME "http_client"
35 #define HTTP_STAT_STR_CONNECTIONS "# HTTP client connections"
36 #define LIBGNUNET_PLUGIN_TRANSPORT_INIT libgnunet_plugin_transport_http_client_init
37 #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_http_client_done
38 #endif
39
40 #define VERBOSE_CURL GNUNET_NO
41
42 #define PUT_DISCONNECT_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1)
43
44 #define ENABLE_PUT GNUNET_YES
45 #define ENABLE_GET GNUNET_YES
46
47 #include "platform.h"
48 #include "gnunet_util_lib.h"
49 #include "gnunet_protocols.h"
50 #include "gnunet_transport_plugin.h"
51 #include "plugin_transport_http_common.h"
52 #if HAVE_CURL_CURL_H
53 #include <curl/curl.h>
54 #elif HAVE_GNURL_CURL_H
55 #include <gnurl/curl.h>
56 #endif
57
58
59 #define LOG(kind,...) GNUNET_log_from(kind, PLUGIN_NAME, __VA_ARGS__)
60
61 /**
62  * Encapsulation of all of the state of the plugin.
63  */
64 struct HTTP_Client_Plugin;
65
66 /**
67  * State of a HTTP PUT request
68  */
69 enum HTTP_PUT_REQUEST_STATE
70 {
71   /**
72    *  Just created, not yet connected
73    */
74   H_NOT_CONNECTED,
75
76   /**
77    *  Connected
78    */
79   H_CONNECTED,
80
81   /**
82    *  Paused, nothing to send
83    */
84   H_PAUSED,
85
86   /**
87    * Temporary disconnect in progress due to inactivity
88    */
89   H_TMP_DISCONNECTING,
90
91   /**
92    * Send request while temporary disconnect, reconnect
93    */
94   H_TMP_RECONNECT_REQUIRED,
95
96   /**
97    * Temporarily disconnected
98    */
99   H_TMP_DISCONNECTED,
100
101   /**
102    * Disconnected
103    */
104   H_DISCONNECTED
105 };
106
107 /**
108  *  Message to send using http
109  */
110 struct HTTP_Message
111 {
112   /**
113    * next pointer for double linked list
114    */
115   struct HTTP_Message *next;
116
117   /**
118    * previous pointer for double linked list
119    */
120   struct HTTP_Message *prev;
121
122   /**
123    * buffer containing data to send
124    */
125   char *buf;
126
127   /**
128    * Continuation function to call once the transmission buffer
129    * has again space available.  NULL if there is no
130    * continuation to call.
131    */
132   GNUNET_TRANSPORT_TransmitContinuation transmit_cont;
133
134   /**
135    * Closure for @e transmit_cont.
136    */
137   void *transmit_cont_cls;
138
139   /**
140    * amount of data already sent
141    */
142   size_t pos;
143
144   /**
145    * buffer length
146    */
147   size_t size;
148
149 };
150
151
152 /**
153  * Session handle for HTTP(S) connections.
154  */
155 struct Session;
156
157
158 /**
159  * A request handle
160  *
161  */
162 struct RequestHandle
163 {
164   /**
165    * Current state of this request
166    */
167   enum HTTP_PUT_REQUEST_STATE state;
168
169   /**
170    * The curl easy handle
171    */
172   CURL *easyhandle;
173
174   /**
175    * The related session
176    */
177   struct Session *s;
178 };
179
180
181 /**
182  * Session handle for connections.
183  */
184 struct Session
185 {
186   /**
187    * The URL to connect to
188    */
189   char *url;
190
191   /**
192    * Address
193    */
194   struct GNUNET_HELLO_Address *address;
195
196   /**
197    * Pointer to the global plugin struct.
198    */
199   struct HTTP_Client_Plugin *plugin;
200
201   /**
202    * Handle for the HTTP PUT request.
203    */
204   struct RequestHandle put;
205
206   /**
207    * Handle for the HTTP GET request.
208    */
209   struct RequestHandle get;
210
211   /**
212    * next pointer for double linked list
213    */
214   struct HTTP_Message *msg_head;
215
216   /**
217    * previous pointer for double linked list
218    */
219   struct HTTP_Message *msg_tail;
220
221   /**
222    * Message stream tokenizer for incoming data
223    */
224   struct GNUNET_SERVER_MessageStreamTokenizer *msg_tk;
225
226   /**
227    * Session timeout task
228    */
229   struct GNUNET_SCHEDULER_Task * put_disconnect_task;
230
231   /**
232    * Session timeout task
233    */
234   struct GNUNET_SCHEDULER_Task * timeout_task;
235
236   /**
237    * Task to wake up client receive handle when receiving is allowed again
238    */
239   struct GNUNET_SCHEDULER_Task * recv_wakeup_task;
240
241   /**
242    * Absolute time when to receive data again.
243    * Used for receive throttling.
244    */
245   struct GNUNET_TIME_Absolute next_receive;
246
247   /**
248    * When does this session time out.
249    */
250   struct GNUNET_TIME_Absolute timeout;
251
252   /**
253    * Number of bytes waiting for transmission to this peer.
254    */
255   unsigned long long bytes_in_queue;
256
257   /**
258    * Outbound overhead due to HTTP connection
259    * Add to next message of this session when calling callback
260    */
261   size_t overhead;
262
263   /**
264    * Number of messages waiting for transmission to this peer.
265    */
266   unsigned int msgs_in_queue;
267
268   /**
269    * ATS network type.
270    */
271   enum GNUNET_ATS_Network_Type scope;
272 };
273
274
275 /**
276  * Encapsulation of all of the state of the plugin.
277  */
278 struct HTTP_Client_Plugin
279 {
280   /**
281    * Our environment.
282    */
283   struct GNUNET_TRANSPORT_PluginEnvironment *env;
284
285   /**
286    * Open sessions.
287    */
288   struct GNUNET_CONTAINER_MultiPeerMap *sessions;
289
290   /**
291    * Function to call about session status changes.
292    */
293   GNUNET_TRANSPORT_SessionInfoCallback sic;
294
295   /**
296    * Closure for @e sic.
297    */
298   void *sic_cls;
299
300   /**
301    * Plugin name
302    */
303   char *name;
304
305   /**
306    * Protocol
307    */
308   char *protocol;
309
310   /**
311    * Proxy configuration: hostname or ip of the proxy server
312    */
313   char *proxy_hostname;
314
315   /**
316    * Username for the proxy server
317    */
318   char *proxy_username;
319
320   /**
321    * Password for the proxy server
322    */
323   char *proxy_password;
324
325   /**
326    * cURL Multihandle
327    */
328   CURLM *curl_multi_handle;
329
330   /**
331    * curl perform task
332    */
333   struct GNUNET_SCHEDULER_Task * client_perform_task;
334
335   /**
336    * Type of proxy server:
337    *
338    * Valid values as supported by curl:
339    * CURLPROXY_HTTP, CURLPROXY_HTTP_1_0 CURLPROXY_SOCKS4, CURLPROXY_SOCKS5,
340    * CURLPROXY_SOCKS4A, CURLPROXY_SOCKS5_HOSTNAME
341    */
342   curl_proxytype proxytype;
343
344   /**
345    * Use proxy tunneling:
346    * Tunnel all operations through a given HTTP instead of have the proxy
347    * evaluate the HTTP request
348    *
349    * Default: #GNUNET_NO, #GNUNET_YES experimental
350    */
351   int proxy_use_httpproxytunnel;
352
353   /**
354    * My options to be included in the address
355    */
356   uint32_t options;
357
358   /**
359    * Maximum number of sockets the plugin can use
360    * Each http connections are two requests
361    */
362   unsigned int max_requests;
363
364   /**
365    * Current number of sockets the plugin can use
366    * Each http connections are two requests
367    */
368   unsigned int cur_requests;
369
370   /**
371    * Last used unique HTTP connection tag
372    */
373   uint32_t last_tag;
374
375   /**
376    * use IPv6
377    */
378   uint16_t use_ipv6;
379
380   /**
381    * use IPv4
382    */
383   uint16_t use_ipv4;
384
385   /**
386    * Should we emulate an XHR client for testing?
387    */
388   int emulate_xhr;
389 };
390
391
392 /**
393  * Disconnect a session
394  *
395  * @param cls the `struct HTTP_Client_Plugin *`
396  * @param s session
397  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
398  */
399 static int
400 http_client_plugin_session_disconnect (void *cls, struct Session *s);
401
402
403 /**
404  * If a session monitor is attached, notify it about the new
405  * session state.
406  *
407  * @param plugin our plugin
408  * @param session session that changed state
409  * @param state new state of the session
410  */
411 static void
412 notify_session_monitor (struct HTTP_Client_Plugin *plugin,
413                         struct Session *session,
414                         enum GNUNET_TRANSPORT_SessionState state)
415 {
416   struct GNUNET_TRANSPORT_SessionInfo info;
417
418   if (NULL == plugin->sic)
419     return;
420   memset (&info, 0, sizeof (info));
421   info.state = state;
422   info.is_inbound = GNUNET_NO;
423   info.num_msg_pending = session->msgs_in_queue;
424   info.num_bytes_pending = session->bytes_in_queue;
425   info.receive_delay = session->next_receive;
426   info.session_timeout = session->timeout;
427   info.address = session->address;
428   plugin->sic (plugin->sic_cls,
429                session,
430                &info);
431 }
432
433
434 /**
435  * Delete session @a s.
436  *
437  * @param s the session to delete
438  */
439 static void
440 client_delete_session (struct Session *s)
441 {
442   struct HTTP_Client_Plugin *plugin = s->plugin;
443   struct HTTP_Message *pos;
444   struct HTTP_Message *next;
445   CURLMcode mret;
446
447   if (NULL != s->timeout_task)
448   {
449     GNUNET_SCHEDULER_cancel (s->timeout_task);
450     s->timeout_task = NULL;
451     s->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
452   }
453   if (NULL != s->put_disconnect_task)
454   {
455     GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
456     s->put_disconnect_task = NULL;
457   }
458   if (NULL != s->recv_wakeup_task)
459   {
460     GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
461     s->recv_wakeup_task = NULL;
462   }
463   GNUNET_assert (GNUNET_OK ==
464                  GNUNET_CONTAINER_multipeermap_remove (plugin->sessions,
465                                                        &s->address->peer,
466                                                        s));
467   if (NULL != s->put.easyhandle)
468   {
469     LOG (GNUNET_ERROR_TYPE_DEBUG,
470          "Session %p/request %p: disconnecting PUT request to peer `%s'\n",
471          s,
472          s->put.easyhandle,
473          GNUNET_i2s (&s->address->peer));
474
475     /* remove curl handle from multi handle */
476     mret = curl_multi_remove_handle (plugin->curl_multi_handle,
477                                      s->put.easyhandle);
478     GNUNET_break (CURLM_OK == mret);
479     curl_easy_cleanup (s->put.easyhandle);
480     GNUNET_assert (plugin->cur_requests > 0);
481     plugin->cur_requests--;
482     s->put.easyhandle = NULL;
483   }
484   if (NULL != s->get.easyhandle)
485   {
486     LOG (GNUNET_ERROR_TYPE_DEBUG,
487          "Session %p/request %p: disconnecting GET request to peer `%s'\n",
488          s, s->get.easyhandle,
489          GNUNET_i2s (&s->address->peer));
490     /* remove curl handle from multi handle */
491     mret = curl_multi_remove_handle (plugin->curl_multi_handle,
492                                      s->get.easyhandle);
493     GNUNET_break (CURLM_OK == mret);
494     curl_easy_cleanup (s->get.easyhandle);
495     GNUNET_assert (plugin->cur_requests > 0);
496     plugin->cur_requests--;
497     s->get.easyhandle = NULL;
498   }
499
500   GNUNET_STATISTICS_set (plugin->env->stats,
501                          HTTP_STAT_STR_CONNECTIONS,
502                          plugin->cur_requests,
503                          GNUNET_NO);
504   next = s->msg_head;
505   while (NULL != (pos = next))
506   {
507     next = pos->next;
508     GNUNET_CONTAINER_DLL_remove (s->msg_head,
509                                  s->msg_tail,
510                                  pos);
511     GNUNET_assert (0 < s->msgs_in_queue);
512     s->msgs_in_queue--;
513     GNUNET_assert (pos->size <= s->bytes_in_queue);
514     s->bytes_in_queue -= pos->size;
515     if (NULL != pos->transmit_cont)
516       pos->transmit_cont (pos->transmit_cont_cls,
517                           &s->address->peer,
518                           GNUNET_SYSERR,
519                           pos->size,
520                           pos->pos + s->overhead);
521     s->overhead = 0;
522     GNUNET_free (pos);
523   }
524   GNUNET_assert (0 == s->msgs_in_queue);
525   GNUNET_assert (0 == s->bytes_in_queue);
526   notify_session_monitor (plugin,
527                           s,
528                           GNUNET_TRANSPORT_SS_DONE);
529   if (NULL != s->msg_tk)
530   {
531     GNUNET_SERVER_mst_destroy (s->msg_tk);
532     s->msg_tk = NULL;
533   }
534   GNUNET_HELLO_address_free (s->address);
535   GNUNET_free (s->url);
536   GNUNET_free (s);
537 }
538
539
540 /**
541  * Increment session timeout due to activity for session @a s.
542  *
543  * @param s the session
544  */
545 static void
546 client_reschedule_session_timeout (struct Session *s)
547 {
548   GNUNET_assert (NULL != s->timeout_task);
549   s->timeout = GNUNET_TIME_relative_to_absolute (GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT);
550 }
551
552
553 /**
554  * Task performing curl operations
555  *
556  * @param cls plugin as closure
557  * @param tc gnunet scheduler task context
558  */
559 static void
560 client_run (void *cls,
561             const struct GNUNET_SCHEDULER_TaskContext *tc);
562
563
564 /**
565  * Function setting up file descriptors and scheduling task to run
566  *
567  * @param plugin the plugin as closure
568  * @param now schedule task in 1ms, regardless of what curl may say
569  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for ok
570  */
571 static int
572 client_schedule (struct HTTP_Client_Plugin *plugin,
573                  int now)
574 {
575   fd_set rs;
576   fd_set ws;
577   fd_set es;
578   int max;
579   struct GNUNET_NETWORK_FDSet *grs;
580   struct GNUNET_NETWORK_FDSet *gws;
581   long to;
582   CURLMcode mret;
583   struct GNUNET_TIME_Relative timeout;
584
585   /* Cancel previous scheduled task */
586   if (plugin->client_perform_task != NULL)
587   {
588     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
589     plugin->client_perform_task = NULL;
590   }
591   max = -1;
592   FD_ZERO (&rs);
593   FD_ZERO (&ws);
594   FD_ZERO (&es);
595   mret = curl_multi_fdset (plugin->curl_multi_handle, &rs, &ws, &es, &max);
596   if (mret != CURLM_OK)
597   {
598     LOG (GNUNET_ERROR_TYPE_ERROR,
599          _("%s failed at %s:%d: `%s'\n"),
600          "curl_multi_fdset",
601          __FILE__,
602          __LINE__,
603          curl_multi_strerror (mret));
604     return GNUNET_SYSERR;
605   }
606   mret = curl_multi_timeout (plugin->curl_multi_handle, &to);
607   if (-1 == to)
608     timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1);
609   else
610     timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, to);
611   if (now == GNUNET_YES)
612     timeout = GNUNET_TIME_UNIT_MILLISECONDS;
613
614   if (CURLM_OK != mret)
615   {
616     LOG (GNUNET_ERROR_TYPE_ERROR,
617                 _("%s failed at %s:%d: `%s'\n"),
618                 "curl_multi_timeout", __FILE__, __LINE__,
619                 curl_multi_strerror (mret));
620     return GNUNET_SYSERR;
621   }
622
623   grs = GNUNET_NETWORK_fdset_create ();
624   gws = GNUNET_NETWORK_fdset_create ();
625   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
626   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
627
628   /* Schedule task to run when select is ready to read or write */
629   plugin->client_perform_task =
630       GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
631                                    timeout, grs, gws,
632                                    &client_run, plugin);
633   GNUNET_NETWORK_fdset_destroy (gws);
634   GNUNET_NETWORK_fdset_destroy (grs);
635   return GNUNET_OK;
636 }
637
638
639 #if VERBOSE_CURL
640 /**
641  * Loggging function
642  *
643  * @param curl the curl easy handle
644  * @param type message type
645  * @param data data to log, NOT a 0-terminated string
646  * @param size data length
647  * @param cls the closure
648  * @return always 0
649  */
650 static int
651 client_log (CURL *curl,
652             curl_infotype type,
653             const char *data,
654             size_t size,
655             void *cls)
656 {
657   struct RequestHandle *ch = cls;
658   const char *ttype = "UNSPECIFIED";
659   char text[size + 2];
660
661   if (! ((CURLINFO_TEXT == type) ||
662          (CURLINFO_HEADER_IN == type) ||
663          (CURLINFO_HEADER_OUT == type)))
664     return 0;
665   switch (type)
666   {
667   case CURLINFO_TEXT:
668     ttype = "TEXT";
669     break;
670   case CURLINFO_HEADER_IN:
671     ttype = "HEADER_IN";
672     break;
673   case CURLINFO_HEADER_OUT:
674     ttype = "HEADER_OUT";
675     /* Overhead*/
676     GNUNET_assert (NULL != ch);
677     GNUNET_assert (NULL != ch->easyhandle);
678     GNUNET_assert (NULL != ch->s);
679     ch->s->overhead += size;
680     break;
681   default:
682     ttype = "UNSPECIFIED";
683     break;
684   }
685   memcpy (text, data, size);
686   if (text[size - 1] == '\n')
687   {
688     text[size] = '\0';
689   }
690   else
691   {
692     text[size] = '\n';
693     text[size + 1] = '\0';
694   }
695   LOG (GNUNET_ERROR_TYPE_DEBUG,
696        "Request %p %s: %s",
697        ch->easyhandle,
698        ttype,
699        text);
700   return 0;
701 }
702 #endif
703
704 /**
705  * Connect GET request
706  *
707  * @param s the session to connect
708  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
709  */
710 static int
711 client_connect_get (struct Session *s);
712
713
714 /**
715  * Connect a HTTP put request
716  *
717  * @param s the session to connect
718  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for success
719  */
720 static int
721 client_connect_put (struct Session *s);
722
723
724 /**
725  * Function that can be used by the transport service to transmit
726  * a message using the plugin.   Note that in the case of a
727  * peer disconnecting, the continuation MUST be called
728  * prior to the disconnect notification itself.  This function
729  * will be called with this peer's HELLO message to initiate
730  * a fresh connection to another peer.
731  *
732  * @param cls closure
733  * @param s which session must be used
734  * @param msgbuf the message to transmit
735  * @param msgbuf_size number of bytes in @a msgbuf
736  * @param priority how important is the message (most plugins will
737  *                 ignore message priority and just FIFO)
738  * @param to how long to wait at most for the transmission (does not
739  *                require plugins to discard the message after the timeout,
740  *                just advisory for the desired delay; most plugins will ignore
741  *                this as well)
742  * @param cont continuation to call once the message has
743  *        been transmitted (or if the transport is ready
744  *        for the next transmission call; or if the
745  *        peer disconnected...); can be NULL
746  * @param cont_cls closure for @a cont
747  * @return number of bytes used (on the physical network, with overheads);
748  *         -1 on hard errors (i.e. address invalid); 0 is a legal value
749  *         and does NOT mean that the message was not transmitted (DV)
750  */
751 static ssize_t
752 http_client_plugin_send (void *cls,
753                          struct Session *s,
754                          const char *msgbuf,
755                          size_t msgbuf_size,
756                          unsigned int priority,
757                          struct GNUNET_TIME_Relative to,
758                          GNUNET_TRANSPORT_TransmitContinuation cont,
759                          void *cont_cls)
760 {
761   struct HTTP_Client_Plugin *plugin = cls;
762   struct HTTP_Message *msg;
763   char *stat_txt;
764
765   LOG (GNUNET_ERROR_TYPE_DEBUG,
766        "Session %p/request %p: Sending message with %u to peer `%s' \n",
767        s,
768        s->put.easyhandle,
769        msgbuf_size,
770        GNUNET_i2s (&s->address->peer));
771
772   /* create new message and schedule */
773   msg = GNUNET_malloc (sizeof (struct HTTP_Message) + msgbuf_size);
774   msg->size = msgbuf_size;
775   msg->buf = (char *) &msg[1];
776   msg->transmit_cont = cont;
777   msg->transmit_cont_cls = cont_cls;
778   memcpy (msg->buf,
779           msgbuf,
780           msgbuf_size);
781   GNUNET_CONTAINER_DLL_insert_tail (s->msg_head,
782                                     s->msg_tail,
783                                     msg);
784   s->msgs_in_queue++;
785   s->bytes_in_queue += msg->size;
786
787   GNUNET_asprintf (&stat_txt,
788                    "# bytes currently in %s_client buffers",
789                    plugin->protocol);
790   GNUNET_STATISTICS_update (plugin->env->stats,
791                             stat_txt, msgbuf_size, GNUNET_NO);
792   GNUNET_free (stat_txt);
793   notify_session_monitor (plugin,
794                           s,
795                           GNUNET_TRANSPORT_SS_UPDATE);
796   if (H_TMP_DISCONNECTING == s->put.state)
797   {
798     /* PUT request is currently getting disconnected */
799     s->put.state = H_TMP_RECONNECT_REQUIRED;
800     LOG (GNUNET_ERROR_TYPE_DEBUG,
801          "Session %p/request %p: currently disconnecting, reconnecting immediately\n",
802          s,
803          s->put.easyhandle);
804     return msgbuf_size;
805   }
806   if (H_PAUSED == s->put.state)
807   {
808     /* PUT request was paused, unpause */
809     GNUNET_assert (s->put_disconnect_task != NULL);
810     GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
811     s->put_disconnect_task = NULL;
812     LOG (GNUNET_ERROR_TYPE_DEBUG,
813          "Session %p/request %p: unpausing request\n",
814          s, s->put.easyhandle);
815     s->put.state = H_CONNECTED;
816     if (NULL != s->put.easyhandle)
817       curl_easy_pause (s->put.easyhandle, CURLPAUSE_CONT);
818   }
819   else if (H_TMP_DISCONNECTED == s->put.state)
820   {
821     /* PUT request was disconnected, reconnect */
822     LOG (GNUNET_ERROR_TYPE_DEBUG, "Session %p: Reconnecting PUT request\n", s);
823     GNUNET_break (NULL == s->put.easyhandle);
824     if (GNUNET_SYSERR == client_connect_put (s))
825     {
826       /* Could not reconnect */
827       http_client_plugin_session_disconnect (plugin, s);
828       return GNUNET_SYSERR;
829     }
830   }
831   client_schedule (s->plugin, GNUNET_YES);
832   return msgbuf_size;
833 }
834
835
836 /**
837  * Disconnect a session
838  *
839  * @param cls the `struct HTTP_Client_Plugin *`
840  * @param s session
841  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
842  */
843 static int
844 http_client_plugin_session_disconnect (void *cls,
845                                        struct Session *s)
846 {
847   struct HTTP_Client_Plugin *plugin = cls;
848
849   LOG (GNUNET_ERROR_TYPE_DEBUG,
850        "Session %p: notifying transport about ending session\n",
851        s);
852   plugin->env->session_end (plugin->env->cls,
853                             s->address,
854                             s);
855   client_delete_session (s);
856
857   /* Re-schedule since handles have changed */
858   if (NULL != plugin->client_perform_task)
859   {
860     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
861     plugin->client_perform_task = NULL;
862   }
863   client_schedule (plugin, GNUNET_YES);
864
865   return GNUNET_OK;
866 }
867
868
869 /**
870  * Function that is called to get the keepalive factor.
871  * #GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT is divided by this number to
872  * calculate the interval between keepalive packets.
873  *
874  * @param cls closure with the `struct Plugin`
875  * @return keepalive factor
876  */
877 static unsigned int
878 http_client_query_keepalive_factor (void *cls)
879 {
880   return 3;
881 }
882
883
884 /**
885  * Callback to destroys all sessions on exit.
886  *
887  * @param cls the `struct HTTP_Client_Plugin *`
888  * @param peer identity of the peer
889  * @param value the `struct Session *`
890  * @return #GNUNET_OK (continue iterating)
891  */
892 static int
893 destroy_session_cb (void *cls,
894                     const struct GNUNET_PeerIdentity *peer,
895                     void *value)
896 {
897   struct HTTP_Client_Plugin *plugin = cls;
898   struct Session *session = value;
899
900   http_client_plugin_session_disconnect (plugin, session);
901   return GNUNET_OK;
902 }
903
904
905 /**
906  * Function that can be used to force the plugin to disconnect
907  * from the given peer and cancel all previous transmissions
908  * (and their continuationc).
909  *
910  * @param cls closure
911  * @param target peer from which to disconnect
912  */
913 static void
914 http_client_plugin_peer_disconnect (void *cls,
915                                     const struct GNUNET_PeerIdentity *target)
916 {
917   struct HTTP_Client_Plugin *plugin = cls;
918
919   LOG (GNUNET_ERROR_TYPE_DEBUG,
920        "Transport tells me to disconnect `%s'\n",
921        GNUNET_i2s (target));
922   GNUNET_CONTAINER_multipeermap_get_multiple (plugin->sessions,
923                                               target,
924                                               &destroy_session_cb,
925                                               plugin);
926 }
927
928
929 /**
930  * Closure for #session_lookup_client_by_address().
931  */
932 struct SessionClientCtx
933 {
934   /**
935    * Address we are looking for.
936    */
937   const struct GNUNET_HELLO_Address *address;
938
939   /**
940    * Session that was found.
941    */
942   struct Session *ret;
943 };
944
945
946 /**
947  * Locate the seession object for a given address.
948  *
949  * @param cls the `struct SessionClientCtx *`
950  * @param key peer identity
951  * @param value the `struct Session` to check
952  * @return #GNUNET_NO if found, #GNUNET_OK if not
953  */
954 static int
955 session_lookup_client_by_address (void *cls,
956                                   const struct GNUNET_PeerIdentity *key,
957                                   void *value)
958 {
959   struct SessionClientCtx *sc_ctx = cls;
960   struct Session *s = value;
961
962   if (0 == GNUNET_HELLO_address_cmp (sc_ctx->address,
963                                      s->address))
964   {
965     sc_ctx->ret = s;
966     return GNUNET_NO;
967   }
968   return GNUNET_YES;
969 }
970
971
972 /**
973  * Check if a sessions exists for an specific address
974  *
975  * @param plugin the plugin
976  * @param address the address
977  * @return the session or NULL
978  */
979 static struct Session *
980 client_lookup_session (struct HTTP_Client_Plugin *plugin,
981                        const struct GNUNET_HELLO_Address *address)
982 {
983   struct SessionClientCtx sc_ctx;
984
985   sc_ctx.address = address;
986   sc_ctx.ret = NULL;
987   GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
988                                          &session_lookup_client_by_address,
989                                          &sc_ctx);
990   return sc_ctx.ret;
991 }
992
993
994 /**
995  * When we have nothing to transmit, we pause the HTTP PUT
996  * after a while (so that gnurl stops asking).  This task
997  * is the delayed task that actually disconnects the PUT.
998  *
999  * @param cls the `struct Session *` with the put
1000  * @param tc scheduler context
1001  */
1002 static void
1003 client_put_disconnect (void *cls,
1004                        const struct GNUNET_SCHEDULER_TaskContext *tc)
1005 {
1006   struct Session *s = cls;
1007
1008   s->put_disconnect_task = NULL;
1009   LOG (GNUNET_ERROR_TYPE_DEBUG,
1010        "Session %p/request %p: will be disconnected due to no activity\n",
1011        s, s->put.easyhandle);
1012   s->put.state = H_TMP_DISCONNECTING;
1013   if (NULL != s->put.easyhandle)
1014     curl_easy_pause (s->put.easyhandle,
1015                      CURLPAUSE_CONT);
1016   client_schedule (s->plugin, GNUNET_YES);
1017 }
1018
1019
1020 /**
1021  * Callback method used with libcurl
1022  * Method is called when libcurl needs to read data during sending
1023  *
1024  * @param stream pointer where to write data
1025  * @param size size of an individual element
1026  * @param nmemb count of elements that can be written to the buffer
1027  * @param cls our `struct Session`
1028  * @return bytes written to stream, returning 0 will terminate request!
1029  */
1030 static size_t
1031 client_send_cb (void *stream,
1032                 size_t size,
1033                 size_t nmemb,
1034                 void *cls)
1035 {
1036   struct Session *s = cls;
1037   struct HTTP_Client_Plugin *plugin = s->plugin;
1038   struct HTTP_Message *msg = s->msg_head;
1039   size_t len;
1040   char *stat_txt;
1041
1042   if (H_TMP_DISCONNECTING == s->put.state)
1043   {
1044     LOG (GNUNET_ERROR_TYPE_DEBUG,
1045          "Session %p/request %p: disconnect due to inactivity\n",
1046          s, s->put.easyhandle);
1047     return 0;
1048   }
1049
1050   if (NULL == msg)
1051   {
1052     if (GNUNET_YES == plugin->emulate_xhr)
1053     {
1054       LOG (GNUNET_ERROR_TYPE_DEBUG,
1055            "Session %p/request %p: PUT request finished\n",
1056            s,
1057            s->put.easyhandle);
1058       s->put.state = H_TMP_DISCONNECTING;
1059       return 0;
1060     }
1061
1062     /* We have nothing to send, so pause PUT request */
1063     LOG (GNUNET_ERROR_TYPE_DEBUG,
1064          "Session %p/request %p: nothing to send, suspending\n",
1065          s,
1066          s->put.easyhandle);
1067     s->put_disconnect_task = GNUNET_SCHEDULER_add_delayed (PUT_DISCONNECT_TIMEOUT,
1068         &client_put_disconnect, s);
1069     s->put.state = H_PAUSED;
1070     return CURL_READFUNC_PAUSE;
1071   }
1072   /* data to send */
1073   GNUNET_assert (msg->pos < msg->size);
1074   /* calculate how much fits in buffer */
1075   len = GNUNET_MIN (msg->size - msg->pos,
1076                     size * nmemb);
1077   memcpy (stream, &msg->buf[msg->pos], len);
1078   msg->pos += len;
1079   if (msg->pos == msg->size)
1080   {
1081     LOG (GNUNET_ERROR_TYPE_DEBUG,
1082          "Session %p/request %p: sent message with %u bytes sent, removing message from queue\n",
1083          s,
1084          s->put.easyhandle,
1085          msg->size,
1086          msg->pos);
1087     /* Calling transmit continuation  */
1088     GNUNET_CONTAINER_DLL_remove (s->msg_head,
1089                                  s->msg_tail,
1090                                  msg);
1091     GNUNET_assert (0 < s->msgs_in_queue);
1092     s->msgs_in_queue--;
1093     GNUNET_assert (msg->size <= s->bytes_in_queue);
1094     s->bytes_in_queue -= msg->size;
1095     if (NULL != msg->transmit_cont)
1096       msg->transmit_cont (msg->transmit_cont_cls,
1097                           &s->address->peer,
1098                           GNUNET_OK,
1099                           msg->size,
1100                           msg->size + s->overhead);
1101     s->overhead = 0;
1102     GNUNET_free (msg);
1103   }
1104   notify_session_monitor (plugin,
1105                           s,
1106                           GNUNET_TRANSPORT_SS_UPDATE);
1107   GNUNET_asprintf (&stat_txt,
1108                    "# bytes currently in %s_client buffers",
1109                    plugin->protocol);
1110   GNUNET_STATISTICS_update (plugin->env->stats,
1111                             stat_txt,
1112                             - len,
1113                             GNUNET_NO);
1114   GNUNET_free (stat_txt);
1115   GNUNET_asprintf (&stat_txt,
1116                    "# bytes transmitted via %s_client",
1117                    plugin->protocol);
1118   GNUNET_STATISTICS_update (plugin->env->stats,
1119                             stat_txt,
1120                             len,
1121                             GNUNET_NO);
1122   GNUNET_free (stat_txt);
1123   return len;
1124 }
1125
1126
1127 /**
1128  * Wake up a curl handle which was suspended
1129  *
1130  * @param cls the session
1131  * @param tc task context
1132  */
1133 static void
1134 client_wake_up (void *cls,
1135                 const struct GNUNET_SCHEDULER_TaskContext *tc)
1136 {
1137   struct Session *s = cls;
1138
1139   s->recv_wakeup_task = NULL;
1140   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
1141     return;
1142   LOG (GNUNET_ERROR_TYPE_DEBUG,
1143        "Session %p/request %p: Waking up GET handle\n",
1144        s, s->get.easyhandle);
1145   if (H_PAUSED == s->put.state)
1146   {
1147     /* PUT request was paused, unpause */
1148     GNUNET_assert (s->put_disconnect_task != NULL);
1149     GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
1150     s->put_disconnect_task = NULL;
1151     s->put.state = H_CONNECTED;
1152     if (NULL != s->put.easyhandle)
1153       curl_easy_pause (s->put.easyhandle, CURLPAUSE_CONT);
1154   }
1155   if (NULL != s->get.easyhandle)
1156     curl_easy_pause (s->get.easyhandle, CURLPAUSE_CONT);
1157 }
1158
1159
1160 /**
1161  * Callback for message stream tokenizer
1162  *
1163  * @param cls the session
1164  * @param client not used
1165  * @param message the message received
1166  * @return always #GNUNET_OK
1167  */
1168 static int
1169 client_receive_mst_cb (void *cls,
1170                        void *client,
1171                        const struct GNUNET_MessageHeader *message)
1172 {
1173   struct Session *s = cls;
1174   struct HTTP_Client_Plugin *plugin;
1175   struct GNUNET_TIME_Relative delay;
1176   char *stat_txt;
1177
1178   plugin = s->plugin;
1179   delay = s->plugin->env->receive (plugin->env->cls,
1180                                    s->address,
1181                                    s,
1182                                    message);
1183   GNUNET_asprintf (&stat_txt,
1184                    "# bytes received via %s_client",
1185                    plugin->protocol);
1186   GNUNET_STATISTICS_update (plugin->env->stats,
1187                             stat_txt,
1188                             ntohs (message->size),
1189                             GNUNET_NO);
1190   GNUNET_free (stat_txt);
1191
1192   s->next_receive = GNUNET_TIME_relative_to_absolute (delay);
1193   if (GNUNET_TIME_absolute_get ().abs_value_us < s->next_receive.abs_value_us)
1194   {
1195     LOG (GNUNET_ERROR_TYPE_DEBUG,
1196          "Client: peer `%s' address `%s' next read delayed for %s\n",
1197          GNUNET_i2s (&s->address->peer),
1198          http_common_plugin_address_to_string (s->plugin->protocol,
1199                                                s->address->address,
1200                                                s->address->address_length),
1201          GNUNET_STRINGS_relative_time_to_string (delay,
1202                                                  GNUNET_YES));
1203   }
1204   client_reschedule_session_timeout (s);
1205   return GNUNET_OK;
1206 }
1207
1208
1209 /**
1210  * Callback method used with libcurl when data for a PUT request are
1211  * received.  We do not expect data here, so we just discard it.
1212  *
1213  * @param stream pointer where to write data
1214  * @param size size of an individual element
1215  * @param nmemb count of elements that can be written to the buffer
1216  * @param cls destination pointer, passed to the libcurl handle
1217  * @return bytes read from stream
1218  */
1219 static size_t
1220 client_receive_put (void *stream,
1221                     size_t size,
1222                     size_t nmemb,
1223                     void *cls)
1224 {
1225   return size * nmemb;
1226 }
1227
1228
1229 /**
1230  * Callback method used with libcurl when data for a GET request are
1231  * received. Forward to MST
1232  *
1233  * @param stream pointer where to write data
1234  * @param size size of an individual element
1235  * @param nmemb count of elements that can be written to the buffer
1236  * @param cls destination pointer, passed to the libcurl handle
1237  * @return bytes read from stream
1238  */
1239 static size_t
1240 client_receive (void *stream,
1241                 size_t size,
1242                 size_t nmemb,
1243                 void *cls)
1244 {
1245   struct Session *s = cls;
1246   struct GNUNET_TIME_Absolute now;
1247   size_t len = size * nmemb;
1248
1249   LOG (GNUNET_ERROR_TYPE_DEBUG,
1250        "Session %p / request %p: Received %u bytes from peer `%s'\n",
1251        s,
1252        s->get.easyhandle,
1253        len,
1254        GNUNET_i2s (&s->address->peer));
1255   now = GNUNET_TIME_absolute_get ();
1256   if (now.abs_value_us < s->next_receive.abs_value_us)
1257   {
1258     struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
1259     struct GNUNET_TIME_Relative delta
1260       = GNUNET_TIME_absolute_get_difference (now, s->next_receive);
1261
1262     LOG (GNUNET_ERROR_TYPE_DEBUG,
1263          "Session %p / request %p: No inbound bandwidth available! Next read was delayed for %s\n",
1264          s,
1265          s->get.easyhandle,
1266          GNUNET_STRINGS_relative_time_to_string (delta,
1267                                                  GNUNET_YES));
1268     if (s->recv_wakeup_task != NULL)
1269     {
1270       GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
1271       s->recv_wakeup_task = NULL;
1272     }
1273     s->recv_wakeup_task
1274       = GNUNET_SCHEDULER_add_delayed (delta,
1275                                       &client_wake_up,
1276                                       s);
1277     return CURL_WRITEFUNC_PAUSE;
1278   }
1279   if (NULL == s->msg_tk)
1280     s->msg_tk = GNUNET_SERVER_mst_create (&client_receive_mst_cb,
1281                                           s);
1282   GNUNET_SERVER_mst_receive (s->msg_tk,
1283                              s,
1284                              stream,
1285                              len,
1286                              GNUNET_NO,
1287                              GNUNET_NO);
1288   return len;
1289 }
1290
1291
1292 /**
1293  * Task performing curl operations
1294  *
1295  * @param cls plugin as closure
1296  * @param tc scheduler task context
1297  */
1298 static void
1299 client_run (void *cls,
1300             const struct GNUNET_SCHEDULER_TaskContext *tc)
1301 {
1302   struct HTTP_Client_Plugin *plugin = cls;
1303   int running;
1304   long http_statuscode;
1305   CURLMcode mret;
1306   CURLMsg *msg;
1307   int put_request; /* GNUNET_YES if easy handle is put, GNUNET_NO for get */
1308   int msgs_left;
1309
1310   plugin->client_perform_task = NULL;
1311   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
1312     return;
1313
1314   /* While data are available or timeouts occured */
1315   do
1316   {
1317     running = 0;
1318     /* Perform operations for all handles */
1319     mret = curl_multi_perform (plugin->curl_multi_handle, &running);
1320
1321     /* Get additional information for all handles */
1322     while (NULL != (msg = curl_multi_info_read (plugin->curl_multi_handle, &msgs_left)))
1323     {
1324       CURL *easy_h = msg->easy_handle;
1325       struct Session *s = NULL;
1326       char *d = NULL; /* curl requires 'd' to be a 'char *' */
1327
1328       GNUNET_assert (NULL != easy_h);
1329
1330       /* Obtain session from easy handle */
1331       GNUNET_assert (CURLE_OK == curl_easy_getinfo (easy_h, CURLINFO_PRIVATE, &d));
1332       s = (struct Session *) d;
1333       GNUNET_assert (NULL != s);
1334
1335       if (msg->msg != CURLMSG_DONE)
1336         continue; /* This should not happen */
1337
1338       /* Get HTTP response code */
1339       GNUNET_break (CURLE_OK == curl_easy_getinfo (easy_h,
1340           CURLINFO_RESPONSE_CODE, &http_statuscode));
1341
1342       if (easy_h == s->put.easyhandle)
1343         put_request = GNUNET_YES;
1344       else
1345         put_request = GNUNET_NO;
1346
1347       /* Log status of terminated request */
1348       if  ((0 != msg->data.result) || (http_statuscode != 200))
1349         LOG (GNUNET_ERROR_TYPE_DEBUG,
1350              "Session %p/request %p: %s request to `%s' ended with status %i reason %i: `%s'\n",
1351              s, msg->easy_handle,
1352              (GNUNET_YES == put_request) ? "PUT" : "GET",
1353              GNUNET_i2s (&s->address->peer),
1354              http_statuscode,
1355              msg->data.result,
1356              curl_easy_strerror (msg->data.result));
1357       else
1358         LOG (GNUNET_ERROR_TYPE_DEBUG,
1359              "Session %p/request %p: %s request to `%s' ended normal\n",
1360              s, msg->easy_handle,
1361              (GNUNET_YES == put_request) ? "PUT" : "GET",
1362              GNUNET_i2s (&s->address->peer));
1363
1364       /* Remove easy handle from multi handle */
1365       curl_multi_remove_handle (plugin->curl_multi_handle, easy_h);
1366
1367       /* Clean up easy handle */
1368       curl_easy_cleanup (easy_h);
1369
1370       /* Remove information */
1371       GNUNET_assert (plugin->cur_requests > 0);
1372       plugin->cur_requests--;
1373       LOG  (GNUNET_ERROR_TYPE_INFO,
1374           "%s request to %s done, number of requests decreased to %u\n",
1375           (GNUNET_YES == put_request) ? "PUT" : "GET",
1376           s->url,
1377           plugin->cur_requests);
1378
1379       if (GNUNET_YES == put_request)
1380       {
1381         /* Clean up a PUT request */
1382         s->put.easyhandle = NULL;
1383         s->put.s = NULL;
1384
1385         switch (s->put.state) {
1386           case H_NOT_CONNECTED:
1387           case H_DISCONNECTED:
1388           case H_TMP_DISCONNECTED:
1389             /* This must not happen */
1390             GNUNET_break (0);
1391             break;
1392           case H_TMP_RECONNECT_REQUIRED:
1393             /* Transport called send while disconnect in progess, reconnect */
1394             if (GNUNET_SYSERR == client_connect_put (s))
1395             {
1396               /* Reconnect failed, disconnect session */
1397               http_client_plugin_session_disconnect (plugin, s);
1398             }
1399             break;
1400           case H_TMP_DISCONNECTING:
1401             /* PUT gets temporarily disconnected */
1402             s->put.state = H_TMP_DISCONNECTED;
1403             break;
1404           case H_PAUSED:
1405           case H_CONNECTED:
1406             /* PUT gets permanently disconnected */
1407             s->put.state = H_DISCONNECTED;
1408             http_client_plugin_session_disconnect (plugin, s);
1409             break;
1410           default:
1411             GNUNET_break (0);
1412             break;
1413         }
1414       }
1415       else if (GNUNET_NO == put_request)
1416       {
1417         /* Clean up a GET request */
1418         s->get.easyhandle = NULL;
1419         s->get.s = NULL;
1420
1421         /* If we are emulating an XHR client we need to make another GET
1422          * request.
1423          */
1424         if (GNUNET_YES == plugin->emulate_xhr)
1425         {
1426           if (GNUNET_SYSERR == client_connect_get (s))
1427             http_client_plugin_session_disconnect (plugin, s);
1428         }
1429         else
1430         {
1431           /* GET request was terminated, so disconnect session */
1432           http_client_plugin_session_disconnect (plugin, s);
1433         }
1434       }
1435       else
1436         GNUNET_break (0); /* Must not happen */
1437
1438       GNUNET_STATISTICS_set (plugin->env->stats,
1439                              HTTP_STAT_STR_CONNECTIONS,
1440                              plugin->cur_requests,
1441                              GNUNET_NO);
1442     }
1443   }
1444   while (mret == CURLM_CALL_MULTI_PERFORM);
1445   client_schedule (plugin, GNUNET_NO);
1446 }
1447
1448
1449 #ifdef TCP_STEALTH
1450 /**
1451  * Open TCP socket with TCP STEALTH enabled.
1452  *
1453  * @param clientp our `struct Session *`
1454  * @param purpose why does curl want to open a socket
1455  * @param address what kind of socket does curl want to have opened?
1456  * @return opened socket
1457  */
1458 static curl_socket_t
1459 open_tcp_stealth_socket_cb (void *clientp,
1460                             curlsocktype purpose,
1461                             struct curl_sockaddr *address)
1462 {
1463   struct Session *s = clientp;
1464   int ret;
1465
1466   switch (purpose)
1467   {
1468   case CURLSOCKTYPE_IPCXN:
1469     ret = socket (address->family,
1470                   address->socktype,
1471                   address->protocol);
1472     if (-1 == ret)
1473       return CURL_SOCKET_BAD;
1474     if ( ( (SOCK_STREAM != address->socktype) ||
1475            ( (0 != address->protocol) &&
1476              (IPPROTO_TCP != address->protocol))) )
1477       return (curl_socket_t) ret;
1478     if ( (0 != setsockopt (ret,
1479                            IPPROTO_TCP,
1480                            TCP_STEALTH,
1481                            &s->address->peer,
1482                            sizeof (struct GNUNET_PeerIdentity))) )
1483     {
1484       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1485                   _("TCP_STEALTH not supported on this platform.\n"));
1486       (void) close (ret);
1487       return CURL_SOCKET_BAD;
1488     }
1489     return (curl_socket_t) ret;
1490   case CURLSOCKTYPE_ACCEPT:
1491     GNUNET_break (0);
1492     return CURL_SOCKET_BAD;
1493     break;
1494   case CURLSOCKTYPE_LAST:
1495     GNUNET_break (0);
1496     return CURL_SOCKET_BAD;
1497   default:
1498     GNUNET_break (0);
1499     return CURL_SOCKET_BAD;
1500   }
1501 }
1502 #endif
1503
1504
1505 /**
1506  * Connect GET request for a session
1507  *
1508  * @param s the session to connect
1509  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
1510  */
1511 static int
1512 client_connect_get (struct Session *s)
1513 {
1514   CURLMcode mret;
1515   struct HttpAddress *ha;
1516   uint32_t options;
1517
1518   ha = (struct HttpAddress *) s->address->address;
1519   options = ntohl (ha->options);
1520   /* create get request */
1521   s->get.easyhandle = curl_easy_init ();
1522   s->get.s = s;
1523   if (0 != (options & HTTP_OPTIONS_TCP_STEALTH))
1524   {
1525 #ifdef TCP_STEALTH
1526     curl_easy_setopt (s->get.easyhandle,
1527                       CURLOPT_OPENSOCKETFUNCTION,
1528                       &open_tcp_stealth_socket_cb);
1529     curl_easy_setopt (s->get.easyhandle,
1530                       CURLOPT_OPENSOCKETDATA,
1531                       s);
1532 #else
1533     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1534                 "Cannot connect, TCP STEALTH needed and not supported by kernel.\n");
1535     curl_easy_cleanup (s->get.easyhandle);
1536     s->get.easyhandle = NULL;
1537     s->get.s = NULL;
1538     return GNUNET_SYSERR;
1539 #endif
1540   }
1541
1542 #if VERBOSE_CURL
1543   curl_easy_setopt (s->get.easyhandle,
1544                     CURLOPT_VERBOSE,
1545                     1L);
1546   curl_easy_setopt (s->get.easyhandle,
1547                     CURLOPT_DEBUGFUNCTION,
1548                     &client_log);
1549   curl_easy_setopt (s->get.easyhandle,
1550                     CURLOPT_DEBUGDATA,
1551                     &s->get);
1552 #endif
1553 #if BUILD_HTTPS
1554   curl_easy_setopt (s->get.easyhandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
1555   {
1556     if (HTTP_OPTIONS_VERIFY_CERTIFICATE ==
1557         (options & HTTP_OPTIONS_VERIFY_CERTIFICATE))
1558     {
1559       curl_easy_setopt (s->get.easyhandle,
1560                         CURLOPT_SSL_VERIFYPEER, 1L);
1561       curl_easy_setopt (s->get.easyhandle,
1562                         CURLOPT_SSL_VERIFYHOST,
1563                         2L);
1564     }
1565     else
1566     {
1567       curl_easy_setopt (s->get.easyhandle,
1568                         CURLOPT_SSL_VERIFYPEER,
1569                         0L);
1570       curl_easy_setopt (s->get.easyhandle,
1571                         CURLOPT_SSL_VERIFYHOST,
1572                         0L);
1573     }
1574   }
1575   curl_easy_setopt (s->get.easyhandle,
1576                     CURLOPT_PROTOCOLS,
1577                     CURLPROTO_HTTPS);
1578   curl_easy_setopt (s->get.easyhandle,
1579                     CURLOPT_REDIR_PROTOCOLS,
1580                     CURLPROTO_HTTPS);
1581 #else
1582   curl_easy_setopt (s->get.easyhandle,
1583                     CURLOPT_PROTOCOLS,
1584                     CURLPROTO_HTTP);
1585   curl_easy_setopt (s->get.easyhandle,
1586                     CURLOPT_REDIR_PROTOCOLS,
1587                     CURLPROTO_HTTP);
1588 #endif
1589
1590   if (NULL != s->plugin->proxy_hostname)
1591   {
1592     curl_easy_setopt (s->get.easyhandle,
1593                       CURLOPT_PROXY,
1594                       s->plugin->proxy_hostname);
1595     curl_easy_setopt (s->get.easyhandle,
1596                       CURLOPT_PROXYTYPE,
1597                       s->plugin->proxytype);
1598     if (NULL != s->plugin->proxy_username)
1599       curl_easy_setopt (s->get.easyhandle,
1600                         CURLOPT_PROXYUSERNAME,
1601                         s->plugin->proxy_username);
1602     if (NULL != s->plugin->proxy_password)
1603       curl_easy_setopt (s->get.easyhandle,
1604                         CURLOPT_PROXYPASSWORD,
1605                         s->plugin->proxy_password);
1606     if (GNUNET_YES == s->plugin->proxy_use_httpproxytunnel)
1607       curl_easy_setopt (s->get.easyhandle,
1608                         CURLOPT_HTTPPROXYTUNNEL,
1609                         s->plugin->proxy_use_httpproxytunnel);
1610   }
1611
1612   if (GNUNET_YES == s->plugin->emulate_xhr)
1613   {
1614     char *url;
1615
1616     GNUNET_asprintf (&url,
1617                      "%s,1",
1618                      s->url);
1619     curl_easy_setopt (s->get.easyhandle,
1620                       CURLOPT_URL,
1621                       url);
1622     GNUNET_free(url);
1623   }
1624   else
1625   {
1626     curl_easy_setopt (s->get.easyhandle,
1627                       CURLOPT_URL,
1628                       s->url);
1629   }
1630   curl_easy_setopt (s->get.easyhandle,
1631                     CURLOPT_READFUNCTION,
1632                     &client_send_cb);
1633   curl_easy_setopt (s->get.easyhandle,
1634                     CURLOPT_READDATA,
1635                     s);
1636   curl_easy_setopt (s->get.easyhandle,
1637                     CURLOPT_WRITEFUNCTION,
1638                     &client_receive);
1639   curl_easy_setopt (s->get.easyhandle,
1640                     CURLOPT_WRITEDATA,
1641                     s);
1642   /* No timeout by default, timeout done with session timeout */
1643   curl_easy_setopt (s->get.easyhandle,
1644                     CURLOPT_TIMEOUT,
1645                     0L);
1646   curl_easy_setopt (s->get.easyhandle,
1647                     CURLOPT_PRIVATE, s);
1648   curl_easy_setopt (s->get.easyhandle,
1649                     CURLOPT_CONNECTTIMEOUT_MS,
1650                     (long) (HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value_us / 1000LL));
1651   curl_easy_setopt (s->get.easyhandle, CURLOPT_BUFFERSIZE,
1652                     2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1653 #if CURL_TCP_NODELAY
1654   curl_easy_setopt (ps->recv_endpoint,
1655                     CURLOPT_TCP_NODELAY,
1656                     1L);
1657 #endif
1658   curl_easy_setopt (s->get.easyhandle,
1659                     CURLOPT_FOLLOWLOCATION,
1660                     0L);
1661
1662   mret = curl_multi_add_handle (s->plugin->curl_multi_handle,
1663                                 s->get.easyhandle);
1664   if (CURLM_OK != mret)
1665   {
1666     LOG (GNUNET_ERROR_TYPE_ERROR,
1667          "Session %p : Failed to add GET handle to multihandle: `%s'\n",
1668          s,
1669          curl_multi_strerror (mret));
1670     curl_easy_cleanup (s->get.easyhandle);
1671     s->get.easyhandle = NULL;
1672     s->get.s = NULL;
1673     GNUNET_break (0);
1674     return GNUNET_SYSERR;
1675   }
1676   s->plugin->cur_requests++;
1677   LOG (GNUNET_ERROR_TYPE_INFO,
1678        "GET request `%s' established, number of requests increased to %u\n",
1679        s->url,
1680        s->plugin->cur_requests);
1681   return GNUNET_OK;
1682 }
1683
1684
1685 /**
1686  * Connect a HTTP put request
1687  *
1688  * @param s the session to connect
1689  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for ok
1690  */
1691 static int
1692 client_connect_put (struct Session *s)
1693 {
1694   CURLMcode mret;
1695   struct HttpAddress *ha;
1696   uint32_t options;
1697
1698   ha = (struct HttpAddress *) s->address->address;
1699   options = ntohl (ha->options);
1700   /* create put request */
1701   LOG (GNUNET_ERROR_TYPE_DEBUG,
1702        "Session %p: Init PUT handle\n",
1703        s);
1704   s->put.easyhandle = curl_easy_init ();
1705   s->put.s = s;
1706 #if VERBOSE_CURL
1707   curl_easy_setopt (s->put.easyhandle,
1708                     CURLOPT_VERBOSE,
1709                     1L);
1710   curl_easy_setopt (s->put.easyhandle,
1711                     CURLOPT_DEBUGFUNCTION,
1712                     &client_log);
1713   curl_easy_setopt (s->put.easyhandle,
1714                     CURLOPT_DEBUGDATA,
1715                     &s->put);
1716 #endif
1717   if (0 != (options & HTTP_OPTIONS_TCP_STEALTH))
1718   {
1719 #ifdef TCP_STEALTH
1720     curl_easy_setopt (s->put.easyhandle,
1721                       CURLOPT_OPENSOCKETFUNCTION,
1722                       &open_tcp_stealth_socket_cb);
1723     curl_easy_setopt (s->put.easyhandle,
1724                       CURLOPT_OPENSOCKETDATA,
1725                       s);
1726 #else
1727     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1728                 "Cannot connect, TCP STEALTH needed and not supported by kernel.\n");
1729     curl_easy_cleanup (s->put.easyhandle);
1730     s->put.easyhandle = NULL;
1731     s->put.s = NULL;
1732     s->put.state = H_DISCONNECTED;
1733     return GNUNET_SYSERR;
1734 #endif
1735   }
1736 #if BUILD_HTTPS
1737   curl_easy_setopt (s->put.easyhandle,
1738                     CURLOPT_SSLVERSION,
1739                     CURL_SSLVERSION_TLSv1);
1740   {
1741     struct HttpAddress *ha;
1742     ha = (struct HttpAddress *) s->address->address;
1743
1744     if (HTTP_OPTIONS_VERIFY_CERTIFICATE ==
1745         (ntohl (ha->options) & HTTP_OPTIONS_VERIFY_CERTIFICATE))
1746     {
1747       curl_easy_setopt (s->put.easyhandle,
1748                         CURLOPT_SSL_VERIFYPEER,
1749                         1L);
1750       curl_easy_setopt (s->put.easyhandle,
1751                         CURLOPT_SSL_VERIFYHOST,
1752                         2L);
1753     }
1754     else
1755     {
1756       curl_easy_setopt (s->put.easyhandle,
1757                         CURLOPT_SSL_VERIFYPEER,
1758                         0L);
1759       curl_easy_setopt (s->put.easyhandle,
1760                         CURLOPT_SSL_VERIFYHOST,
1761                         0L);
1762     }
1763   }
1764   curl_easy_setopt (s->put.easyhandle,
1765                     CURLOPT_PROTOCOLS,
1766                     CURLPROTO_HTTPS);
1767   curl_easy_setopt (s->put.easyhandle,
1768                     CURLOPT_REDIR_PROTOCOLS,
1769                     CURLPROTO_HTTPS);
1770 #else
1771   curl_easy_setopt (s->put.easyhandle,
1772                     CURLOPT_PROTOCOLS,
1773                     CURLPROTO_HTTP);
1774   curl_easy_setopt (s->put.easyhandle,
1775                     CURLOPT_REDIR_PROTOCOLS,
1776                     CURLPROTO_HTTP);
1777 #endif
1778   if (NULL != s->plugin->proxy_hostname)
1779   {
1780     curl_easy_setopt (s->put.easyhandle,
1781                       CURLOPT_PROXY,
1782                       s->plugin->proxy_hostname);
1783     curl_easy_setopt (s->put.easyhandle,
1784                       CURLOPT_PROXYTYPE,
1785                       s->plugin->proxytype);
1786     if (NULL != s->plugin->proxy_username)
1787       curl_easy_setopt (s->put.easyhandle,
1788                         CURLOPT_PROXYUSERNAME,
1789                         s->plugin->proxy_username);
1790     if (NULL != s->plugin->proxy_password)
1791       curl_easy_setopt (s->put.easyhandle,
1792                         CURLOPT_PROXYPASSWORD,
1793                         s->plugin->proxy_password);
1794     if (GNUNET_YES == s->plugin->proxy_use_httpproxytunnel)
1795       curl_easy_setopt (s->put.easyhandle,
1796                         CURLOPT_HTTPPROXYTUNNEL,
1797                         s->plugin->proxy_use_httpproxytunnel);
1798   }
1799
1800   curl_easy_setopt (s->put.easyhandle,
1801                     CURLOPT_URL,
1802                     s->url);
1803   curl_easy_setopt (s->put.easyhandle,
1804                     CURLOPT_UPLOAD,
1805                     1L);
1806   curl_easy_setopt (s->put.easyhandle,
1807                     CURLOPT_READFUNCTION,
1808                     &client_send_cb);
1809   curl_easy_setopt (s->put.easyhandle,
1810                     CURLOPT_READDATA,
1811                     s);
1812   curl_easy_setopt (s->put.easyhandle,
1813                     CURLOPT_WRITEFUNCTION,
1814                     &client_receive_put);
1815   curl_easy_setopt (s->put.easyhandle,
1816                     CURLOPT_WRITEDATA,
1817                     s);
1818   /* No timeout by default, timeout done with session timeout */
1819   curl_easy_setopt (s->put.easyhandle,
1820                     CURLOPT_TIMEOUT,
1821                     0L);
1822   curl_easy_setopt (s->put.easyhandle,
1823                     CURLOPT_PRIVATE,
1824                     s);
1825   curl_easy_setopt (s->put.easyhandle,
1826                     CURLOPT_CONNECTTIMEOUT_MS,
1827                     (long) (HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value_us / 1000LL));
1828   curl_easy_setopt (s->put.easyhandle, CURLOPT_BUFFERSIZE,
1829                     2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1830 #if CURL_TCP_NODELAY
1831   curl_easy_setopt (s->put.easyhandle, CURLOPT_TCP_NODELAY, 1);
1832 #endif
1833   mret = curl_multi_add_handle (s->plugin->curl_multi_handle,
1834                                 s->put.easyhandle);
1835   if (CURLM_OK != mret)
1836   {
1837     LOG (GNUNET_ERROR_TYPE_ERROR,
1838          "Session %p : Failed to add PUT handle to multihandle: `%s'\n",
1839          s, curl_multi_strerror (mret));
1840     curl_easy_cleanup (s->put.easyhandle);
1841     s->put.easyhandle = NULL;
1842     s->put.s = NULL;
1843     s->put.state = H_DISCONNECTED;
1844     return GNUNET_SYSERR;
1845   }
1846   s->put.state = H_CONNECTED;
1847   s->plugin->cur_requests++;
1848
1849   LOG  (GNUNET_ERROR_TYPE_INFO,
1850       "PUT request `%s' established, number of requests increased to %u\n",
1851       s->url, s->plugin->cur_requests);
1852
1853   return GNUNET_OK;
1854 }
1855
1856
1857 /**
1858  * Connect both PUT and GET request for a session
1859  *
1860  * @param s the session to connect
1861  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
1862  */
1863 static int
1864 client_connect (struct Session *s)
1865 {
1866   struct HTTP_Client_Plugin *plugin = s->plugin;
1867   int res = GNUNET_OK;
1868
1869   /* create url */
1870   if (NULL ==
1871       http_common_plugin_address_to_string(plugin->protocol,
1872                                            s->address->address,
1873                                            s->address->address_length))
1874   {
1875     LOG (GNUNET_ERROR_TYPE_DEBUG,
1876          "Invalid address peer `%s'\n",
1877          GNUNET_i2s(&s->address->peer));
1878     return GNUNET_SYSERR;
1879   }
1880
1881   GNUNET_asprintf (&s->url,
1882                    "%s/%s;%u",
1883                    http_common_plugin_address_to_url (NULL,
1884                                                       s->address->address,
1885                                                       s->address->address_length),
1886                    GNUNET_i2s_full (plugin->env->my_identity),
1887                    plugin->last_tag);
1888
1889   plugin->last_tag++;
1890   LOG (GNUNET_ERROR_TYPE_DEBUG,
1891        "Initiating outbound session peer `%s' using address `%s'\n",
1892        GNUNET_i2s (&s->address->peer), s->url);
1893
1894   if (GNUNET_SYSERR == client_connect_get (s))
1895     return GNUNET_SYSERR;
1896   /* If we are emulating an XHR client then delay sending a PUT request until
1897    * there is something to send.
1898    */
1899   if (GNUNET_YES == plugin->emulate_xhr)
1900   {
1901     s->put.state = H_TMP_DISCONNECTED;
1902   }
1903   else if (GNUNET_SYSERR == client_connect_put (s))
1904     return GNUNET_SYSERR;
1905
1906   LOG (GNUNET_ERROR_TYPE_DEBUG,
1907        "Session %p: connected with GET %p and PUT %p\n",
1908        s, s->get.easyhandle,
1909        s->put.easyhandle);
1910   /* Perform connect */
1911   GNUNET_STATISTICS_set (plugin->env->stats,
1912                          HTTP_STAT_STR_CONNECTIONS,
1913                          plugin->cur_requests,
1914                          GNUNET_NO);
1915   /* Re-schedule since handles have changed */
1916   if (NULL != plugin->client_perform_task)
1917   {
1918     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
1919     plugin->client_perform_task = NULL;
1920   }
1921
1922   /* Schedule task to run immediately */
1923   plugin->client_perform_task = GNUNET_SCHEDULER_add_now (client_run,
1924                                                           plugin);
1925   return res;
1926 }
1927
1928
1929 /**
1930  * Function obtain the network type for a session
1931  *
1932  * @param cls closure (`struct Plugin*`)
1933  * @param session the session
1934  * @return the network type
1935  */
1936 static enum GNUNET_ATS_Network_Type
1937 http_client_plugin_get_network (void *cls,
1938                                 struct Session *session)
1939 {
1940   return session->scope;
1941 }
1942
1943
1944 /**
1945  * Function obtain the network type for an address.
1946  *
1947  * @param cls closure (`struct Plugin *`)
1948  * @param address the address
1949  * @return the network type
1950  */
1951 static enum GNUNET_ATS_Network_Type
1952 http_client_plugin_get_network_for_address (void *cls,
1953                                             const struct GNUNET_HELLO_Address *address)
1954 {
1955   struct HTTP_Client_Plugin *plugin = cls;
1956
1957   return http_common_get_network_for_address (plugin->env,
1958                                               address);
1959 }
1960
1961
1962 /**
1963  * Session was idle, so disconnect it
1964  *
1965  * @param cls the `struct Session` of the idle session
1966  * @param tc scheduler context
1967  */
1968 static void
1969 client_session_timeout (void *cls,
1970                         const struct GNUNET_SCHEDULER_TaskContext *tc)
1971 {
1972   struct Session *s = cls;
1973   struct GNUNET_TIME_Relative left;
1974
1975   s->timeout_task = NULL;
1976   left = GNUNET_TIME_absolute_get_remaining (s->timeout);
1977   if (0 != left.rel_value_us)
1978   {
1979     /* not actually our turn yet, but let's at least update
1980        the monitor, it may think we're about to die ... */
1981     notify_session_monitor (s->plugin,
1982                             s,
1983                             GNUNET_TRANSPORT_SS_UPDATE);
1984     s->timeout_task = GNUNET_SCHEDULER_add_delayed (left,
1985                                                     &client_session_timeout,
1986                                                     s);
1987     return;
1988   }
1989   LOG (TIMEOUT_LOG,
1990        "Session %p was idle for %s, disconnecting\n",
1991        s,
1992        GNUNET_STRINGS_relative_time_to_string (HTTP_CLIENT_SESSION_TIMEOUT,
1993                                                GNUNET_YES));
1994   GNUNET_assert (GNUNET_OK ==
1995                  http_client_plugin_session_disconnect (s->plugin,
1996                                                  s));
1997 }
1998
1999
2000 /**
2001  * Creates a new outbound session the transport service will use to
2002  * send data to the peer
2003  *
2004  * @param cls the plugin
2005  * @param address the address
2006  * @return the session or NULL of max connections exceeded
2007  */
2008 static struct Session *
2009 http_client_plugin_get_session (void *cls,
2010                                 const struct GNUNET_HELLO_Address *address)
2011 {
2012   struct HTTP_Client_Plugin *plugin = cls;
2013   struct Session *s;
2014   struct sockaddr *sa;
2015   enum GNUNET_ATS_Network_Type net_type;
2016   size_t salen = 0;
2017   int res;
2018
2019   GNUNET_assert (NULL != address->address);
2020
2021   /* find existing session */
2022   s = client_lookup_session (plugin, address);
2023   if (NULL != s)
2024     return s;
2025
2026   /* create a new session */
2027   if (plugin->max_requests <= plugin->cur_requests)
2028   {
2029     LOG (GNUNET_ERROR_TYPE_WARNING,
2030          "Maximum number of requests (%u) reached: "
2031          "cannot connect to peer `%s'\n",
2032          plugin->max_requests,
2033          GNUNET_i2s (&address->peer));
2034     return NULL;
2035   }
2036
2037   /* Determine network location */
2038   net_type = GNUNET_ATS_NET_UNSPECIFIED;
2039   sa = http_common_socket_from_address (address->address,
2040                                         address->address_length,
2041                                         &res);
2042   if (GNUNET_SYSERR == res)
2043     return NULL;
2044   if (GNUNET_YES == res)
2045   {
2046     GNUNET_assert (NULL != sa);
2047     if (AF_INET == sa->sa_family)
2048     {
2049       salen = sizeof (struct sockaddr_in);
2050     }
2051     else if (AF_INET6 == sa->sa_family)
2052     {
2053       salen = sizeof (struct sockaddr_in6);
2054     }
2055     net_type = plugin->env->get_address_type (plugin->env->cls, sa, salen);
2056     GNUNET_free (sa);
2057   }
2058   else if (GNUNET_NO == res)
2059   {
2060     /* Cannot convert to sockaddr -> is external hostname */
2061     net_type = GNUNET_ATS_NET_WAN;
2062   }
2063   if (GNUNET_ATS_NET_UNSPECIFIED == net_type)
2064   {
2065     GNUNET_break (0);
2066     return NULL;
2067   }
2068
2069   s = GNUNET_new (struct Session);
2070   s->plugin = plugin;
2071   s->address = GNUNET_HELLO_address_copy (address);
2072   s->scope = net_type;
2073
2074   s->put.state = H_NOT_CONNECTED;
2075   s->timeout = GNUNET_TIME_relative_to_absolute (HTTP_CLIENT_SESSION_TIMEOUT);
2076   s->timeout_task =  GNUNET_SCHEDULER_add_delayed (HTTP_CLIENT_SESSION_TIMEOUT,
2077                                                    &client_session_timeout,
2078                                                    s);
2079   LOG (GNUNET_ERROR_TYPE_DEBUG,
2080        "Created new session %p for `%s' address `%s''\n",
2081        s,
2082        http_common_plugin_address_to_string (plugin->protocol,
2083                                              s->address->address,
2084                                              s->address->address_length),
2085        GNUNET_i2s (&s->address->peer));
2086
2087   /* add new session */
2088   (void) GNUNET_CONTAINER_multipeermap_put (plugin->sessions,
2089                                             &s->address->peer,
2090                                             s,
2091                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
2092   /* initiate new connection */
2093   if (GNUNET_SYSERR == client_connect (s))
2094   {
2095     LOG (GNUNET_ERROR_TYPE_ERROR,
2096          "Cannot connect to peer `%s' address `%s''\n",
2097          http_common_plugin_address_to_string (plugin->protocol,
2098              s->address->address, s->address->address_length),
2099              GNUNET_i2s (&s->address->peer));
2100     client_delete_session (s);
2101     return NULL;
2102   }
2103   notify_session_monitor (plugin,
2104                           s,
2105                           GNUNET_TRANSPORT_SS_INIT);
2106   notify_session_monitor (plugin,
2107                           s,
2108                           GNUNET_TRANSPORT_SS_UP); /* or handshake? */
2109   return s;
2110 }
2111
2112
2113 /**
2114  * Setup http_client plugin
2115  *
2116  * @param plugin the plugin handle
2117  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
2118  */
2119 static int
2120 client_start (struct HTTP_Client_Plugin *plugin)
2121 {
2122   curl_global_init (CURL_GLOBAL_ALL);
2123   plugin->curl_multi_handle = curl_multi_init ();
2124
2125   if (NULL == plugin->curl_multi_handle)
2126   {
2127     LOG (GNUNET_ERROR_TYPE_ERROR,
2128          _("Could not initialize curl multi handle, failed to start %s plugin!\n"),
2129          plugin->name);
2130     return GNUNET_SYSERR;
2131   }
2132   return GNUNET_OK;
2133 }
2134
2135
2136 /**
2137  * Another peer has suggested an address for this
2138  * peer and transport plugin.  Check that this could be a valid
2139  * address.  If so, consider adding it to the list
2140  * of addresses.
2141  *
2142  * @param cls closure with the `struct Plugin`
2143  * @param addr pointer to the address
2144  * @param addrlen length of @a addr
2145  * @return #GNUNET_OK if this is a plausible address for this peer
2146  *         and transport; always returns #GNUNET_NO (this is the client!)
2147  */
2148 static int
2149 http_client_plugin_address_suggested (void *cls,
2150                                       const void *addr,
2151                                       size_t addrlen)
2152 {
2153   /* A HTTP/S client does not have any valid address so:*/
2154   return GNUNET_NO;
2155 }
2156
2157
2158 /**
2159  * Exit point from the plugin.
2160  *
2161  * @param cls api as closure
2162  * @return NULL
2163  */
2164 void *
2165 LIBGNUNET_PLUGIN_TRANSPORT_DONE (void *cls)
2166 {
2167   struct GNUNET_TRANSPORT_PluginFunctions *api = cls;
2168   struct HTTP_Client_Plugin *plugin = api->cls;
2169
2170   if (NULL == api->cls)
2171   {
2172     /* Stub shutdown */
2173     GNUNET_free (api);
2174     return NULL;
2175   }
2176   LOG (GNUNET_ERROR_TYPE_DEBUG,
2177        _("Shutting down plugin `%s'\n"),
2178        plugin->name);
2179   GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
2180                                          &destroy_session_cb,
2181                                          plugin);
2182   if (NULL != plugin->client_perform_task)
2183   {
2184     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
2185     plugin->client_perform_task = NULL;
2186   }
2187   if (NULL != plugin->curl_multi_handle)
2188   {
2189     curl_multi_cleanup (plugin->curl_multi_handle);
2190     plugin->curl_multi_handle = NULL;
2191   }
2192   curl_global_cleanup ();
2193   LOG (GNUNET_ERROR_TYPE_DEBUG,
2194        _("Shutdown for plugin `%s' complete\n"),
2195        plugin->name);
2196   GNUNET_CONTAINER_multipeermap_destroy (plugin->sessions);
2197   GNUNET_free_non_null (plugin->proxy_hostname);
2198   GNUNET_free_non_null (plugin->proxy_username);
2199   GNUNET_free_non_null (plugin->proxy_password);
2200   GNUNET_free (plugin);
2201   GNUNET_free (api);
2202   return NULL;
2203 }
2204
2205
2206 /**
2207  * Configure plugin
2208  *
2209  * @param plugin the plugin handle
2210  * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
2211  */
2212 static int
2213 client_configure_plugin (struct HTTP_Client_Plugin *plugin)
2214 {
2215   unsigned long long max_requests;
2216   char *proxy_type;
2217
2218   /* Optional parameters */
2219   if (GNUNET_OK !=
2220       GNUNET_CONFIGURATION_get_value_number (plugin->env->cfg,
2221                                              plugin->name,
2222                                              "MAX_CONNECTIONS",
2223                                              &max_requests))
2224     max_requests = 128;
2225   plugin->max_requests = max_requests;
2226
2227   LOG (GNUNET_ERROR_TYPE_DEBUG,
2228        _("Maximum number of requests is %u\n"),
2229        plugin->max_requests);
2230
2231   /* Read proxy configuration */
2232   if (GNUNET_OK ==
2233       GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2234                                              plugin->name,
2235                                              "PROXY",
2236                                              &plugin->proxy_hostname))
2237   {
2238     LOG (GNUNET_ERROR_TYPE_DEBUG,
2239          "Found proxy host: `%s'\n",
2240          plugin->proxy_hostname);
2241     /* proxy username */
2242     if (GNUNET_OK ==
2243         GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2244                                                plugin->name,
2245                                                "PROXY_USERNAME",
2246                                                &plugin->proxy_username))
2247     {
2248       LOG (GNUNET_ERROR_TYPE_DEBUG,
2249            "Found proxy username name: `%s'\n",
2250            plugin->proxy_username);
2251     }
2252
2253     /* proxy password */
2254     if (GNUNET_OK ==
2255         GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2256                                                plugin->name,
2257                                                "PROXY_PASSWORD",
2258                                                &plugin->proxy_password))
2259     {
2260       LOG (GNUNET_ERROR_TYPE_DEBUG,
2261            "Found proxy password name: `%s'\n",
2262            plugin->proxy_password);
2263     }
2264
2265     /* proxy type */
2266     if (GNUNET_OK ==
2267         GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2268                                                plugin->name,
2269                                                "PROXY_TYPE",
2270                                                &proxy_type))
2271     {
2272       GNUNET_STRINGS_utf8_toupper (proxy_type, proxy_type);
2273
2274       if (0 == strcmp(proxy_type, "HTTP"))
2275         plugin->proxytype = CURLPROXY_HTTP;
2276       else if (0 == strcmp(proxy_type, "SOCKS4"))
2277         plugin->proxytype = CURLPROXY_SOCKS4;
2278       else if (0 == strcmp(proxy_type, "SOCKS5"))
2279         plugin->proxytype = CURLPROXY_SOCKS5;
2280       else if (0 == strcmp(proxy_type, "SOCKS4A"))
2281         plugin->proxytype = CURLPROXY_SOCKS4A;
2282       else if (0 == strcmp(proxy_type, "SOCKS5_HOSTNAME "))
2283         plugin->proxytype = CURLPROXY_SOCKS5_HOSTNAME ;
2284       else
2285       {
2286         LOG (GNUNET_ERROR_TYPE_ERROR,
2287              _("Invalid proxy type: `%s', disabling proxy! Check configuration!\n"),
2288              proxy_type);
2289
2290         GNUNET_free (proxy_type);
2291         GNUNET_free (plugin->proxy_hostname);
2292         plugin->proxy_hostname = NULL;
2293         GNUNET_free_non_null (plugin->proxy_username);
2294         plugin->proxy_username = NULL;
2295         GNUNET_free_non_null (plugin->proxy_password);
2296         plugin->proxy_password = NULL;
2297
2298         return GNUNET_SYSERR;
2299       }
2300
2301       LOG (GNUNET_ERROR_TYPE_DEBUG,
2302            "Found proxy type: `%s'\n",
2303            proxy_type);
2304     }
2305
2306     /* proxy http tunneling */
2307     plugin->proxy_use_httpproxytunnel
2308       = GNUNET_CONFIGURATION_get_value_yesno (plugin->env->cfg,
2309                                               plugin->name,
2310                                               "PROXY_HTTP_TUNNELING");
2311     if (GNUNET_SYSERR == plugin->proxy_use_httpproxytunnel)
2312       plugin->proxy_use_httpproxytunnel = GNUNET_NO;
2313
2314     GNUNET_free_non_null (proxy_type);
2315   }
2316
2317   /* Should we emulate an XHR client for testing? */
2318   plugin->emulate_xhr
2319     = GNUNET_CONFIGURATION_get_value_yesno (plugin->env->cfg,
2320                                             plugin->name,
2321                                             "EMULATE_XHR");
2322   return GNUNET_OK;
2323 }
2324
2325
2326 /**
2327  * Function to convert an address to a human-readable string.
2328  *
2329  * @param cls closure
2330  * @param addr address to convert
2331  * @param addrlen address length
2332  * @return res string if conversion was successful, NULL otherwise
2333  */
2334 static const char *
2335 http_client_plugin_address_to_string (void *cls,
2336                                       const void *addr,
2337                                       size_t addrlen)
2338 {
2339   return http_common_plugin_address_to_string (PLUGIN_NAME,
2340                                                addr,
2341                                                addrlen);
2342 }
2343
2344
2345 /**
2346  * Function that will be called whenever the transport service wants to
2347  * notify the plugin that a session is still active and in use and
2348  * therefore the session timeout for this session has to be updated
2349  *
2350  * @param cls closure
2351  * @param peer which peer was the session for
2352  * @param session which session is being updated
2353  */
2354 static void
2355 http_client_plugin_update_session_timeout (void *cls,
2356                                            const struct GNUNET_PeerIdentity *peer,
2357                                            struct Session *session)
2358 {
2359   client_reschedule_session_timeout (session);
2360 }
2361
2362
2363 /**
2364  * Function that will be called whenever the transport service wants to
2365  * notify the plugin that the inbound quota changed and that the plugin
2366  * should update it's delay for the next receive value
2367  *
2368  * @param cls closure
2369  * @param peer which peer was the session for
2370  * @param s which session is being updated
2371  * @param delay new delay to use for receiving
2372  */
2373 static void
2374 http_client_plugin_update_inbound_delay (void *cls,
2375                                          const struct GNUNET_PeerIdentity *peer,
2376                                          struct Session *s,
2377                                          struct GNUNET_TIME_Relative delay)
2378 {
2379   s->next_receive = GNUNET_TIME_relative_to_absolute (delay);
2380   LOG (GNUNET_ERROR_TYPE_DEBUG,
2381        "New inbound delay %s\n",
2382        GNUNET_STRINGS_relative_time_to_string (delay,
2383                                                GNUNET_NO));
2384   if (s->recv_wakeup_task != NULL)
2385   {
2386     GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
2387     s->recv_wakeup_task = GNUNET_SCHEDULER_add_delayed (delay,
2388         &client_wake_up, s);
2389   }
2390 }
2391
2392
2393 /**
2394  * Return information about the given session to the
2395  * monitor callback.
2396  *
2397  * @param cls the `struct Plugin` with the monitor callback (`sic`)
2398  * @param peer peer we send information about
2399  * @param value our `struct Session` to send information about
2400  * @return #GNUNET_OK (continue to iterate)
2401  */
2402 static int
2403 send_session_info_iter (void *cls,
2404                         const struct GNUNET_PeerIdentity *peer,
2405                         void *value)
2406 {
2407   struct HTTP_Client_Plugin *plugin = cls;
2408   struct Session *session = value;
2409
2410   notify_session_monitor (plugin,
2411                           session,
2412                           GNUNET_TRANSPORT_SS_INIT);
2413   notify_session_monitor (plugin,
2414                           session,
2415                           GNUNET_TRANSPORT_SS_UP); /* FIXME: or handshake? */
2416   return GNUNET_OK;
2417 }
2418
2419
2420 /**
2421  * Begin monitoring sessions of a plugin.  There can only
2422  * be one active monitor per plugin (i.e. if there are
2423  * multiple monitors, the transport service needs to
2424  * multiplex the generated events over all of them).
2425  *
2426  * @param cls closure of the plugin
2427  * @param sic callback to invoke, NULL to disable monitor;
2428  *            plugin will being by iterating over all active
2429  *            sessions immediately and then enter monitor mode
2430  * @param sic_cls closure for @a sic
2431  */
2432 static void
2433 http_client_plugin_setup_monitor (void *cls,
2434                                   GNUNET_TRANSPORT_SessionInfoCallback sic,
2435                                   void *sic_cls)
2436 {
2437   struct HTTP_Client_Plugin *plugin = cls;
2438
2439   plugin->sic = sic;
2440   plugin->sic_cls = sic_cls;
2441   if (NULL != sic)
2442   {
2443     GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
2444                                            &send_session_info_iter,
2445                                            plugin);
2446     /* signal end of first iteration */
2447     sic (sic_cls, NULL, NULL);
2448   }
2449 }
2450
2451
2452 /**
2453  * Entry point for the plugin.
2454  */
2455 void *
2456 LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls)
2457 {
2458   struct GNUNET_TRANSPORT_PluginEnvironment *env = cls;
2459   struct GNUNET_TRANSPORT_PluginFunctions *api;
2460   struct HTTP_Client_Plugin *plugin;
2461
2462   if (NULL == env->receive)
2463   {
2464     /* run in 'stub' mode (i.e. as part of gnunet-peerinfo), don't fully
2465        initialze the plugin or the API */
2466     api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions);
2467     api->cls = NULL;
2468     api->address_to_string = &http_client_plugin_address_to_string;
2469     api->string_to_address = &http_common_plugin_string_to_address;
2470     api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
2471     return api;
2472   }
2473
2474   plugin = GNUNET_new (struct HTTP_Client_Plugin);
2475   plugin->env = env;
2476   plugin->sessions = GNUNET_CONTAINER_multipeermap_create (128,
2477                                                            GNUNET_YES);
2478   api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions);
2479   api->cls = plugin;
2480   api->send = &http_client_plugin_send;
2481   api->disconnect_session = &http_client_plugin_session_disconnect;
2482   api->query_keepalive_factor = &http_client_query_keepalive_factor;
2483   api->disconnect_peer = &http_client_plugin_peer_disconnect;
2484   api->check_address = &http_client_plugin_address_suggested;
2485   api->get_session = &http_client_plugin_get_session;
2486   api->address_to_string = &http_client_plugin_address_to_string;
2487   api->string_to_address = &http_common_plugin_string_to_address;
2488   api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
2489   api->get_network = &http_client_plugin_get_network;
2490   api->get_network_for_address = &http_client_plugin_get_network_for_address;
2491   api->update_session_timeout = &http_client_plugin_update_session_timeout;
2492   api->update_inbound_delay = &http_client_plugin_update_inbound_delay;
2493   api->setup_monitor = &http_client_plugin_setup_monitor;
2494 #if BUILD_HTTPS
2495   plugin->name = "transport-https_client";
2496   plugin->protocol = "https";
2497 #else
2498   plugin->name = "transport-http_client";
2499   plugin->protocol = "http";
2500 #endif
2501   plugin->last_tag = 1;
2502
2503   if (GNUNET_SYSERR == client_configure_plugin (plugin))
2504   {
2505     LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
2506     return NULL;
2507   }
2508
2509   /* Start client */
2510   if (GNUNET_SYSERR == client_start (plugin))
2511   {
2512     LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
2513     return NULL;
2514   }
2515   return api;
2516 }
2517
2518 /* end of plugin_transport_http_client.c */