2 This file is part of GNUnet
3 (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 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
28 #define LIBGNUNET_PLUGIN_TRANSPORT_INIT libgnunet_plugin_transport_https_client_init
29 #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_https_client_done
31 #define LIBGNUNET_PLUGIN_TRANSPORT_INIT libgnunet_plugin_transport_http_client_init
32 #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_http_client_done
35 #define VERBOSE_CURL GNUNET_YES
37 #define PUT_DISCONNECT_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1)
39 #define ENABLE_PUT GNUNET_YES
40 #define ENABLE_GET GNUNET_YES
43 #include "gnunet_protocols.h"
44 #include "gnunet_common.h"
45 #include "gnunet_server_lib.h"
46 #include "gnunet_transport_plugin.h"
47 #include "plugin_transport_http_common.h"
48 #include <curl/curl.h>
52 * Encapsulation of all of the state of the plugin.
54 struct HTTP_Client_Plugin;
58 * Message to send using http
63 * next pointer for double linked list
65 struct HTTP_Message *next;
68 * previous pointer for double linked list
70 struct HTTP_Message *prev;
73 * buffer containing data to send
78 * amount of data already sent
88 * Continuation function to call once the transmission buffer
89 * has again space available. NULL if there is no
90 * continuation to call.
92 GNUNET_TRANSPORT_TransmitContinuation transmit_cont;
95 * Closure for transmit_cont.
97 void *transmit_cont_cls;
101 * Session handle for connections.
106 * To whom are we talking to (set to our identity
107 * if we are still waiting for the welcome message)
109 struct GNUNET_PeerIdentity target;
112 * Stored in a linked list.
114 struct Session *next;
117 * Stored in a linked list.
119 struct Session *prev;
122 * The URL to connect to
137 * ATS network type in NBO
139 uint32_t ats_address_network_type;
142 * Pointer to the global plugin struct.
144 struct HTTP_Client_Plugin *plugin;
147 * Was session given to transport service?
149 // int session_passed;
158 * Is the client PUT handle currently paused
163 * Is the client PUT handle disconnect in progress?
165 int put_tmp_disconnecting;
168 * Is the client PUT handle temporarily disconnected?
170 int put_tmp_disconnected;
173 * We received data to send while disconnecting, reconnect immediately
175 int put_reconnect_required;
178 * Client receive handle
183 * next pointer for double linked list
185 struct HTTP_Message *msg_head;
188 * previous pointer for double linked list
190 struct HTTP_Message *msg_tail;
193 * Message stream tokenizer for incoming data
195 struct GNUNET_SERVER_MessageStreamTokenizer *msg_tk;
198 * Session timeout task
200 GNUNET_SCHEDULER_TaskIdentifier put_disconnect_task;
203 * Session timeout task
205 GNUNET_SCHEDULER_TaskIdentifier timeout_task;
208 * Task to wake up client receive handle when receiving is allowed again
210 GNUNET_SCHEDULER_TaskIdentifier recv_wakeup_task;
213 * Absolute time when to receive data again
214 * Used for receive throttling
216 struct GNUNET_TIME_Absolute next_receive;
221 * Encapsulation of all of the state of the plugin.
223 struct HTTP_Client_Plugin
228 struct GNUNET_TRANSPORT_PluginEnvironment *env;
231 * Linked list head of open sessions.
233 struct Session *head;
236 * Linked list tail of open sessions.
238 struct Session *tail;
251 * Maximum number of sockets the plugin can use
252 * Each http inbound /outbound connections are two connections
254 unsigned int max_connections;
257 * Current number of sockets the plugin can use
258 * Each http inbound /outbound connections are two connections
260 unsigned int cur_connections;
263 * Last used unique HTTP connection tag
280 CURLM *curl_multi_handle;
285 GNUNET_SCHEDULER_TaskIdentifier client_perform_task;
290 * Encapsulation of all of the state of the plugin.
292 struct HTTP_Client_Plugin *p;
296 * Start session timeout for a session
297 * @param s the session
300 client_start_session_timeout (struct Session *s);
304 * Increment session timeout due to activity for a session
305 * @param s the session
308 client_reschedule_session_timeout (struct Session *s);
312 * Cancel timeout for a session
313 * @param s the session
316 client_stop_session_timeout (struct Session *s);
320 * Function setting up file descriptors and scheduling task to run
322 * @param plugin plugin as closure
323 * @param now schedule task in 1ms, regardless of what curl may say
324 * @return GNUNET_SYSERR for hard failure, GNUNET_OK for ok
327 client_schedule (struct HTTP_Client_Plugin *plugin, int now);
330 client_connect_put (struct Session *s);
333 * Does a session s exists?
335 * @param plugin the plugin
336 * @param s desired session
337 * @return GNUNET_YES or GNUNET_NO
340 client_exist_session (struct HTTP_Client_Plugin *plugin, struct Session *s)
342 struct Session * head;
344 GNUNET_assert (NULL != plugin);
345 GNUNET_assert (NULL != s);
347 for (head = plugin->head; head != NULL; head = head->next)
357 * Function to log curl debug messages with GNUNET_log
360 * @param type curl_infotype
367 client_log (CURL * curl, curl_infotype type, char *data, size_t size, void *cls)
370 if ((type == CURLINFO_TEXT) || (type == CURLINFO_HEADER_IN) || (type == CURLINFO_HEADER_OUT))
378 case CURLINFO_HEADER_IN:
381 case CURLINFO_HEADER_OUT:
382 ttype = "HEADER_OUT";
388 memcpy (text, data, size);
389 if (text[size - 1] == '\n')
394 text[size + 1] = '\0';
397 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "transport-https_client",
398 "Connection %p %s: %s", cls, ttype, text);
400 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "transport-http_client",
401 "Connection %p %s: %s", cls, ttype, text);
410 * Function that can be used by the transport service to transmit
411 * a message using the plugin. Note that in the case of a
412 * peer disconnecting, the continuation MUST be called
413 * prior to the disconnect notification itself. This function
414 * will be called with this peer's HELLO message to initiate
415 * a fresh connection to another peer.
418 * @param s which session must be used
419 * @param msgbuf the message to transmit
420 * @param msgbuf_size number of bytes in 'msgbuf'
421 * @param priority how important is the message (most plugins will
422 * ignore message priority and just FIFO)
423 * @param to how long to wait at most for the transmission (does not
424 * require plugins to discard the message after the timeout,
425 * just advisory for the desired delay; most plugins will ignore
427 * @param cont continuation to call once the message has
428 * been transmitted (or if the transport is ready
429 * for the next transmission call; or if the
430 * peer disconnected...); can be NULL
431 * @param cont_cls closure for cont
432 * @return number of bytes used (on the physical network, with overheads);
433 * -1 on hard errors (i.e. address invalid); 0 is a legal value
434 * and does NOT mean that the message was not transmitted (DV)
437 http_client_plugin_send (void *cls,
439 const char *msgbuf, size_t msgbuf_size,
440 unsigned int priority,
441 struct GNUNET_TIME_Relative to,
442 GNUNET_TRANSPORT_TransmitContinuation cont, void *cont_cls)
444 struct HTTP_Client_Plugin *plugin = cls;
445 struct HTTP_Message *msg;
447 GNUNET_assert (plugin != NULL);
448 GNUNET_assert (s != NULL);
450 /* lookup if session is really existing */
451 if (GNUNET_YES != client_exist_session (plugin, s))
454 return GNUNET_SYSERR;
457 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
458 "Session %p/connection %p: Sending message with %u to peer `%s' \n",
460 msgbuf_size, GNUNET_i2s (&s->target));
462 /* create new message and schedule */
463 msg = GNUNET_malloc (sizeof (struct HTTP_Message) + msgbuf_size);
465 msg->size = msgbuf_size;
467 msg->buf = (char *) &msg[1];
468 msg->transmit_cont = cont;
469 msg->transmit_cont_cls = cont_cls;
470 memcpy (msg->buf, msgbuf, msgbuf_size);
471 GNUNET_CONTAINER_DLL_insert_tail (s->msg_head, s->msg_tail, msg);
473 if (GNUNET_YES == s->put_tmp_disconnecting)
475 /* PUT connection is currently getting disconnected */
476 s->put_reconnect_required = GNUNET_YES;
477 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
478 "Session %p/connection %jp: currently disconnecting, reconnecting immediately\n",
482 else if (GNUNET_YES == s->put_paused)
484 /* PUT connection was paused, unpause */
485 GNUNET_assert (s->put_disconnect_task != GNUNET_SCHEDULER_NO_TASK);
486 GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
487 s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
488 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
489 "Session %p/connection %p: unpausing connection\n",
491 s->put_paused = GNUNET_NO;
492 curl_easy_pause (s->client_put, CURLPAUSE_CONT);
494 else if (GNUNET_YES == s->put_tmp_disconnected)
496 /* PUT connection was disconnected, reconnect */
497 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
498 "Session %p: Reconnecting PUT connection\n",
500 s->put_tmp_disconnected = GNUNET_NO;
501 GNUNET_break (s->client_put == NULL);
502 if (GNUNET_SYSERR == client_connect_put (s))
504 return GNUNET_SYSERR;
508 client_schedule (s->plugin, GNUNET_YES);
509 client_reschedule_session_timeout (s);
517 * @param s the session to delete
520 client_delete_session (struct Session *s)
522 struct HTTP_Client_Plugin *plugin = s->plugin;
523 struct HTTP_Message *pos;
524 struct HTTP_Message *next;
526 client_stop_session_timeout (s);
528 if (GNUNET_SCHEDULER_NO_TASK != s->put_disconnect_task)
530 GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
531 s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
534 GNUNET_CONTAINER_DLL_remove (plugin->head, plugin->tail, s);
537 while (NULL != (pos = next))
540 GNUNET_CONTAINER_DLL_remove (s->msg_head, s->msg_tail, pos);
541 if (pos->transmit_cont != NULL)
542 pos->transmit_cont (pos->transmit_cont_cls, &s->target, GNUNET_SYSERR);
546 if (s->msg_tk != NULL)
548 GNUNET_SERVER_mst_destroy (s->msg_tk);
551 GNUNET_free (s->addr);
552 GNUNET_free (s->url);
559 * Disconnect a session
562 * @return GNUNET_OK on success, GNUNET_SYSERR on error
565 client_disconnect (struct Session *s)
567 struct HTTP_Client_Plugin *plugin = s->plugin;
568 struct HTTP_Message *msg;
569 struct HTTP_Message *t;
573 if (GNUNET_YES != client_exist_session (plugin, s))
576 return GNUNET_SYSERR;
579 if (s->client_put != NULL)
581 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
582 "Session %p/connection %p: disconnecting PUT connection to peer `%s'\n",
583 s, s->client_put, GNUNET_i2s (&s->target));
585 /* remove curl handle from multi handle */
586 mret = curl_multi_remove_handle (plugin->curl_multi_handle, s->client_put);
587 if (mret != CURLM_OK)
589 /* clean up easy handle, handle is now invalid and free'd */
593 curl_easy_cleanup (s->client_put);
594 s->client_put = NULL;
598 if (s->recv_wakeup_task != GNUNET_SCHEDULER_NO_TASK)
600 GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
601 s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
604 if (s->client_get != NULL)
606 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
607 "Session %p/connection %p: disconnecting GET connection to peer `%s'\n",
608 s, s->client_get, GNUNET_i2s (&s->target));
610 /* remove curl handle from multi handle */
611 mret = curl_multi_remove_handle (plugin->curl_multi_handle, s->client_get);
612 if (mret != CURLM_OK)
614 /* clean up easy handle, handle is now invalid and free'd */
618 curl_easy_cleanup (s->client_get);
619 s->client_get = NULL;
626 if (NULL != msg->transmit_cont)
627 msg->transmit_cont (msg->transmit_cont_cls, &s->target, GNUNET_SYSERR);
628 GNUNET_CONTAINER_DLL_remove (s->msg_head, s->msg_tail, msg);
633 GNUNET_assert (plugin->cur_connections >= 2);
634 plugin->cur_connections -= 2;
635 GNUNET_STATISTICS_set (plugin->env->stats,
636 "# HTTP client sessions",
637 plugin->cur_connections,
640 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
641 "Session %p: notifying transport about ending session\n",s);
643 plugin->env->session_end (plugin->env->cls, &s->target, s);
644 client_delete_session (s);
646 /* Re-schedule since handles have changed */
647 if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
649 GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
650 plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
652 client_schedule (plugin, GNUNET_YES);
659 * Function that can be used to force the plugin to disconnect
660 * from the given peer and cancel all previous transmissions
661 * (and their continuationc).
664 * @param target peer from which to disconnect
667 http_client_plugin_disconnect (void *cls, const struct GNUNET_PeerIdentity *target)
669 struct HTTP_Client_Plugin *plugin = cls;
670 struct Session *next = NULL;
671 struct Session *pos = NULL;
673 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
674 "Transport tells me to disconnect `%s'\n",
675 GNUNET_i2s (target));
678 while (NULL != (pos = next))
681 if (0 == memcmp (target, &pos->target, sizeof (struct GNUNET_PeerIdentity)))
683 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
684 "Disconnecting session %p to `%pos'\n",
685 pos, GNUNET_i2s (target));
686 GNUNET_assert (GNUNET_OK == client_disconnect (pos));
693 static struct Session *
694 client_lookup_session (struct HTTP_Client_Plugin *plugin,
695 const struct GNUNET_HELLO_Address *address)
699 for (pos = plugin->head; NULL != pos; pos = pos->next)
700 if ((0 == memcmp (&address->peer, &pos->target, sizeof (struct GNUNET_PeerIdentity))) &&
701 (address->address_length == pos->addrlen) &&
702 (0 == memcmp (address->address, pos->addr, pos->addrlen)))
708 client_put_disconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
710 struct Session *s = cls;
711 s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
712 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
713 "Session %p/connection %p: will be disconnected due to no activity\n",
715 s->put_paused = GNUNET_NO;
716 s->put_tmp_disconnecting = GNUNET_YES;
717 curl_easy_pause (s->client_put, CURLPAUSE_CONT);
718 client_schedule (s->plugin, GNUNET_YES);
724 * Callback method used with libcurl
725 * Method is called when libcurl needs to read data during sending
727 * @param stream pointer where to write data
728 * @param size size of an individual element
729 * @param nmemb count of elements that can be written to the buffer
730 * @param cls source pointer, passed to the libcurl handle
731 * @return bytes written to stream, returning 0 will terminate connection!
734 client_send_cb (void *stream, size_t size, size_t nmemb, void *cls)
736 struct Session *s = cls;
737 struct HTTP_Client_Plugin *plugin = s->plugin;
738 struct HTTP_Message *msg = s->msg_head;
741 if (GNUNET_YES != client_exist_session (plugin, s))
746 if (GNUNET_YES == s->put_tmp_disconnecting)
749 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
750 "Session %p/connection %p: disconnect due to inactivity\n",
757 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
758 "Session %p/connection %p: nothing to send, suspending\n",
760 s->put_disconnect_task = GNUNET_SCHEDULER_add_delayed (PUT_DISCONNECT_TIMEOUT, &client_put_disconnect, s);
761 s->put_paused = GNUNET_YES;
762 return CURL_READFUNC_PAUSE;
765 GNUNET_assert (msg->pos < msg->size);
766 /* calculate how much fits in buffer */
767 len = GNUNET_MIN (msg->size - msg->pos,
769 memcpy (stream, &msg->buf[msg->pos], len);
771 if (msg->pos == msg->size)
773 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
774 "Session %p/connection %p: sent message with %u bytes sent, removing message from queue\n",
775 s, s->client_put, msg->size, msg->pos);
776 /* Calling transmit continuation */
777 GNUNET_CONTAINER_DLL_remove (s->msg_head, s->msg_tail, msg);
778 if (NULL != msg->transmit_cont)
779 msg->transmit_cont (msg->transmit_cont_cls, &s->target, GNUNET_OK);
782 client_reschedule_session_timeout (s);
788 * Wake up a curl handle which was suspended
790 * @param cls the session
791 * @param tc task context
794 client_wake_up (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
796 struct Session *s = cls;
798 if (GNUNET_YES != client_exist_session(p, s))
803 s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
804 if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
806 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
807 "Session %p/connection %p: Waking up GET handle\n", s, s->client_get);
808 if (s->client_get != NULL)
809 curl_easy_pause (s->client_get, CURLPAUSE_CONT);
814 * Callback for message stream tokenizer
816 * @param cls the session
817 * @param client not used
818 * @param message the message received
819 * @return always GNUNET_OK
822 client_receive_mst_cb (void *cls, void *client,
823 const struct GNUNET_MessageHeader *message)
825 struct Session *s = cls;
826 struct HTTP_Client_Plugin *plugin;
827 struct GNUNET_TIME_Relative delay;
828 struct GNUNET_ATS_Information atsi[2];
829 if (GNUNET_YES != client_exist_session(p, s))
836 atsi[0].type = htonl (GNUNET_ATS_QUALITY_NET_DISTANCE);
837 atsi[0].value = htonl (1);
838 atsi[1].type = htonl (GNUNET_ATS_NETWORK_TYPE);
839 atsi[1].value = s->ats_address_network_type;
840 GNUNET_break (s->ats_address_network_type != ntohl (GNUNET_ATS_NET_UNSPECIFIED));
842 delay = s->plugin->env->receive (plugin->env->cls, &s->target, message,
843 (const struct GNUNET_ATS_Information *) &atsi, 2,
844 s, s->addr, s->addrlen);
847 GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), delay);
849 if (GNUNET_TIME_absolute_get ().abs_value < s->next_receive.abs_value)
852 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
853 "Client: peer `%s' address `%s' next read delayed for %llu ms\n",
854 GNUNET_i2s (&s->target), GNUNET_a2s (s->addr, s->addrlen),
857 client_reschedule_session_timeout (s);
863 * Callback method used with libcurl when data for a PUT connection are
864 * received. We do not expect data here, so we just dismiss it
866 * @param stream pointer where to write data
867 * @param size size of an individual element
868 * @param nmemb count of elements that can be written to the buffer
869 * @param cls destination pointer, passed to the libcurl handle
870 * @return bytes read from stream
873 client_receive_put (void *stream, size_t size, size_t nmemb, void *cls)
880 * Callback method used with libcurl when data for a GET connection are
881 * received. Forward to MST
883 * @param stream pointer where to write data
884 * @param size size of an individual element
885 * @param nmemb count of elements that can be written to the buffer
886 * @param cls destination pointer, passed to the libcurl handle
887 * @return bytes read from stream
890 client_receive (void *stream, size_t size, size_t nmemb, void *cls)
892 struct Session *s = cls;
893 struct GNUNET_TIME_Absolute now;
894 size_t len = size * nmemb;
895 struct HTTP_Client_Plugin *plugin = s->plugin;
897 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
898 "Session %p / connection %p: Received %u bytes from peer `%s'\n",
900 len, GNUNET_i2s (&s->target));
901 now = GNUNET_TIME_absolute_get ();
902 if (now.abs_value < s->next_receive.abs_value)
904 struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
905 struct GNUNET_TIME_Relative delta =
906 GNUNET_TIME_absolute_get_difference (now, s->next_receive);
907 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
908 "Session %p / connection %p: No inbound bandwidth available! Next read was delayed for %llu ms\n",
909 s, s->client_get, delta.rel_value);
910 if (s->recv_wakeup_task != GNUNET_SCHEDULER_NO_TASK)
912 GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
913 s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
915 s->recv_wakeup_task =
916 GNUNET_SCHEDULER_add_delayed (delta, &client_wake_up, s);
917 return CURL_WRITEFUNC_PAUSE;
919 if (NULL == s->msg_tk)
920 s->msg_tk = GNUNET_SERVER_mst_create (&client_receive_mst_cb, s);
921 GNUNET_SERVER_mst_receive (s->msg_tk, s, stream, len, GNUNET_NO, GNUNET_NO);
927 * Task performing curl operations
929 * @param cls plugin as closure
930 * @param tc gnunet scheduler task context
933 client_run (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
937 * Function setting up file descriptors and scheduling task to run
939 * @param plugin plugin as closure
940 * @param now schedule task in 1ms, regardless of what curl may say
941 * @return GNUNET_SYSERR for hard failure, GNUNET_OK for ok
944 client_schedule (struct HTTP_Client_Plugin *plugin, int now)
950 struct GNUNET_NETWORK_FDSet *grs;
951 struct GNUNET_NETWORK_FDSet *gws;
954 struct GNUNET_TIME_Relative timeout;
956 /* Cancel previous scheduled task */
957 if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
959 GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
960 plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
966 mret = curl_multi_fdset (plugin->curl_multi_handle, &rs, &ws, &es, &max);
967 if (mret != CURLM_OK)
969 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("%s failed at %s:%d: `%s'\n"),
970 "curl_multi_fdset", __FILE__, __LINE__,
971 curl_multi_strerror (mret));
972 return GNUNET_SYSERR;
974 mret = curl_multi_timeout (plugin->curl_multi_handle, &to);
976 timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1);
978 timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, to);
979 if (now == GNUNET_YES)
980 timeout = GNUNET_TIME_UNIT_MILLISECONDS;
982 if (mret != CURLM_OK)
984 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("%s failed at %s:%d: `%s'\n"),
985 "curl_multi_timeout", __FILE__, __LINE__,
986 curl_multi_strerror (mret));
987 return GNUNET_SYSERR;
990 grs = GNUNET_NETWORK_fdset_create ();
991 gws = GNUNET_NETWORK_fdset_create ();
992 GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
993 GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
995 plugin->client_perform_task =
996 GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
998 &client_run, plugin);
999 GNUNET_NETWORK_fdset_destroy (gws);
1000 GNUNET_NETWORK_fdset_destroy (grs);
1006 * Task performing curl operations
1008 * @param cls plugin as closure
1009 * @param tc gnunet scheduler task context
1012 client_run (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
1014 struct HTTP_Client_Plugin *plugin = cls;
1016 long http_statuscode;
1019 GNUNET_assert (cls != NULL);
1021 plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1022 if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
1028 mret = curl_multi_perform (plugin->curl_multi_handle, &running);
1033 while ((msg = curl_multi_info_read (plugin->curl_multi_handle, &msgs_left)))
1035 CURL *easy_h = msg->easy_handle;
1036 struct Session *s = NULL;
1037 char *d = (char *) s;
1042 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1043 "Client: connection to ended with reason %i: `%s', %i handles running\n",
1045 curl_easy_strerror (msg->data.result), running);
1049 GNUNET_assert (CURLE_OK ==
1050 curl_easy_getinfo (easy_h, CURLINFO_PRIVATE, &d));
1051 s = (struct Session *) d;
1053 if (GNUNET_YES != client_exist_session(plugin, s))
1059 GNUNET_assert (s != NULL);
1060 if (msg->msg == CURLMSG_DONE)
1062 curl_easy_getinfo (easy_h, CURLINFO_RESPONSE_CODE, &http_statuscode);
1063 if (easy_h == s->client_put)
1065 if ((0 != msg->data.result) || (http_statuscode != 200))
1067 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1068 "Session %p/connection %p: PUT connection to `%s' ended with status %i reason %i: `%s'\n",
1069 s, msg->easy_handle, GNUNET_i2s (&s->target),
1072 curl_easy_strerror (msg->data.result));
1075 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1076 "Session %p/connection %p: PUT connection to `%s' ended normal\n",
1077 s, msg->easy_handle, GNUNET_i2s (&s->target));
1078 if (s->client_get == NULL)
1080 /* Disconnect other transmission direction and tell transport */
1082 curl_multi_remove_handle (plugin->curl_multi_handle, easy_h);
1083 curl_easy_cleanup (easy_h);
1084 s->put_tmp_disconnecting = GNUNET_NO;
1085 s->put_tmp_disconnected = GNUNET_YES;
1086 s->client_put = NULL;
1089 * Handling a rare case:
1090 * plugin_send was called during temporary put disconnect,
1091 * reconnect required after connection was disconnected
1093 if (GNUNET_YES == s->put_reconnect_required)
1095 s->put_reconnect_required = GNUNET_NO;
1096 client_connect_put(s);
1099 if (easy_h == s->client_get)
1101 if ((0 != msg->data.result) || (http_statuscode != 200))
1103 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1104 "Session %p/connection %p: GET connection to `%s' ended with status %i reason %i: `%s'\n",
1105 s, msg->easy_handle, GNUNET_i2s (&s->target),
1108 curl_easy_strerror (msg->data.result));
1112 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1113 "Session %p/connection %p: GET connection to `%s' ended normal\n",
1114 s, msg->easy_handle, GNUNET_i2s (&s->target));
1115 /* Disconnect other transmission direction and tell transport */
1116 client_disconnect (s);
1121 while (mret == CURLM_CALL_MULTI_PERFORM);
1122 client_schedule (plugin, GNUNET_NO);
1126 client_connect_get (struct Session *s)
1129 /* create get connection */
1130 s->client_get = curl_easy_init ();
1132 curl_easy_setopt (s->client_get, CURLOPT_VERBOSE, 1L);
1133 curl_easy_setopt (s->client_get, CURLOPT_DEBUGFUNCTION, &client_log);
1134 curl_easy_setopt (s->client_get, CURLOPT_DEBUGDATA, s->client_get);
1137 curl_easy_setopt (s->client_get, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
1138 curl_easy_setopt (s->client_get, CURLOPT_SSL_VERIFYPEER, 0);
1139 curl_easy_setopt (s->client_get, CURLOPT_SSL_VERIFYHOST, 0);
1141 curl_easy_setopt (s->client_get, CURLOPT_URL, s->url);
1142 //curl_easy_setopt (s->client_get, CURLOPT_HEADERFUNCTION, &curl_get_header_cb);
1143 //curl_easy_setopt (s->client_get, CURLOPT_WRITEHEADER, ps);
1144 curl_easy_setopt (s->client_get, CURLOPT_READFUNCTION, client_send_cb);
1145 curl_easy_setopt (s->client_get, CURLOPT_READDATA, s);
1146 curl_easy_setopt (s->client_get, CURLOPT_WRITEFUNCTION, client_receive);
1147 curl_easy_setopt (s->client_get, CURLOPT_WRITEDATA, s);
1148 /* No timeout by default, timeout done with session timeout */
1149 curl_easy_setopt (s->client_get, CURLOPT_TIMEOUT, 0);
1150 curl_easy_setopt (s->client_get, CURLOPT_PRIVATE, s);
1151 curl_easy_setopt (s->client_get, CURLOPT_CONNECTTIMEOUT_MS,
1152 (long) HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value);
1153 curl_easy_setopt (s->client_get, CURLOPT_BUFFERSIZE,
1154 2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1155 #if CURL_TCP_NODELAY
1156 curl_easy_setopt (ps->recv_endpoint, CURLOPT_TCP_NODELAY, 1);
1158 mret = curl_multi_add_handle (s->plugin->curl_multi_handle, s->client_get);
1159 if (mret != CURLM_OK)
1161 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, s->plugin->name,
1162 "Session %p : Failed to add GET handle to multihandle: `%s'\n",
1163 s, curl_multi_strerror (mret));
1164 curl_easy_cleanup (s->client_get);
1165 s->client_get = NULL;
1167 return GNUNET_SYSERR;
1174 client_connect_put (struct Session *s)
1177 /* create put connection */
1178 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, s->plugin->name,
1179 "Session %p : Init PUT handle \n", s);
1180 s->client_put = curl_easy_init ();
1182 curl_easy_setopt (s->client_put, CURLOPT_VERBOSE, 1L);
1183 curl_easy_setopt (s->client_put, CURLOPT_DEBUGFUNCTION, &client_log);
1184 curl_easy_setopt (s->client_put, CURLOPT_DEBUGDATA, s->client_put);
1187 curl_easy_setopt (s->client_put, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
1188 curl_easy_setopt (s->client_put, CURLOPT_SSL_VERIFYPEER, 0);
1189 curl_easy_setopt (s->client_put, CURLOPT_SSL_VERIFYHOST, 0);
1191 curl_easy_setopt (s->client_put, CURLOPT_URL, s->url);
1192 curl_easy_setopt (s->client_put, CURLOPT_UPLOAD, 1L);
1193 //curl_easy_setopt (s->client_put, CURLOPT_HEADERFUNCTION, &client_curl_header);
1194 //curl_easy_setopt (s->client_put, CURLOPT_WRITEHEADER, ps);
1195 curl_easy_setopt (s->client_put, CURLOPT_READFUNCTION, client_send_cb);
1196 curl_easy_setopt (s->client_put, CURLOPT_READDATA, s);
1197 curl_easy_setopt (s->client_put, CURLOPT_WRITEFUNCTION, client_receive_put);
1198 curl_easy_setopt (s->client_put, CURLOPT_WRITEDATA, s);
1199 /* No timeout by default, timeout done with session timeout */
1200 curl_easy_setopt (s->client_put, CURLOPT_TIMEOUT, 0);
1201 curl_easy_setopt (s->client_put, CURLOPT_PRIVATE, s);
1202 curl_easy_setopt (s->client_put, CURLOPT_CONNECTTIMEOUT_MS,
1203 (long) HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value);
1204 curl_easy_setopt (s->client_put, CURLOPT_BUFFERSIZE,
1205 2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1206 #if CURL_TCP_NODELAY
1207 curl_easy_setopt (s->client_put, CURLOPT_TCP_NODELAY, 1);
1209 mret = curl_multi_add_handle (s->plugin->curl_multi_handle, s->client_put);
1210 if (mret != CURLM_OK)
1212 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, s->plugin->name,
1213 "Session %p : Failed to add PUT handle to multihandle: `%s'\n",
1214 s, curl_multi_strerror (mret));
1215 curl_easy_cleanup (s->client_put);
1216 s->client_put = NULL;
1217 return GNUNET_SYSERR;
1223 client_connect (struct Session *s)
1226 struct HTTP_Client_Plugin *plugin = s->plugin;
1227 int res = GNUNET_OK;
1231 if (NULL == http_common_plugin_address_to_string (NULL, s->addr, s->addrlen))
1233 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1234 "Invalid address peer `%s'\n",
1235 GNUNET_i2s (&s->target));
1236 return GNUNET_SYSERR;
1239 GNUNET_asprintf (&s->url, "%s/%s;%u",
1240 http_common_plugin_address_to_string (plugin, s->addr, s->addrlen),
1241 GNUNET_h2s_full (&plugin->env->my_identity->hashPubKey),
1246 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1247 "Initiating outbound session peer `%s' using address `%s'\n",
1248 GNUNET_i2s (&s->target), s->url);
1250 if ((GNUNET_SYSERR == client_connect_get (s)) ||
1251 (GNUNET_SYSERR == client_connect_put (s)))
1254 return GNUNET_SYSERR;
1257 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1258 "Session %p: connected with connections GET %p and PUT %p\n",
1259 s, s->client_get, s->client_put);
1261 /* Perform connect */
1262 plugin->cur_connections += 2;
1263 GNUNET_STATISTICS_set (plugin->env->stats,
1264 "# HTTP client connections",
1265 plugin->cur_connections,
1268 /* Re-schedule since handles have changed */
1269 if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
1271 GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
1272 plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1274 plugin->client_perform_task = GNUNET_SCHEDULER_add_now (client_run, plugin);
1280 * Creates a new outbound session the transport service will use to send data to the
1283 * @param cls the plugin
1284 * @param address the address
1285 * @return the session or NULL of max connections exceeded
1287 static struct Session *
1288 http_client_plugin_get_session (void *cls,
1289 const struct GNUNET_HELLO_Address *address)
1291 struct HTTP_Client_Plugin *plugin = cls;
1292 struct Session * s = NULL;
1293 struct sockaddr *sa;
1294 struct GNUNET_ATS_Information ats;
1298 GNUNET_assert (plugin != NULL);
1299 GNUNET_assert (address != NULL);
1300 GNUNET_assert (address->address != NULL);
1302 /* find existing session */
1303 s = client_lookup_session (plugin, address);
1307 if (plugin->max_connections <= plugin->cur_connections)
1309 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, plugin->name,
1310 "Maximum number of connections (%u) reached: "
1311 "cannot connect to peer `%s'\n",
1312 plugin->max_connections,
1313 GNUNET_i2s (&address->peer));
1317 ats.type = htonl (GNUNET_ATS_NETWORK_TYPE);
1318 ats.value = htonl (GNUNET_ATS_NET_UNSPECIFIED);
1319 sa = http_common_socket_from_address (address->address, address->address_length, &res);
1321 if (GNUNET_SYSERR == res)
1325 else if (GNUNET_YES == res)
1327 GNUNET_assert (NULL != sa);
1328 if (AF_INET == sa->sa_family)
1330 salen = sizeof (struct sockaddr_in);
1332 else if (AF_INET == sa->sa_family)
1334 salen = sizeof (struct sockaddr_in6);
1336 ats = plugin->env->get_address_type (plugin->env->cls, sa, salen);
1339 else if (GNUNET_NO == res)
1341 ats.value = htonl (GNUNET_ATS_COST_WAN);
1344 if (GNUNET_ATS_NET_UNSPECIFIED == ntohl(ats.value))
1350 s = GNUNET_malloc (sizeof (struct Session));
1351 memcpy (&s->target, &address->peer, sizeof (struct GNUNET_PeerIdentity));
1353 s->addr = GNUNET_malloc (address->address_length);
1354 memcpy (s->addr, address->address, address->address_length);
1355 s->addrlen = address->address_length;
1356 s->ats_address_network_type = ats.value;
1357 s->put_paused = GNUNET_NO;
1358 s->put_tmp_disconnecting = GNUNET_NO;
1359 s->put_tmp_disconnected = GNUNET_NO;
1360 client_start_session_timeout (s);
1362 /* add new session */
1363 GNUNET_CONTAINER_DLL_insert (plugin->head, plugin->tail, s);
1365 /* initiate new connection */
1366 if (GNUNET_SYSERR == client_connect (s))
1368 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, plugin->name,
1369 "Cannot connect to peer `%s' address `%s''\n",
1370 http_common_plugin_address_to_string (NULL, s->addr, s->addrlen),
1371 GNUNET_i2s (&s->target));
1372 client_delete_session (s);
1380 * Setup http_client plugin
1382 * @param plugin the plugin handle
1383 * @return GNUNET_OK on success, GNUNET_SYSERR on error
1386 client_start (struct HTTP_Client_Plugin *plugin)
1388 curl_global_init (CURL_GLOBAL_ALL);
1389 plugin->curl_multi_handle = curl_multi_init ();
1391 if (NULL == plugin->curl_multi_handle)
1393 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, plugin->name,
1394 _("Could not initialize curl multi handle, failed to start %s plugin!\n"),
1396 return GNUNET_SYSERR;
1402 * Session was idle, so disconnect it
1405 client_session_timeout (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
1407 GNUNET_assert (NULL != cls);
1408 struct Session *s = cls;
1410 s->timeout_task = GNUNET_SCHEDULER_NO_TASK;
1411 GNUNET_log (TIMEOUT_LOG,
1412 "Session %p was idle for %llu ms, disconnecting\n",
1413 s, (unsigned long long) CLIENT_SESSION_TIMEOUT.rel_value);
1415 /* call session destroy function */
1416 GNUNET_assert (GNUNET_OK == client_disconnect (s));
1421 * Start session timeout for session s
1423 * @param s the session
1426 client_start_session_timeout (struct Session *s)
1429 GNUNET_assert (NULL != s);
1430 GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == s->timeout_task);
1431 s->timeout_task = GNUNET_SCHEDULER_add_delayed (CLIENT_SESSION_TIMEOUT,
1432 &client_session_timeout,
1434 GNUNET_log (TIMEOUT_LOG,
1435 "Timeout for session %p set to %llu ms\n",
1436 s, (unsigned long long) CLIENT_SESSION_TIMEOUT.rel_value);
1441 * Increment session timeout due to activity for session s
1443 * param s the session
1446 client_reschedule_session_timeout (struct Session *s)
1449 GNUNET_assert (NULL != s);
1450 GNUNET_assert (GNUNET_SCHEDULER_NO_TASK != s->timeout_task);
1452 GNUNET_SCHEDULER_cancel (s->timeout_task);
1453 s->timeout_task = GNUNET_SCHEDULER_add_delayed (CLIENT_SESSION_TIMEOUT,
1454 &client_session_timeout,
1456 GNUNET_log (TIMEOUT_LOG,
1457 "Timeout rescheduled for session %p set to %llu ms\n",
1458 s, (unsigned long long) CLIENT_SESSION_TIMEOUT.rel_value);
1463 * Cancel timeout due to activity for session s
1465 * param s the session
1468 client_stop_session_timeout (struct Session *s)
1470 GNUNET_assert (NULL != s);
1472 if (GNUNET_SCHEDULER_NO_TASK != s->timeout_task)
1474 GNUNET_SCHEDULER_cancel (s->timeout_task);
1475 s->timeout_task = GNUNET_SCHEDULER_NO_TASK;
1476 GNUNET_log (TIMEOUT_LOG, "Timeout stopped for session %p\n", s);
1482 * Another peer has suggested an address for this
1483 * peer and transport plugin. Check that this could be a valid
1484 * address. If so, consider adding it to the list
1487 * @param cls closure
1488 * @param addr pointer to the address
1489 * @param addrlen length of addr
1490 * @return GNUNET_OK if this is a plausible address for this peer
1494 http_client_plugin_address_suggested (void *cls, const void *addr, size_t addrlen)
1496 /* struct Plugin *plugin = cls; */
1498 /* A HTTP/S client does not have any valid address so:*/
1504 * Exit point from the plugin.
1506 * @param cls api as closure
1510 LIBGNUNET_PLUGIN_TRANSPORT_DONE (void *cls)
1512 struct GNUNET_TRANSPORT_PluginFunctions *api = cls;
1513 struct HTTP_Client_Plugin *plugin = api->cls;
1514 struct Session *pos;
1515 struct Session *next;
1517 if (NULL == api->cls)
1524 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1525 _("Shutting down plugin `%s'\n"),
1529 next = plugin->head;
1530 while (NULL != (pos = next))
1533 client_disconnect (pos);
1535 if (GNUNET_SCHEDULER_NO_TASK != plugin->client_perform_task)
1537 GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
1538 plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1542 if (NULL != plugin->curl_multi_handle)
1544 curl_multi_cleanup (plugin->curl_multi_handle);
1545 plugin->curl_multi_handle = NULL;
1547 curl_global_cleanup ();
1549 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1550 _("Shutdown for plugin `%s' complete\n"),
1553 GNUNET_free (plugin);
1562 * @param plugin the plugin handle
1563 * @return GNUNET_OK on success, GNUNET_SYSERR on failure
1566 client_configure_plugin (struct HTTP_Client_Plugin *plugin)
1568 unsigned long long max_connections;
1570 /* Optional parameters */
1571 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (plugin->env->cfg,
1573 "MAX_CONNECTIONS", &max_connections))
1574 max_connections = 128;
1575 plugin->max_connections = max_connections;
1577 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, plugin->name,
1578 _("Maximum number of connections is %u\n"),
1579 plugin->max_connections);
1584 * Entry point for the plugin.
1587 LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls)
1589 struct GNUNET_TRANSPORT_PluginEnvironment *env = cls;
1590 struct GNUNET_TRANSPORT_PluginFunctions *api;
1591 struct HTTP_Client_Plugin *plugin;
1593 if (NULL == env->receive)
1595 /* run in 'stub' mode (i.e. as part of gnunet-peerinfo), don't fully
1596 initialze the plugin or the API */
1597 api = GNUNET_malloc (sizeof (struct GNUNET_TRANSPORT_PluginFunctions));
1599 api->address_to_string = &http_common_plugin_address_to_string;
1600 api->string_to_address = &http_common_plugin_string_to_address;
1601 api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
1605 plugin = GNUNET_malloc (sizeof (struct HTTP_Client_Plugin));
1608 api = GNUNET_malloc (sizeof (struct GNUNET_TRANSPORT_PluginFunctions));
1610 api->send = &http_client_plugin_send;
1611 api->disconnect = &http_client_plugin_disconnect;
1612 api->check_address = &http_client_plugin_address_suggested;
1613 api->get_session = &http_client_plugin_get_session;
1614 api->address_to_string = &http_common_plugin_address_to_string;
1615 api->string_to_address = &http_common_plugin_string_to_address;
1616 api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
1620 plugin->name = "transport-https_client";
1621 plugin->protocol = "https";
1623 plugin->name = "transport-http_client";
1624 plugin->protocol = "http";
1626 plugin->last_tag = 1;
1628 if (GNUNET_SYSERR == client_configure_plugin (plugin))
1630 LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
1635 if (GNUNET_SYSERR == client_start (plugin))
1637 LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
1643 /* end of plugin_transport_http_client.c */