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