2 This file is part of GNUnet
3 (C) 2002-2014 Christian Grothoff (and other contributing authors)
5 GNUnet is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.
22 * @file transport/plugin_transport_http_client.c
23 * @brief HTTP/S client transport plugin
24 * @author Matthias Wachs
25 * @author Christian Grothoff
29 #define PLUGIN_NAME "https_client"
30 #define HTTP_STAT_STR_CONNECTIONS "# HTTPS client connections"
31 #define LIBGNUNET_PLUGIN_TRANSPORT_INIT libgnunet_plugin_transport_https_client_init
32 #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_https_client_done
34 #define PLUGIN_NAME "http_client"
35 #define HTTP_STAT_STR_CONNECTIONS "# HTTP client connections"
36 #define LIBGNUNET_PLUGIN_TRANSPORT_INIT libgnunet_plugin_transport_http_client_init
37 #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_http_client_done
40 #define VERBOSE_CURL GNUNET_NO
42 #define PUT_DISCONNECT_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1)
44 #define ENABLE_PUT GNUNET_YES
45 #define ENABLE_GET GNUNET_YES
48 #include "gnunet_util_lib.h"
49 #include "gnunet_protocols.h"
50 #include "gnunet_transport_plugin.h"
51 #include "plugin_transport_http_common.h"
52 #include <curl/curl.h>
55 #define LOG(kind,...) GNUNET_log_from(kind, PLUGIN_NAME, __VA_ARGS__)
58 * Encapsulation of all of the state of the plugin.
60 struct HTTP_Client_Plugin;
63 * State of a HTTP PUT request
65 enum HTTP_PUT_REQUEST_STATE
68 * Just created, not yet connected
78 * Paused, nothing to send
83 * Temporary disconnect in progress due to inactivity
88 * Send request while temporary disconnect, reconnect
90 H_TMP_RECONNECT_REQUIRED,
93 * Temporarily disconnected
104 * Message to send using http
109 * next pointer for double linked list
111 struct HTTP_Message *next;
114 * previous pointer for double linked list
116 struct HTTP_Message *prev;
119 * buffer containing data to send
124 * Continuation function to call once the transmission buffer
125 * has again space available. NULL if there is no
126 * continuation to call.
128 GNUNET_TRANSPORT_TransmitContinuation transmit_cont;
131 * Closure for @e transmit_cont.
133 void *transmit_cont_cls;
136 * amount of data already sent
149 * Session handle for HTTP(S) connections.
161 * Current state of this request
163 enum HTTP_PUT_REQUEST_STATE state;
166 * The curl easy handle
171 * The related session
178 * Session handle for connections.
183 * The URL to connect to
190 struct GNUNET_HELLO_Address *address;
193 * Pointer to the global plugin struct.
195 struct HTTP_Client_Plugin *plugin;
198 * Handle for the HTTP PUT request.
200 struct RequestHandle put;
203 * Handle for the HTTP GET request.
205 struct RequestHandle get;
208 * next pointer for double linked list
210 struct HTTP_Message *msg_head;
213 * previous pointer for double linked list
215 struct HTTP_Message *msg_tail;
218 * Message stream tokenizer for incoming data
220 struct GNUNET_SERVER_MessageStreamTokenizer *msg_tk;
223 * Session timeout task
225 GNUNET_SCHEDULER_TaskIdentifier put_disconnect_task;
228 * Session timeout task
230 GNUNET_SCHEDULER_TaskIdentifier timeout_task;
233 * Task to wake up client receive handle when receiving is allowed again
235 GNUNET_SCHEDULER_TaskIdentifier recv_wakeup_task;
238 * Absolute time when to receive data again.
239 * Used for receive throttling.
241 struct GNUNET_TIME_Absolute next_receive;
244 * When does this session time out.
246 struct GNUNET_TIME_Absolute timeout;
249 * Number of bytes waiting for transmission to this peer.
251 unsigned long long bytes_in_queue;
254 * Outbound overhead due to HTTP connection
255 * Add to next message of this session when calling callback
260 * Number of messages waiting for transmission to this peer.
262 unsigned int msgs_in_queue;
265 * ATS network type in NBO
267 uint32_t ats_address_network_type;
272 * Encapsulation of all of the state of the plugin.
274 struct HTTP_Client_Plugin
279 struct GNUNET_TRANSPORT_PluginEnvironment *env;
284 struct GNUNET_CONTAINER_MultiPeerMap *sessions;
287 * Function to call about session status changes.
289 GNUNET_TRANSPORT_SessionInfoCallback sic;
292 * Closure for @e sic.
307 * Proxy configuration: hostname or ip of the proxy server
309 char *proxy_hostname;
312 * Username for the proxy server
314 char *proxy_username;
317 * Password for the proxy server
319 char *proxy_password;
324 CURLM *curl_multi_handle;
329 GNUNET_SCHEDULER_TaskIdentifier client_perform_task;
332 * Type of proxy server:
334 * Valid values as supported by curl:
335 * CURLPROXY_HTTP, CURLPROXY_HTTP_1_0 CURLPROXY_SOCKS4, CURLPROXY_SOCKS5,
336 * CURLPROXY_SOCKS4A, CURLPROXY_SOCKS5_HOSTNAME
338 curl_proxytype proxytype;
341 * Use proxy tunneling:
342 * Tunnel all operations through a given HTTP instead of have the proxy
343 * evaluate the HTTP request
345 * Default: #GNUNET_NO, #GNUNET_YES experimental
347 int proxy_use_httpproxytunnel;
350 * My options to be included in the address
355 * Maximum number of sockets the plugin can use
356 * Each http connections are two requests
358 unsigned int max_requests;
361 * Current number of sockets the plugin can use
362 * Each http connections are two requests
364 unsigned int cur_requests;
367 * Last used unique HTTP connection tag
382 * Should we emulate an XHR client for testing?
388 * Disconnect a session
390 * @param cls the `struct HTTP_Client_Plugin *`
392 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
395 http_client_plugin_session_disconnect (void *cls, struct Session *s);
398 * If a session monitor is attached, notify it about the new
401 * @param plugin our plugin
402 * @param session session that changed state
403 * @param state new state of the session
406 notify_session_monitor (struct HTTP_Client_Plugin *plugin,
407 struct Session *session,
408 enum GNUNET_TRANSPORT_SessionState state)
410 struct GNUNET_TRANSPORT_SessionInfo info;
412 if (NULL == plugin->sic)
414 memset (&info, 0, sizeof (info));
416 info.is_inbound = GNUNET_NO;
417 info.num_msg_pending = session->msgs_in_queue;
418 info.num_bytes_pending = session->bytes_in_queue;
419 info.receive_delay = session->next_receive;
420 info.session_timeout = session->timeout;
421 info.address = session->address;
422 plugin->sic (plugin->sic_cls,
429 * Delete session @a s.
431 * @param s the session to delete
434 client_delete_session (struct Session *s)
436 struct HTTP_Client_Plugin *plugin = s->plugin;
437 struct HTTP_Message *pos;
438 struct HTTP_Message *next;
441 if (GNUNET_SCHEDULER_NO_TASK != s->timeout_task)
443 GNUNET_SCHEDULER_cancel (s->timeout_task);
444 s->timeout_task = GNUNET_SCHEDULER_NO_TASK;
445 s->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
447 if (GNUNET_SCHEDULER_NO_TASK != s->put_disconnect_task)
449 GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
450 s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
452 if (GNUNET_SCHEDULER_NO_TASK != s->recv_wakeup_task)
454 GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
455 s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
457 GNUNET_assert (GNUNET_OK ==
458 GNUNET_CONTAINER_multipeermap_remove (plugin->sessions,
461 if (NULL != s->put.easyhandle)
463 LOG (GNUNET_ERROR_TYPE_DEBUG,
464 "Session %p/request %p: disconnecting PUT request to peer `%s'\n",
467 GNUNET_i2s (&s->address->peer));
469 /* remove curl handle from multi handle */
470 mret = curl_multi_remove_handle (plugin->curl_multi_handle,
472 GNUNET_break (CURLM_OK == mret);
473 curl_easy_cleanup (s->put.easyhandle);
474 s->put.easyhandle = NULL;
476 if (NULL != s->get.easyhandle)
478 LOG (GNUNET_ERROR_TYPE_DEBUG,
479 "Session %p/request %p: disconnecting GET request to peer `%s'\n",
480 s, s->get.easyhandle,
481 GNUNET_i2s (&s->address->peer));
482 /* remove curl handle from multi handle */
483 mret = curl_multi_remove_handle (plugin->curl_multi_handle,
485 GNUNET_break (CURLM_OK == mret);
486 curl_easy_cleanup (s->get.easyhandle);
487 GNUNET_assert (plugin->cur_requests > 0);
488 plugin->cur_requests--;
489 s->get.easyhandle = NULL;
491 GNUNET_STATISTICS_set (plugin->env->stats,
492 HTTP_STAT_STR_CONNECTIONS,
493 plugin->cur_requests,
496 while (NULL != (pos = next))
499 GNUNET_CONTAINER_DLL_remove (s->msg_head,
502 GNUNET_assert (0 < s->msgs_in_queue);
504 GNUNET_assert (pos->size <= s->bytes_in_queue);
505 s->bytes_in_queue -= pos->size;
506 if (NULL != pos->transmit_cont)
507 pos->transmit_cont (pos->transmit_cont_cls,
511 pos->pos + s->overhead);
515 GNUNET_assert (0 == s->msgs_in_queue);
516 GNUNET_assert (0 == s->bytes_in_queue);
517 notify_session_monitor (plugin,
519 GNUNET_TRANSPORT_SS_DOWN);
520 if (NULL != s->msg_tk)
522 GNUNET_SERVER_mst_destroy (s->msg_tk);
525 GNUNET_HELLO_address_free (s->address);
526 GNUNET_free (s->url);
532 * Increment session timeout due to activity for session @a s.
534 * @param s the session
537 client_reschedule_session_timeout (struct Session *s)
539 GNUNET_assert (GNUNET_SCHEDULER_NO_TASK != s->timeout_task);
540 s->timeout = GNUNET_TIME_relative_to_absolute (GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT);
545 * Task performing curl operations
547 * @param cls plugin as closure
548 * @param tc gnunet scheduler task context
551 client_run (void *cls,
552 const struct GNUNET_SCHEDULER_TaskContext *tc);
556 * Function setting up file descriptors and scheduling task to run
558 * @param plugin the plugin as closure
559 * @param now schedule task in 1ms, regardless of what curl may say
560 * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for ok
563 client_schedule (struct HTTP_Client_Plugin *plugin,
570 struct GNUNET_NETWORK_FDSet *grs;
571 struct GNUNET_NETWORK_FDSet *gws;
574 struct GNUNET_TIME_Relative timeout;
576 /* Cancel previous scheduled task */
577 if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
579 GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
580 plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
586 mret = curl_multi_fdset (plugin->curl_multi_handle, &rs, &ws, &es, &max);
587 if (mret != CURLM_OK)
589 LOG (GNUNET_ERROR_TYPE_ERROR, _("%s failed at %s:%d: `%s'\n"),
590 "curl_multi_fdset", __FILE__, __LINE__,
591 curl_multi_strerror (mret));
592 return GNUNET_SYSERR;
594 mret = curl_multi_timeout (plugin->curl_multi_handle, &to);
596 timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1);
598 timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, to);
599 if (now == GNUNET_YES)
600 timeout = GNUNET_TIME_UNIT_MILLISECONDS;
602 if (mret != CURLM_OK)
604 LOG (GNUNET_ERROR_TYPE_ERROR,
605 _("%s failed at %s:%d: `%s'\n"),
606 "curl_multi_timeout", __FILE__, __LINE__,
607 curl_multi_strerror (mret));
608 return GNUNET_SYSERR;
611 grs = GNUNET_NETWORK_fdset_create ();
612 gws = GNUNET_NETWORK_fdset_create ();
613 GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
614 GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
616 /* Schedule task to run when select is ready to read or write */
617 plugin->client_perform_task =
618 GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
620 &client_run, plugin);
621 GNUNET_NETWORK_fdset_destroy (gws);
622 GNUNET_NETWORK_fdset_destroy (grs);
630 * @param curl the curl easy handle
631 * @param type message type
632 * @param data data to log, NOT a 0-terminated string
633 * @param size data length
634 * @param cls the closure
638 client_log (CURL *curl,
644 struct RequestHandle *ch = cls;
645 const char *ttype = "UNSPECIFIED";
648 if (! ((type == CURLINFO_TEXT) || (type == CURLINFO_HEADER_IN) || (type == CURLINFO_HEADER_OUT)))
655 case CURLINFO_HEADER_IN:
658 case CURLINFO_HEADER_OUT:
659 ttype = "HEADER_OUT";
661 GNUNET_assert (NULL != ch);
662 GNUNET_assert (NULL != ch->easyhandle);
663 GNUNET_assert (NULL != ch->s);
664 ch->s->overhead += size;
667 ttype = "UNSPECIFIED";
670 memcpy (text, data, size);
671 if (text[size - 1] == '\n')
678 text[size + 1] = '\0';
680 LOG (GNUNET_ERROR_TYPE_DEBUG,
690 * Connect GET request
692 * @param s the session to connect
693 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
696 client_connect_get (struct Session *s);
700 * Connect a HTTP put request
702 * @param s the session to connect
703 * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for success
706 client_connect_put (struct Session *s);
710 * Function that can be used by the transport service to transmit
711 * a message using the plugin. Note that in the case of a
712 * peer disconnecting, the continuation MUST be called
713 * prior to the disconnect notification itself. This function
714 * will be called with this peer's HELLO message to initiate
715 * a fresh connection to another peer.
718 * @param s which session must be used
719 * @param msgbuf the message to transmit
720 * @param msgbuf_size number of bytes in @a msgbuf
721 * @param priority how important is the message (most plugins will
722 * ignore message priority and just FIFO)
723 * @param to how long to wait at most for the transmission (does not
724 * require plugins to discard the message after the timeout,
725 * just advisory for the desired delay; most plugins will ignore
727 * @param cont continuation to call once the message has
728 * been transmitted (or if the transport is ready
729 * for the next transmission call; or if the
730 * peer disconnected...); can be NULL
731 * @param cont_cls closure for cont
732 * @return number of bytes used (on the physical network, with overheads);
733 * -1 on hard errors (i.e. address invalid); 0 is a legal value
734 * and does NOT mean that the message was not transmitted (DV)
737 http_client_plugin_send (void *cls,
741 unsigned int priority,
742 struct GNUNET_TIME_Relative to,
743 GNUNET_TRANSPORT_TransmitContinuation cont,
746 struct HTTP_Client_Plugin *plugin = cls;
747 struct HTTP_Message *msg;
750 LOG (GNUNET_ERROR_TYPE_DEBUG,
751 "Session %p/request %p: Sending message with %u to peer `%s' \n",
752 s, s->put.easyhandle,
753 msgbuf_size, GNUNET_i2s (&s->address->peer));
755 /* create new message and schedule */
756 msg = GNUNET_malloc (sizeof (struct HTTP_Message) + msgbuf_size);
758 msg->size = msgbuf_size;
760 msg->buf = (char *) &msg[1];
761 msg->transmit_cont = cont;
762 msg->transmit_cont_cls = cont_cls;
763 memcpy (msg->buf, msgbuf, msgbuf_size);
764 GNUNET_CONTAINER_DLL_insert_tail (s->msg_head,
768 s->bytes_in_queue += msg->size;
770 GNUNET_asprintf (&stat_txt,
771 "# bytes currently in %s_client buffers",
773 GNUNET_STATISTICS_update (plugin->env->stats,
774 stat_txt, msgbuf_size, GNUNET_NO);
775 GNUNET_free (stat_txt);
776 notify_session_monitor (plugin,
778 GNUNET_TRANSPORT_SS_UP);
779 if (H_TMP_DISCONNECTING == s->put.state)
781 /* PUT request is currently getting disconnected */
782 s->put.state = H_TMP_RECONNECT_REQUIRED;
783 LOG (GNUNET_ERROR_TYPE_DEBUG,
784 "Session %p/request %p: currently disconnecting, reconnecting immediately\n",
789 if (H_PAUSED == s->put.state)
791 /* PUT request was paused, unpause */
792 GNUNET_assert (s->put_disconnect_task != GNUNET_SCHEDULER_NO_TASK);
793 GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
794 s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
795 LOG (GNUNET_ERROR_TYPE_DEBUG,
796 "Session %p/request %p: unpausing request\n",
797 s, s->put.easyhandle);
798 s->put.state = H_CONNECTED;
799 if (NULL != s->put.easyhandle)
800 curl_easy_pause (s->put.easyhandle, CURLPAUSE_CONT);
802 else if (H_TMP_DISCONNECTED == s->put.state)
804 /* PUT request was disconnected, reconnect */
805 LOG (GNUNET_ERROR_TYPE_DEBUG, "Session %p: Reconnecting PUT request\n", s);
806 GNUNET_break (NULL == s->put.easyhandle);
807 if (GNUNET_SYSERR == client_connect_put (s))
809 /* Could not reconnect */
810 http_client_plugin_session_disconnect (plugin, s);
811 return GNUNET_SYSERR;
814 client_schedule (s->plugin, GNUNET_YES);
820 * Disconnect a session
822 * @param cls the `struct HTTP_Client_Plugin *`
824 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
827 http_client_plugin_session_disconnect (void *cls,
830 struct HTTP_Client_Plugin *plugin = cls;
832 LOG (GNUNET_ERROR_TYPE_DEBUG,
833 "Session %p: notifying transport about ending session\n",s);
834 plugin->env->session_end (plugin->env->cls, s->address, s);
835 client_delete_session (s);
837 /* Re-schedule since handles have changed */
838 if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
840 GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
841 plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
843 client_schedule (plugin, GNUNET_YES);
850 * Function that is called to get the keepalive factor.
851 * #GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT is divided by this number to
852 * calculate the interval between keepalive packets.
854 * @param cls closure with the `struct Plugin`
855 * @return keepalive factor
858 http_client_query_keepalive_factor (void *cls)
865 * Callback to destroys all sessions on exit.
867 * @param cls the `struct HTTP_Client_Plugin *`
868 * @param peer identity of the peer
869 * @param value the `struct Session *`
870 * @return #GNUNET_OK (continue iterating)
873 destroy_session_cb (void *cls,
874 const struct GNUNET_PeerIdentity *peer,
877 struct HTTP_Client_Plugin *plugin = cls;
878 struct Session *session = value;
880 http_client_plugin_session_disconnect (plugin, session);
886 * Function that can be used to force the plugin to disconnect
887 * from the given peer and cancel all previous transmissions
888 * (and their continuationc).
891 * @param target peer from which to disconnect
894 http_client_plugin_peer_disconnect (void *cls,
895 const struct GNUNET_PeerIdentity *target)
897 struct HTTP_Client_Plugin *plugin = cls;
899 LOG (GNUNET_ERROR_TYPE_DEBUG,
900 "Transport tells me to disconnect `%s'\n",
901 GNUNET_i2s (target));
902 GNUNET_CONTAINER_multipeermap_get_multiple (plugin->sessions, target,
903 &destroy_session_cb, plugin);
908 * Closure for #session_lookup_client_by_address().
910 struct SessionClientCtx
913 * Address we are looking for.
915 const struct GNUNET_HELLO_Address *address;
918 * Session that was found.
925 * Locate the seession object for a given address.
927 * @param cls the `struct SessionClientCtx *`
928 * @param key peer identity
929 * @param value the `struct Session` to check
930 * @return #GNUNET_NO if found, #GNUNET_OK if not
933 session_lookup_client_by_address (void *cls,
934 const struct GNUNET_PeerIdentity *key,
937 struct SessionClientCtx *sc_ctx = cls;
938 struct Session *s = value;
940 if (0 == GNUNET_HELLO_address_cmp (sc_ctx->address,
951 * Check if a sessions exists for an specific address
953 * @param plugin the plugin
954 * @param address the address
955 * @return the session or NULL
957 static struct Session *
958 client_lookup_session (struct HTTP_Client_Plugin *plugin,
959 const struct GNUNET_HELLO_Address *address)
961 struct SessionClientCtx sc_ctx;
963 sc_ctx.address = address;
965 GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
966 &session_lookup_client_by_address,
973 * When we have nothing to transmit, we pause the HTTP PUT
974 * after a while (so that gnurl stops asking). This task
975 * is the delayed task that actually disconnects the PUT.
977 * @param cls the `struct Session *` with the put
978 * @param tc scheduler context
981 client_put_disconnect (void *cls,
982 const struct GNUNET_SCHEDULER_TaskContext *tc)
984 struct Session *s = cls;
986 s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
987 LOG (GNUNET_ERROR_TYPE_DEBUG,
988 "Session %p/request %p: will be disconnected due to no activity\n",
989 s, s->put.easyhandle);
990 s->put.state = H_TMP_DISCONNECTING;
991 if (NULL != s->put.easyhandle)
992 curl_easy_pause (s->put.easyhandle, CURLPAUSE_CONT);
993 client_schedule (s->plugin, GNUNET_YES);
998 * Callback method used with libcurl
999 * Method is called when libcurl needs to read data during sending
1001 * @param stream pointer where to write data
1002 * @param size size of an individual element
1003 * @param nmemb count of elements that can be written to the buffer
1004 * @param cls our `struct Session`
1005 * @return bytes written to stream, returning 0 will terminate request!
1008 client_send_cb (void *stream,
1013 struct Session *s = cls;
1014 struct HTTP_Client_Plugin *plugin = s->plugin;
1015 struct HTTP_Message *msg = s->msg_head;
1019 if (H_TMP_DISCONNECTING == s->put.state)
1021 LOG (GNUNET_ERROR_TYPE_DEBUG,
1022 "Session %p/request %p: disconnect due to inactivity\n",
1023 s, s->put.easyhandle);
1029 if (GNUNET_YES == plugin->emulate_xhr)
1031 LOG (GNUNET_ERROR_TYPE_DEBUG,
1032 "Session %p/request %p: PUT request finished\n",
1033 s, s->put.easyhandle);
1034 s->put.state = H_TMP_DISCONNECTING;
1038 /* We have nothing to send, so pause PUT request */
1039 LOG (GNUNET_ERROR_TYPE_DEBUG,
1040 "Session %p/request %p: nothing to send, suspending\n",
1041 s, s->put.easyhandle);
1042 s->put_disconnect_task = GNUNET_SCHEDULER_add_delayed (PUT_DISCONNECT_TIMEOUT,
1043 &client_put_disconnect, s);
1044 s->put.state = H_PAUSED;
1045 return CURL_READFUNC_PAUSE;
1048 GNUNET_assert (msg->pos < msg->size);
1049 /* calculate how much fits in buffer */
1050 len = GNUNET_MIN (msg->size - msg->pos,
1052 memcpy (stream, &msg->buf[msg->pos], len);
1054 if (msg->pos == msg->size)
1056 LOG (GNUNET_ERROR_TYPE_DEBUG,
1057 "Session %p/request %p: sent message with %u bytes sent, removing message from queue\n",
1058 s, s->put.easyhandle, msg->size, msg->pos);
1059 /* Calling transmit continuation */
1060 GNUNET_CONTAINER_DLL_remove (s->msg_head,
1063 GNUNET_assert (0 < s->msgs_in_queue);
1065 GNUNET_assert (msg->size <= s->bytes_in_queue);
1066 s->bytes_in_queue -= msg->size;
1067 if (NULL != msg->transmit_cont)
1068 msg->transmit_cont (msg->transmit_cont_cls,
1072 msg->size + s->overhead);
1076 notify_session_monitor (plugin,
1078 GNUNET_TRANSPORT_SS_UP);
1079 GNUNET_asprintf (&stat_txt,
1080 "# bytes currently in %s_client buffers",
1082 GNUNET_STATISTICS_update (plugin->env->stats,
1086 GNUNET_free (stat_txt);
1087 GNUNET_asprintf (&stat_txt,
1088 "# bytes transmitted via %s_client",
1090 GNUNET_STATISTICS_update (plugin->env->stats,
1094 GNUNET_free (stat_txt);
1100 * Wake up a curl handle which was suspended
1102 * @param cls the session
1103 * @param tc task context
1106 client_wake_up (void *cls,
1107 const struct GNUNET_SCHEDULER_TaskContext *tc)
1109 struct Session *s = cls;
1111 s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
1112 if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
1114 LOG (GNUNET_ERROR_TYPE_DEBUG,
1115 "Session %p/request %p: Waking up GET handle\n",
1116 s, s->get.easyhandle);
1117 if (H_PAUSED == s->put.state)
1119 /* PUT request was paused, unpause */
1120 GNUNET_assert (s->put_disconnect_task != GNUNET_SCHEDULER_NO_TASK);
1121 GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
1122 s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
1123 s->put.state = H_CONNECTED;
1124 if (NULL != s->put.easyhandle)
1125 curl_easy_pause (s->put.easyhandle, CURLPAUSE_CONT);
1127 if (NULL != s->get.easyhandle)
1128 curl_easy_pause (s->get.easyhandle, CURLPAUSE_CONT);
1133 * Callback for message stream tokenizer
1135 * @param cls the session
1136 * @param client not used
1137 * @param message the message received
1138 * @return always #GNUNET_OK
1141 client_receive_mst_cb (void *cls,
1143 const struct GNUNET_MessageHeader *message)
1145 struct Session *s = cls;
1146 struct HTTP_Client_Plugin *plugin;
1147 struct GNUNET_TIME_Relative delay;
1148 struct GNUNET_ATS_Information atsi;
1152 atsi.type = htonl (GNUNET_ATS_NETWORK_TYPE);
1153 atsi.value = s->ats_address_network_type;
1154 GNUNET_break (s->ats_address_network_type != ntohl (GNUNET_ATS_NET_UNSPECIFIED));
1156 delay = s->plugin->env->receive (plugin->env->cls,
1160 plugin->env->update_address_metrics (plugin->env->cls,
1164 GNUNET_asprintf (&stat_txt,
1165 "# bytes received via %s_client",
1167 GNUNET_STATISTICS_update (plugin->env->stats,
1169 ntohs (message->size),
1171 GNUNET_free (stat_txt);
1173 s->next_receive = GNUNET_TIME_relative_to_absolute (delay);
1174 if (GNUNET_TIME_absolute_get ().abs_value_us < s->next_receive.abs_value_us)
1176 LOG (GNUNET_ERROR_TYPE_DEBUG,
1177 "Client: peer `%s' address `%s' next read delayed for %s\n",
1178 GNUNET_i2s (&s->address->peer),
1179 http_common_plugin_address_to_string (s->plugin->protocol,
1180 s->address->address,
1181 s->address->address_length),
1182 GNUNET_STRINGS_relative_time_to_string (delay,
1185 client_reschedule_session_timeout (s);
1191 * Callback method used with libcurl when data for a PUT request are
1192 * received. We do not expect data here, so we just discard it.
1194 * @param stream pointer where to write data
1195 * @param size size of an individual element
1196 * @param nmemb count of elements that can be written to the buffer
1197 * @param cls destination pointer, passed to the libcurl handle
1198 * @return bytes read from stream
1201 client_receive_put (void *stream,
1206 return size * nmemb;
1211 * Callback method used with libcurl when data for a GET request are
1212 * received. Forward to MST
1214 * @param stream pointer where to write data
1215 * @param size size of an individual element
1216 * @param nmemb count of elements that can be written to the buffer
1217 * @param cls destination pointer, passed to the libcurl handle
1218 * @return bytes read from stream
1221 client_receive (void *stream,
1226 struct Session *s = cls;
1227 struct GNUNET_TIME_Absolute now;
1228 size_t len = size * nmemb;
1230 LOG (GNUNET_ERROR_TYPE_DEBUG,
1231 "Session %p / request %p: Received %u bytes from peer `%s'\n",
1232 s, s->get.easyhandle,
1233 len, GNUNET_i2s (&s->address->peer));
1234 now = GNUNET_TIME_absolute_get ();
1235 if (now.abs_value_us < s->next_receive.abs_value_us)
1237 struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
1238 struct GNUNET_TIME_Relative delta
1239 = GNUNET_TIME_absolute_get_difference (now, s->next_receive);
1241 LOG (GNUNET_ERROR_TYPE_DEBUG,
1242 "Session %p / request %p: No inbound bandwidth available! Next read was delayed for %s\n",
1245 GNUNET_STRINGS_relative_time_to_string (delta,
1247 if (s->recv_wakeup_task != GNUNET_SCHEDULER_NO_TASK)
1249 GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
1250 s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
1253 = GNUNET_SCHEDULER_add_delayed (delta,
1256 return CURL_WRITEFUNC_PAUSE;
1258 if (NULL == s->msg_tk)
1259 s->msg_tk = GNUNET_SERVER_mst_create (&client_receive_mst_cb,
1261 GNUNET_SERVER_mst_receive (s->msg_tk,
1272 * Task performing curl operations
1274 * @param cls plugin as closure
1275 * @param tc scheduler task context
1278 client_run (void *cls,
1279 const struct GNUNET_SCHEDULER_TaskContext *tc)
1281 struct HTTP_Client_Plugin *plugin = cls;
1283 long http_statuscode;
1286 int put_request; /* GNUNET_YES if easy handle is put, GNUNET_NO for get */
1289 plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1290 if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
1293 /* While data are available or timeouts occured */
1297 /* Perform operations for all handles */
1298 mret = curl_multi_perform (plugin->curl_multi_handle, &running);
1300 /* Get additional information for all handles */
1301 while (NULL != (msg = curl_multi_info_read (plugin->curl_multi_handle, &msgs_left)))
1303 CURL *easy_h = msg->easy_handle;
1304 struct Session *s = NULL;
1305 char *d = NULL; /* curl requires 'd' to be a 'char *' */
1307 GNUNET_assert (NULL != easy_h);
1309 /* Obtain session from easy handle */
1310 GNUNET_assert (CURLE_OK == curl_easy_getinfo (easy_h, CURLINFO_PRIVATE, &d));
1311 s = (struct Session *) d;
1312 GNUNET_assert (NULL != s);
1314 if (msg->msg != CURLMSG_DONE)
1315 continue; /* This should not happen */
1317 /* Get HTTP response code */
1318 GNUNET_break (CURLE_OK == curl_easy_getinfo (easy_h,
1319 CURLINFO_RESPONSE_CODE, &http_statuscode));
1322 if (easy_h == s->put.easyhandle)
1323 put_request = GNUNET_YES;
1325 put_request = GNUNET_NO;
1327 /* Log status of terminated request */
1328 if ((0 != msg->data.result) || (http_statuscode != 200))
1329 LOG (GNUNET_ERROR_TYPE_DEBUG,
1330 "Session %p/request %p: %s request to `%s' ended with status %i reason %i: `%s'\n",
1331 s, msg->easy_handle,
1332 (GNUNET_YES == put_request) ? "PUT" : "GET",
1333 GNUNET_i2s (&s->address->peer),
1336 curl_easy_strerror (msg->data.result));
1338 LOG (GNUNET_ERROR_TYPE_DEBUG,
1339 "Session %p/request %p: %s request to `%s' ended normal\n",
1340 s, msg->easy_handle,
1341 (GNUNET_YES == put_request) ? "PUT" : "GET",
1342 GNUNET_i2s (&s->address->peer));
1344 /* Remove easy handle from multi handle */
1345 curl_multi_remove_handle (plugin->curl_multi_handle, easy_h);
1347 /* Clean up easy handle */
1348 curl_easy_cleanup (easy_h);
1350 /* Remove information */
1351 GNUNET_assert (plugin->cur_requests > 0);
1352 plugin->cur_requests--;
1353 LOG (GNUNET_ERROR_TYPE_INFO,
1354 "%s request to %s done, number of requests decreased to %u\n",
1355 (GNUNET_YES == put_request) ? "PUT" : "GET",
1357 plugin->cur_requests);
1359 if (GNUNET_YES == put_request)
1361 /* Clean up a PUT request */
1362 s->put.easyhandle = NULL;
1365 switch (s->put.state) {
1366 case H_NOT_CONNECTED:
1367 case H_DISCONNECTED:
1368 case H_TMP_DISCONNECTED:
1369 /* This must not happen */
1372 case H_TMP_RECONNECT_REQUIRED:
1373 /* Transport called send while disconnect in progess, reconnect */
1374 if (GNUNET_SYSERR == client_connect_put (s))
1376 /* Reconnect failed, disconnect session */
1377 http_client_plugin_session_disconnect (plugin, s);
1380 case H_TMP_DISCONNECTING:
1381 /* PUT gets temporarily disconnected */
1382 s->put.state = H_TMP_DISCONNECTED;
1386 /* PUT gets permanently disconnected */
1387 s->put.state = H_DISCONNECTED;
1388 http_client_plugin_session_disconnect (plugin, s);
1395 else if (GNUNET_NO == put_request)
1397 /* Clean up a GET request */
1398 s->get.easyhandle = NULL;
1401 /* If we are emulating an XHR client we need to make another GET
1404 if (GNUNET_YES == plugin->emulate_xhr)
1406 if (GNUNET_SYSERR == client_connect_get (s))
1407 http_client_plugin_session_disconnect (plugin, s);
1411 /* GET request was terminated, so disconnect session */
1412 http_client_plugin_session_disconnect (plugin, s);
1416 GNUNET_break (0); /* Must not happen */
1419 while (mret == CURLM_CALL_MULTI_PERFORM);
1420 client_schedule (plugin, GNUNET_NO);
1425 * Connect GET request for a session
1427 * @param s the session to connect
1428 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
1431 client_connect_get (struct Session *s)
1435 /* create get request */
1436 s->get.easyhandle = curl_easy_init ();
1439 curl_easy_setopt (s->get.easyhandle, CURLOPT_VERBOSE, 1L);
1440 curl_easy_setopt (s->get.easyhandle, CURLOPT_DEBUGFUNCTION, &client_log);
1441 curl_easy_setopt (s->get.easyhandle, CURLOPT_DEBUGDATA, &s->get);
1444 curl_easy_setopt (s->get.easyhandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
1446 struct HttpAddress *ha;
1448 ha = (struct HttpAddress *) s->address->address;
1450 if (HTTP_OPTIONS_VERIFY_CERTIFICATE ==
1451 (ntohl (ha->options) & HTTP_OPTIONS_VERIFY_CERTIFICATE))
1453 curl_easy_setopt (s->get.easyhandle, CURLOPT_SSL_VERIFYPEER, 1L);
1454 curl_easy_setopt (s->get.easyhandle, CURLOPT_SSL_VERIFYHOST, 2L);
1458 curl_easy_setopt (s->get.easyhandle, CURLOPT_SSL_VERIFYPEER, 0);
1459 curl_easy_setopt (s->get.easyhandle, CURLOPT_SSL_VERIFYHOST, 0);
1462 curl_easy_setopt (s->get.easyhandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
1463 curl_easy_setopt (s->get.easyhandle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
1465 curl_easy_setopt (s->get.easyhandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP);
1466 curl_easy_setopt (s->get.easyhandle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP);
1469 if (NULL != s->plugin->proxy_hostname)
1471 curl_easy_setopt (s->get.easyhandle, CURLOPT_PROXY, s->plugin->proxy_hostname);
1472 curl_easy_setopt (s->get.easyhandle, CURLOPT_PROXYTYPE, s->plugin->proxytype);
1473 if (NULL != s->plugin->proxy_username)
1474 curl_easy_setopt (s->get.easyhandle, CURLOPT_PROXYUSERNAME,
1475 s->plugin->proxy_username);
1476 if (NULL != s->plugin->proxy_password)
1477 curl_easy_setopt (s->get.easyhandle, CURLOPT_PROXYPASSWORD,
1478 s->plugin->proxy_password);
1479 if (GNUNET_YES == s->plugin->proxy_use_httpproxytunnel)
1480 curl_easy_setopt (s->get.easyhandle, CURLOPT_HTTPPROXYTUNNEL,
1481 s->plugin->proxy_use_httpproxytunnel);
1484 if (GNUNET_YES == s->plugin->emulate_xhr)
1488 GNUNET_asprintf(&url, "%s,1", s->url);
1489 curl_easy_setopt (s->get.easyhandle, CURLOPT_URL, url);
1492 curl_easy_setopt (s->get.easyhandle, CURLOPT_URL, s->url);
1493 //curl_easy_setopt (s->get.easyhandle, CURLOPT_HEADERFUNCTION, &curl_get_header_cb);
1494 //curl_easy_setopt (s->get.easyhandle, CURLOPT_WRITEHEADER, ps);
1495 curl_easy_setopt (s->get.easyhandle, CURLOPT_READFUNCTION, client_send_cb);
1496 curl_easy_setopt (s->get.easyhandle, CURLOPT_READDATA, s);
1497 curl_easy_setopt (s->get.easyhandle, CURLOPT_WRITEFUNCTION, client_receive);
1498 curl_easy_setopt (s->get.easyhandle, CURLOPT_WRITEDATA, s);
1499 /* No timeout by default, timeout done with session timeout */
1500 curl_easy_setopt (s->get.easyhandle, CURLOPT_TIMEOUT, 0);
1501 curl_easy_setopt (s->get.easyhandle, CURLOPT_PRIVATE, s);
1502 curl_easy_setopt (s->get.easyhandle, CURLOPT_CONNECTTIMEOUT_MS,
1503 (long) (HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value_us / 1000LL));
1504 curl_easy_setopt (s->get.easyhandle, CURLOPT_BUFFERSIZE,
1505 2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1506 #if CURL_TCP_NODELAY
1507 curl_easy_setopt (ps->recv_endpoint, CURLOPT_TCP_NODELAY, 1);
1509 curl_easy_setopt (s->get.easyhandle, CURLOPT_FOLLOWLOCATION, 0);
1511 mret = curl_multi_add_handle (s->plugin->curl_multi_handle,
1513 if (CURLM_OK != mret)
1515 LOG (GNUNET_ERROR_TYPE_ERROR,
1516 "Session %p : Failed to add GET handle to multihandle: `%s'\n",
1518 curl_multi_strerror (mret));
1519 curl_easy_cleanup (s->get.easyhandle);
1520 s->get.easyhandle = NULL;
1522 s->get.easyhandle = NULL;
1524 return GNUNET_SYSERR;
1526 s->plugin->cur_requests++;
1527 LOG (GNUNET_ERROR_TYPE_INFO,
1528 "GET request `%s' established, number of requests increased to %u\n",
1529 s->url, s->plugin->cur_requests);
1535 * Connect a HTTP put request
1537 * @param s the session to connect
1538 * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for ok
1541 client_connect_put (struct Session *s)
1545 /* create put request */
1546 LOG (GNUNET_ERROR_TYPE_DEBUG,
1547 "Session %p: Init PUT handle\n", s);
1548 s->put.easyhandle = curl_easy_init ();
1551 curl_easy_setopt (s->put.easyhandle, CURLOPT_VERBOSE, 1L);
1552 curl_easy_setopt (s->put.easyhandle, CURLOPT_DEBUGFUNCTION, &client_log);
1553 curl_easy_setopt (s->put.easyhandle, CURLOPT_DEBUGDATA, &s->put);
1556 curl_easy_setopt (s->put.easyhandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
1558 struct HttpAddress *ha;
1559 ha = (struct HttpAddress *) s->address->address;
1561 if (HTTP_OPTIONS_VERIFY_CERTIFICATE ==
1562 (ntohl (ha->options) & HTTP_OPTIONS_VERIFY_CERTIFICATE))
1564 curl_easy_setopt (s->put.easyhandle, CURLOPT_SSL_VERIFYPEER, 1L);
1565 curl_easy_setopt (s->put.easyhandle, CURLOPT_SSL_VERIFYHOST, 2L);
1569 curl_easy_setopt (s->put.easyhandle, CURLOPT_SSL_VERIFYPEER, 0);
1570 curl_easy_setopt (s->put.easyhandle, CURLOPT_SSL_VERIFYHOST, 0);
1573 curl_easy_setopt (s->put.easyhandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
1574 curl_easy_setopt (s->put.easyhandle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
1576 curl_easy_setopt (s->put.easyhandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP);
1577 curl_easy_setopt (s->put.easyhandle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP);
1579 if (s->plugin->proxy_hostname != NULL)
1581 curl_easy_setopt (s->put.easyhandle, CURLOPT_PROXY, s->plugin->proxy_hostname);
1582 curl_easy_setopt (s->put.easyhandle, CURLOPT_PROXYTYPE, s->plugin->proxytype);
1583 if (NULL != s->plugin->proxy_username)
1584 curl_easy_setopt (s->put.easyhandle, CURLOPT_PROXYUSERNAME,
1585 s->plugin->proxy_username);
1586 if (NULL != s->plugin->proxy_password)
1587 curl_easy_setopt (s->put.easyhandle, CURLOPT_PROXYPASSWORD,
1588 s->plugin->proxy_password);
1589 if (GNUNET_YES == s->plugin->proxy_use_httpproxytunnel)
1590 curl_easy_setopt (s->put.easyhandle, CURLOPT_HTTPPROXYTUNNEL,
1591 s->plugin->proxy_use_httpproxytunnel);
1594 curl_easy_setopt (s->put.easyhandle, CURLOPT_URL, s->url);
1595 curl_easy_setopt (s->put.easyhandle, CURLOPT_UPLOAD, 1L);
1596 //curl_easy_setopt (s->put.easyhandle, CURLOPT_HEADERFUNCTION, &client_curl_header);
1597 //curl_easy_setopt (s->put.easyhandle, CURLOPT_WRITEHEADER, ps);
1598 curl_easy_setopt (s->put.easyhandle, CURLOPT_READFUNCTION, client_send_cb);
1599 curl_easy_setopt (s->put.easyhandle, CURLOPT_READDATA, s);
1600 curl_easy_setopt (s->put.easyhandle, CURLOPT_WRITEFUNCTION, client_receive_put);
1601 curl_easy_setopt (s->put.easyhandle, CURLOPT_WRITEDATA, s);
1602 /* No timeout by default, timeout done with session timeout */
1603 curl_easy_setopt (s->put.easyhandle, CURLOPT_TIMEOUT, 0);
1604 curl_easy_setopt (s->put.easyhandle, CURLOPT_PRIVATE, s);
1605 curl_easy_setopt (s->put.easyhandle, CURLOPT_CONNECTTIMEOUT_MS,
1606 (long) (HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value_us / 1000LL));
1607 curl_easy_setopt (s->put.easyhandle, CURLOPT_BUFFERSIZE,
1608 2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1609 #if CURL_TCP_NODELAY
1610 curl_easy_setopt (s->put.easyhandle, CURLOPT_TCP_NODELAY, 1);
1612 mret = curl_multi_add_handle (s->plugin->curl_multi_handle,
1614 if (CURLM_OK != mret)
1616 LOG (GNUNET_ERROR_TYPE_ERROR,
1617 "Session %p : Failed to add PUT handle to multihandle: `%s'\n",
1618 s, curl_multi_strerror (mret));
1619 curl_easy_cleanup (s->put.easyhandle);
1620 s->put.easyhandle = NULL;
1621 s->put.easyhandle = NULL;
1623 s->put.state = H_DISCONNECTED;
1624 return GNUNET_SYSERR;
1626 s->put.state = H_CONNECTED;
1627 s->plugin->cur_requests++;
1629 LOG (GNUNET_ERROR_TYPE_INFO,
1630 "PUT request `%s' established, number of requests increased to %u\n",
1631 s->url, s->plugin->cur_requests);
1638 * Connect both PUT and GET request for a session
1640 * @param s the session to connect
1641 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
1644 client_connect (struct Session *s)
1646 struct HTTP_Client_Plugin *plugin = s->plugin;
1647 int res = GNUNET_OK;
1650 if (NULL == http_common_plugin_address_to_string(plugin->protocol,
1651 s->address->address, s->address->address_length))
1653 LOG(GNUNET_ERROR_TYPE_DEBUG, "Invalid address peer `%s'\n",
1654 GNUNET_i2s(&s->address->peer));
1655 return GNUNET_SYSERR;
1658 GNUNET_asprintf(&s->url, "%s/%s;%u",
1659 http_common_plugin_address_to_url(NULL, s->address->address,
1660 s->address->address_length),
1661 GNUNET_i2s_full(plugin->env->my_identity), plugin->last_tag);
1664 LOG (GNUNET_ERROR_TYPE_DEBUG,
1665 "Initiating outbound session peer `%s' using address `%s'\n",
1666 GNUNET_i2s (&s->address->peer), s->url);
1668 if (GNUNET_SYSERR == client_connect_get (s))
1669 return GNUNET_SYSERR;
1670 /* If we are emulating an XHR client then delay sending a PUT request until
1671 * there is something to send.
1673 if (GNUNET_YES == plugin->emulate_xhr)
1675 s->put.state = H_TMP_DISCONNECTED;
1677 else if (GNUNET_SYSERR == client_connect_put (s))
1678 return GNUNET_SYSERR;
1680 LOG (GNUNET_ERROR_TYPE_DEBUG,
1681 "Session %p: connected with GET %p and PUT %p\n",
1682 s, s->get.easyhandle, s->put.easyhandle);
1683 /* Perform connect */
1684 GNUNET_STATISTICS_set (plugin->env->stats,
1685 HTTP_STAT_STR_CONNECTIONS,
1686 plugin->cur_requests,
1688 /* Re-schedule since handles have changed */
1689 if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
1691 GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
1692 plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1695 /* Schedule task to run immediately */
1696 plugin->client_perform_task = GNUNET_SCHEDULER_add_now (client_run, plugin);
1702 * Function obtain the network type for a session
1704 * @param cls closure (`struct Plugin*`)
1705 * @param session the session
1706 * @return the network type
1708 static enum GNUNET_ATS_Network_Type
1709 http_client_plugin_get_network (void *cls,
1710 struct Session *session)
1712 return ntohl (session->ats_address_network_type);
1717 * Session was idle, so disconnect it
1719 * @param cls the `struct Session` of the idle session
1720 * @param tc scheduler context
1723 client_session_timeout (void *cls,
1724 const struct GNUNET_SCHEDULER_TaskContext *tc)
1726 struct Session *s = cls;
1727 struct GNUNET_TIME_Relative left;
1729 s->timeout_task = GNUNET_SCHEDULER_NO_TASK;
1730 left = GNUNET_TIME_absolute_get_remaining (s->timeout);
1731 if (0 != left.rel_value_us)
1733 /* not actually our turn yet, but let's at least update
1734 the monitor, it may think we're about to die ... */
1735 notify_session_monitor (s->plugin,
1737 GNUNET_TRANSPORT_SS_UP);
1738 s->timeout_task = GNUNET_SCHEDULER_add_delayed (left,
1739 &client_session_timeout,
1744 "Session %p was idle for %s, disconnecting\n",
1746 GNUNET_STRINGS_relative_time_to_string (HTTP_CLIENT_SESSION_TIMEOUT,
1748 GNUNET_assert (GNUNET_OK ==
1749 http_client_plugin_session_disconnect (s->plugin,
1755 * Creates a new outbound session the transport service will use to
1756 * send data to the peer
1758 * @param cls the plugin
1759 * @param address the address
1760 * @return the session or NULL of max connections exceeded
1762 static struct Session *
1763 http_client_plugin_get_session (void *cls,
1764 const struct GNUNET_HELLO_Address *address)
1766 struct HTTP_Client_Plugin *plugin = cls;
1768 struct sockaddr *sa;
1769 struct GNUNET_ATS_Information ats;
1773 GNUNET_assert (NULL != address->address);
1775 /* find existing session */
1776 s = client_lookup_session (plugin, address);
1780 /* create a new session */
1781 if (plugin->max_requests <= plugin->cur_requests)
1783 LOG (GNUNET_ERROR_TYPE_WARNING,
1784 "Maximum number of requests (%u) reached: "
1785 "cannot connect to peer `%s'\n",
1786 plugin->max_requests,
1787 GNUNET_i2s (&address->peer));
1791 /* Determine network location */
1792 ats.type = htonl (GNUNET_ATS_NETWORK_TYPE);
1793 ats.value = htonl (GNUNET_ATS_NET_UNSPECIFIED);
1794 sa = http_common_socket_from_address (address->address, address->address_length, &res);
1795 if (GNUNET_SYSERR == res)
1797 if (GNUNET_YES == res)
1799 GNUNET_assert (NULL != sa);
1800 if (AF_INET == sa->sa_family)
1802 salen = sizeof (struct sockaddr_in);
1804 else if (AF_INET6 == sa->sa_family)
1806 salen = sizeof (struct sockaddr_in6);
1808 ats = plugin->env->get_address_type (plugin->env->cls, sa, salen);
1811 else if (GNUNET_NO == res)
1813 /* Cannot convert to sockaddr -> is external hostname */
1814 ats.value = htonl (GNUNET_ATS_NET_WAN);
1816 if (GNUNET_ATS_NET_UNSPECIFIED == ntohl (ats.value))
1822 s = GNUNET_new (struct Session);
1824 s->address = GNUNET_HELLO_address_copy (address);
1825 s->ats_address_network_type = ats.value;
1827 s->put.state = H_NOT_CONNECTED;
1828 s->timeout = GNUNET_TIME_relative_to_absolute (HTTP_CLIENT_SESSION_TIMEOUT);
1829 s->timeout_task = GNUNET_SCHEDULER_add_delayed (HTTP_CLIENT_SESSION_TIMEOUT,
1830 &client_session_timeout,
1832 LOG (GNUNET_ERROR_TYPE_DEBUG,
1833 "Created new session %p for `%s' address `%s''\n",
1835 http_common_plugin_address_to_string (plugin->protocol,
1836 s->address->address,
1837 s->address->address_length),
1838 GNUNET_i2s (&s->address->peer));
1840 /* add new session */
1841 (void) GNUNET_CONTAINER_multipeermap_put (plugin->sessions,
1844 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
1845 /* initiate new connection */
1846 if (GNUNET_SYSERR == client_connect (s))
1848 LOG (GNUNET_ERROR_TYPE_ERROR,
1849 "Cannot connect to peer `%s' address `%s''\n",
1850 http_common_plugin_address_to_string (plugin->protocol,
1851 s->address->address, s->address->address_length),
1852 GNUNET_i2s (&s->address->peer));
1853 client_delete_session (s);
1856 notify_session_monitor (plugin, s, GNUNET_TRANSPORT_SS_UP); /* or handshake? */
1862 * Setup http_client plugin
1864 * @param plugin the plugin handle
1865 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
1868 client_start (struct HTTP_Client_Plugin *plugin)
1870 curl_global_init (CURL_GLOBAL_ALL);
1871 plugin->curl_multi_handle = curl_multi_init ();
1873 if (NULL == plugin->curl_multi_handle)
1875 LOG (GNUNET_ERROR_TYPE_ERROR,
1876 _("Could not initialize curl multi handle, failed to start %s plugin!\n"),
1878 return GNUNET_SYSERR;
1885 * Another peer has suggested an address for this
1886 * peer and transport plugin. Check that this could be a valid
1887 * address. If so, consider adding it to the list
1890 * @param cls closure with the `struct Plugin`
1891 * @param addr pointer to the address
1892 * @param addrlen length of @a addr
1893 * @return #GNUNET_OK if this is a plausible address for this peer
1894 * and transport; always returns #GNUNET_NO (this is the client!)
1897 http_client_plugin_address_suggested (void *cls,
1901 /* A HTTP/S client does not have any valid address so:*/
1907 * Exit point from the plugin.
1909 * @param cls api as closure
1913 LIBGNUNET_PLUGIN_TRANSPORT_DONE (void *cls)
1915 struct GNUNET_TRANSPORT_PluginFunctions *api = cls;
1916 struct HTTP_Client_Plugin *plugin = api->cls;
1918 if (NULL == api->cls)
1924 LOG (GNUNET_ERROR_TYPE_DEBUG,
1925 _("Shutting down plugin `%s'\n"),
1927 GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
1928 &destroy_session_cb,
1930 if (GNUNET_SCHEDULER_NO_TASK != plugin->client_perform_task)
1932 GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
1933 plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1935 if (NULL != plugin->curl_multi_handle)
1937 curl_multi_cleanup (plugin->curl_multi_handle);
1938 plugin->curl_multi_handle = NULL;
1940 curl_global_cleanup ();
1941 LOG (GNUNET_ERROR_TYPE_DEBUG,
1942 _("Shutdown for plugin `%s' complete\n"),
1944 GNUNET_CONTAINER_multipeermap_destroy (plugin->sessions);
1945 GNUNET_free_non_null (plugin->proxy_hostname);
1946 GNUNET_free_non_null (plugin->proxy_username);
1947 GNUNET_free_non_null (plugin->proxy_password);
1948 GNUNET_free (plugin);
1957 * @param plugin the plugin handle
1958 * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
1961 client_configure_plugin (struct HTTP_Client_Plugin *plugin)
1963 unsigned long long max_requests;
1967 /* Optional parameters */
1969 GNUNET_CONFIGURATION_get_value_number (plugin->env->cfg,
1974 plugin->max_requests = max_requests;
1976 LOG (GNUNET_ERROR_TYPE_DEBUG,
1977 _("Maximum number of requests is %u\n"),
1978 plugin->max_requests);
1980 /* Read proxy configuration */
1981 if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
1982 plugin->name, "PROXY", &plugin->proxy_hostname))
1984 LOG (GNUNET_ERROR_TYPE_DEBUG,
1985 "Found proxy host: `%s'\n",
1986 plugin->proxy_hostname);
1987 /* proxy username */
1989 GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
1992 &plugin->proxy_username))
1994 LOG (GNUNET_ERROR_TYPE_DEBUG,
1995 "Found proxy username name: `%s'\n",
1996 plugin->proxy_username);
1999 /* proxy password */
2001 GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2004 &plugin->proxy_password))
2006 LOG (GNUNET_ERROR_TYPE_DEBUG,
2007 "Found proxy password name: `%s'\n",
2008 plugin->proxy_password);
2013 GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2018 GNUNET_STRINGS_utf8_toupper (proxy_type, proxy_type);
2020 if (0 == strcmp(proxy_type, "HTTP"))
2021 plugin->proxytype = CURLPROXY_HTTP;
2022 else if (0 == strcmp(proxy_type, "SOCKS4"))
2023 plugin->proxytype = CURLPROXY_SOCKS4;
2024 else if (0 == strcmp(proxy_type, "SOCKS5"))
2025 plugin->proxytype = CURLPROXY_SOCKS5;
2026 else if (0 == strcmp(proxy_type, "SOCKS4A"))
2027 plugin->proxytype = CURLPROXY_SOCKS4A;
2028 else if (0 == strcmp(proxy_type, "SOCKS5_HOSTNAME "))
2029 plugin->proxytype = CURLPROXY_SOCKS5_HOSTNAME ;
2032 LOG (GNUNET_ERROR_TYPE_ERROR,
2033 _("Invalid proxy type: `%s', disabling proxy! Check configuration!\n"),
2036 GNUNET_free (proxy_type);
2037 GNUNET_free (plugin->proxy_hostname);
2038 plugin->proxy_hostname = NULL;
2039 GNUNET_free_non_null (plugin->proxy_username);
2040 plugin->proxy_username = NULL;
2041 GNUNET_free_non_null (plugin->proxy_password);
2042 plugin->proxy_password = NULL;
2044 return GNUNET_SYSERR;
2047 LOG (GNUNET_ERROR_TYPE_DEBUG,
2048 "Found proxy type: `%s'\n",
2052 /* proxy http tunneling */
2053 plugin->proxy_use_httpproxytunnel
2054 = GNUNET_CONFIGURATION_get_value_yesno (plugin->env->cfg,
2056 "PROXY_HTTP_TUNNELING");
2057 if (GNUNET_SYSERR == plugin->proxy_use_httpproxytunnel)
2058 plugin->proxy_use_httpproxytunnel = GNUNET_NO;
2060 GNUNET_free_non_null (proxy_type);
2063 /* Should we emulate an XHR client for testing? */
2065 = GNUNET_CONFIGURATION_get_value_yesno (plugin->env->cfg,
2073 * Function called by the pretty printer for the resolved address for
2074 * each human-readable address obtained. The callback can be called
2075 * several times. The last invocation must be with a @a address of
2076 * NULL and a @a res of #GNUNET_OK. Thus, to indicate conversion
2077 * errors, the callback might be called first with @a address NULL and
2078 * @a res being #GNUNET_SYSERR. In that case, there must still be a
2079 * subsequent call later with @a address NULL and @a res #GNUNET_OK.
2081 * @param cls closure
2082 * @param address one of the names for the host, NULL on last callback
2083 * @param res #GNUNET_OK if conversion was successful, #GNUNET_SYSERR on failure,
2084 * #GNUNET_OK on last callback
2087 http_client_plugin_address_to_string (void *cls,
2091 return http_common_plugin_address_to_string (PLUGIN_NAME,
2098 * Function that will be called whenever the transport service wants to
2099 * notify the plugin that a session is still active and in use and
2100 * therefore the session timeout for this session has to be updated
2102 * @param cls closure
2103 * @param peer which peer was the session for
2104 * @param session which session is being updated
2107 http_client_plugin_update_session_timeout (void *cls,
2108 const struct GNUNET_PeerIdentity *peer,
2109 struct Session *session)
2111 client_reschedule_session_timeout (session);
2116 * Function that will be called whenever the transport service wants to
2117 * notify the plugin that the inbound quota changed and that the plugin
2118 * should update it's delay for the next receive value
2120 * @param cls closure
2121 * @param peer which peer was the session for
2122 * @param session which session is being updated
2123 * @param delay new delay to use for receiving
2126 http_client_plugin_update_inbound_delay (void *cls,
2127 const struct GNUNET_PeerIdentity *peer,
2129 struct GNUNET_TIME_Relative delay)
2131 s->next_receive = GNUNET_TIME_relative_to_absolute (delay);
2132 LOG (GNUNET_ERROR_TYPE_DEBUG,
2133 "New inbound delay %s\n",
2134 GNUNET_STRINGS_relative_time_to_string (delay,
2136 if (s->recv_wakeup_task != GNUNET_SCHEDULER_NO_TASK)
2138 GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
2139 s->recv_wakeup_task = GNUNET_SCHEDULER_add_delayed (delay,
2140 &client_wake_up, s);
2146 * Return information about the given session to the
2149 * @param cls the `struct Plugin` with the monitor callback (`sic`)
2150 * @param peer peer we send information about
2151 * @param value our `struct Session` to send information about
2152 * @return #GNUNET_OK (continue to iterate)
2155 send_session_info_iter (void *cls,
2156 const struct GNUNET_PeerIdentity *peer,
2159 struct HTTP_Client_Plugin *plugin = cls;
2160 struct Session *session = value;
2162 notify_session_monitor (plugin,
2164 GNUNET_TRANSPORT_SS_UP);
2170 * Begin monitoring sessions of a plugin. There can only
2171 * be one active monitor per plugin (i.e. if there are
2172 * multiple monitors, the transport service needs to
2173 * multiplex the generated events over all of them).
2175 * @param cls closure of the plugin
2176 * @param sic callback to invoke, NULL to disable monitor;
2177 * plugin will being by iterating over all active
2178 * sessions immediately and then enter monitor mode
2179 * @param sic_cls closure for @a sic
2182 http_client_plugin_setup_monitor (void *cls,
2183 GNUNET_TRANSPORT_SessionInfoCallback sic,
2186 struct HTTP_Client_Plugin *plugin = cls;
2189 plugin->sic_cls = sic_cls;
2192 GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
2193 &send_session_info_iter,
2195 /* signal end of first iteration */
2196 sic (sic_cls, NULL, NULL);
2202 * Entry point for the plugin.
2205 LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls)
2207 struct GNUNET_TRANSPORT_PluginEnvironment *env = cls;
2208 struct GNUNET_TRANSPORT_PluginFunctions *api;
2209 struct HTTP_Client_Plugin *plugin;
2211 if (NULL == env->receive)
2213 /* run in 'stub' mode (i.e. as part of gnunet-peerinfo), don't fully
2214 initialze the plugin or the API */
2215 api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions);
2217 api->address_to_string = &http_client_plugin_address_to_string;
2218 api->string_to_address = &http_common_plugin_string_to_address;
2219 api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
2223 plugin = GNUNET_new (struct HTTP_Client_Plugin);
2225 plugin->sessions = GNUNET_CONTAINER_multipeermap_create (128,
2227 api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions);
2229 api->send = &http_client_plugin_send;
2230 api->disconnect_session = &http_client_plugin_session_disconnect;
2231 api->query_keepalive_factor = &http_client_query_keepalive_factor;
2232 api->disconnect_peer = &http_client_plugin_peer_disconnect;
2233 api->check_address = &http_client_plugin_address_suggested;
2234 api->get_session = &http_client_plugin_get_session;
2235 api->address_to_string = &http_client_plugin_address_to_string;
2236 api->string_to_address = &http_common_plugin_string_to_address;
2237 api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
2238 api->get_network = &http_client_plugin_get_network;
2239 api->update_session_timeout = &http_client_plugin_update_session_timeout;
2240 api->update_inbound_delay = &http_client_plugin_update_inbound_delay;
2241 api->setup_monitor = &http_client_plugin_setup_monitor;
2243 plugin->name = "transport-https_client";
2244 plugin->protocol = "https";
2246 plugin->name = "transport-http_client";
2247 plugin->protocol = "http";
2249 plugin->last_tag = 1;
2251 if (GNUNET_SYSERR == client_configure_plugin (plugin))
2253 LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
2258 if (GNUNET_SYSERR == client_start (plugin))
2260 LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
2266 /* end of plugin_transport_http_client.c */