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