From 0a217a8df1657b4334b55b0e4a6c7837a8dbcfd9 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 29 May 2009 00:46:26 +0000 Subject: ng --- src/transport/Makefile.am | 84 + src/transport/NOTES | 46 + src/transport/gnunet-service-transport.c | 2852 +++++++++++++++++++++++++++ src/transport/gnunet-transport.c | 42 + src/transport/plugin_transport.h | 468 +++++ src/transport/plugin_transport_http.c | 2085 ++++++++++++++++++++ src/transport/plugin_transport_smtp.c | 906 +++++++++ src/transport/plugin_transport_tcp.c | 1782 +++++++++++++++++ src/transport/plugin_transport_template.c | 335 ++++ src/transport/plugin_transport_udp.c | 592 ++++++ src/transport/test_transport_api.c | 305 +++ src/transport/test_transport_api_data.conf | 24 + src/transport/test_transport_api_peer1.conf | 25 + src/transport/test_transport_api_peer2.conf | 25 + src/transport/transport.h | 238 +++ src/transport/transport_api.c | 1863 +++++++++++++++++ 16 files changed, 11672 insertions(+) create mode 100644 src/transport/Makefile.am create mode 100644 src/transport/NOTES create mode 100644 src/transport/gnunet-service-transport.c create mode 100644 src/transport/gnunet-transport.c create mode 100644 src/transport/plugin_transport.h create mode 100644 src/transport/plugin_transport_http.c create mode 100644 src/transport/plugin_transport_smtp.c create mode 100644 src/transport/plugin_transport_tcp.c create mode 100644 src/transport/plugin_transport_template.c create mode 100644 src/transport/plugin_transport_udp.c create mode 100644 src/transport/test_transport_api.c create mode 100644 src/transport/test_transport_api_data.conf create mode 100644 src/transport/test_transport_api_peer1.conf create mode 100644 src/transport/test_transport_api_peer2.conf create mode 100644 src/transport/transport.h create mode 100644 src/transport/transport_api.c (limited to 'src/transport') diff --git a/src/transport/Makefile.am b/src/transport/Makefile.am new file mode 100644 index 000000000..236dec7c4 --- /dev/null +++ b/src/transport/Makefile.am @@ -0,0 +1,84 @@ +INCLUDES = -I$(top_srcdir)/src/include + +plugindir = $(libdir)/gnunet + +if MINGW + WINFLAGS = -Wl,--no-undefined -Wl,--export-all-symbols +endif + +if USE_COVERAGE + AM_CFLAGS = -fprofile-arcs -ftest-coverage +endif + + +lib_LTLIBRARIES = \ + libgnunettransport.la + +libgnunettransport_la_SOURCES = \ + transport_api.c transport.h +libgnunettransport_la_LIBADD = \ + $(top_builddir)/src/arm/libgnunetarm.la \ + $(top_builddir)/src/hello/libgnunethello.la \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(GN_LIBINTL) +libgnunettransport_la_LDFLAGS = \ + $(GN_LIB_LDFLAGS) $(WINFLAGS) \ + -version-info 0:0:0 + + +bin_PROGRAMS = \ + gnunet-transport \ + gnunet-service-transport + +gnunet_transport_SOURCES = \ + gnunet-transport.c +gnunet_transport_LDADD = \ + $(top_builddir)/src/transport/libgnunettransport.la \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(GN_LIBINTL) + +gnunet_service_transport_SOURCES = \ + gnunet-service-transport.c +gnunet_service_transport_LDADD = \ + $(top_builddir)/src/peerinfo/libgnunetpeerinfo.la \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(GN_LIBINTL) + + + +plugin_LTLIBRARIES = \ + libgnunet_plugin_transport_tcp.la \ + libgnunet_plugin_transport_template.la +# TODO: add udp, http, nat, etc. + +libgnunet_plugin_transport_tcp_la_SOURCES = \ + plugin_transport_tcp.c +libgnunet_plugin_transport_tcp_la_LIBADD = \ + $(top_builddir)/src/resolver/libgnunetresolver.la \ + $(top_builddir)/src/util/libgnunetutil.la +libgnunet_plugin_transport_tcp_la_LDFLAGS = \ + $(GN_PLUGIN_LDFLAGS) + +libgnunet_plugin_transport_template_la_SOURCES = \ + plugin_transport_template.c +libgnunet_plugin_transport_template_la_LDFLAGS = \ + $(GN_PLUGIN_LDFLAGS) + + +check_PROGRAMS = \ + test_transport_api +# TODO: add tests for tcp, udp, http, nat, etc. + +TESTS = $(check_PROGRAMS) + +test_transport_api_SOURCES = \ + test_transport_api.c +test_transport_api_LDADD = \ + $(top_builddir)/src/transport/libgnunettransport.la \ + $(top_builddir)/src/util/libgnunetutil.la + + +EXTRA_DIST = \ + test_transport_api_data.conf \ + test_transport_api_peer1.conf \ + test_transport_api_peer2.conf diff --git a/src/transport/NOTES b/src/transport/NOTES new file mode 100644 index 000000000..41404e1f9 --- /dev/null +++ b/src/transport/NOTES @@ -0,0 +1,46 @@ +KEY DESIGN CHOICES: + - who decides which connections to keep/create? + => higher level session/key management! + - who enforces things like F2F topology, etc? + => higher level session/key management! + - who tracks all known HELLOs & validates? + => We validate, PEERINFO tracks! + - who advertises our HELLO? + => us! (need background job; previously: advertising) + - who advertises other peers HELLOs? + => higher level (core?) + - who does bootstrapping? + => bootstrap service (external!) + - who enforces inbound bandwidth limits? + => transport-service and plugins! (previously: core); + either by limiting reads (TCP) or discarding packets + (transport-service) + - who enforces outbound bandwidth limits? + => transport_api! + - who decides outbound bandwidth limits? + => other peer, via core (need authenticated limits!) + - who decides inbound bandwidth limits? + => core / apps above core (need trust info) + - cost function for transports is latency estimate in ms + => plugin provides latency data, transport-service + selects plugin(s) for transmission + - who is responsible for fragmentation? + => plugins! (may use common shared library) + - should we require UDP to be reliable? + => NO. There are other places that may (rarely) + use messages that we can not fix + - how do we access the 50% of service that we need for TCP/UDP + from service.c without code replication or getting 50% + that we do not want (i.e. shutdown, pid-file-writing, etc.) + => use GNUNET_SERVICE_start/stop functions! + - At what level do we manage timeouts? + => At the plugin (TCP connections), + transport-service (neighbours) and + core (sessions) level! + => All plugins have to disconnect before service-level + disconnect occurs + => We can have a plugin-connection die, but the session + survives! + => We can have a session die (no further authenticated + communication) even if the plugin thinks it is still + up! diff --git a/src/transport/gnunet-service-transport.c b/src/transport/gnunet-service-transport.c new file mode 100644 index 000000000..08745c378 --- /dev/null +++ b/src/transport/gnunet-service-transport.c @@ -0,0 +1,2852 @@ +/* + This file is part of GNUnet. + (C) 2009 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file transport/gnunet-service-transport.c + * @brief low-level P2P messaging + * @author Christian Grothoff + * + * TODO: + * - if we do not receive an ACK in response to our + * HELLO, retransmit HELLO! + */ +#include "platform.h" +#include "gnunet_client_lib.h" +#include "gnunet_getopt_lib.h" +#include "gnunet_hello_lib.h" +#include "gnunet_os_lib.h" +#include "gnunet_peerinfo_service.h" +#include "gnunet_plugin_lib.h" +#include "gnunet_protocols.h" +#include "gnunet_service_lib.h" +#include "gnunet_signatures.h" +#include "plugin_transport.h" +#include "transport.h" + +/** + * How many messages can we have pending for a given client process + * before we start to drop incoming messages? We typically should + * have only one client and so this would be the primary buffer for + * messages, so the number should be chosen rather generously. + * + * The expectation here is that most of the time the queue is large + * enough so that a drop is virtually never required. + */ +#define MAX_PENDING 128 + +/** + * How often should we try to reconnect to a peer using a particular + * transport plugin before giving up? Note that the plugin may be + * added back to the list after PLUGIN_RETRY_FREQUENCY expires. + */ +#define MAX_CONNECT_RETRY 3 + +/** + * How often must a peer violate bandwidth quotas before we start + * to simply drop its messages? + */ +#define QUOTA_VIOLATION_DROP_THRESHOLD 100 + +/** + * How long until a HELLO verification attempt should time out? + */ +#define HELLO_VERIFICATION_TIMEOUT GNUNET_TIME_UNIT_MINUTES + +/** + * How often do we re-add (cheaper) plugins to our list of plugins + * to try for a given connected peer? + */ +#define PLUGIN_RETRY_FREQUENCY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15) + +/** + * After how long do we expire an address in a HELLO + * that we just validated? This value is also used + * for our own addresses when we create a HELLO. + */ +#define HELLO_ADDRESS_EXPIRATION GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS, 12) + +/** + * After how long do we consider a connection to a peer dead + * if we don't receive messages from the peer? + */ +#define IDLE_CONNECTION_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 5) + + +/** + * Entry in linked list of network addresses. + */ +struct AddressList +{ + /** + * This is a linked list. + */ + struct AddressList *next; + + /** + * The address, actually a pointer to the end + * of this struct. Do not free! + */ + void *addr; + + /** + * How long until we auto-expire this address (unless it is + * re-confirmed by the transport)? + */ + struct GNUNET_TIME_Absolute expires; + + /** + * Length of addr. + */ + size_t addrlen; + +}; + + +/** + * Entry in linked list of all of our plugins. + */ +struct TransportPlugin +{ + + /** + * This is a linked list. + */ + struct TransportPlugin *next; + + /** + * API of the transport as returned by the plugin's + * initialization function. + */ + struct GNUNET_TRANSPORT_PluginFunctions *api; + + /** + * Short name for the plugin (i.e. "tcp"). + */ + char *short_name; + + /** + * Name of the library (i.e. "gnunet_plugin_transport_tcp"). + */ + char *lib_name; + + /** + * List of our known addresses for this transport. + */ + struct AddressList *addresses; + + /** + * Environment this transport service is using + * for this plugin. + */ + struct GNUNET_TRANSPORT_PluginEnvironment env; + + /** + * ID of task that is used to clean up expired addresses. + */ + GNUNET_SCHEDULER_TaskIdentifier address_update_task; + + + /** + * Set to GNUNET_YES if we need to scrap the existing + * list of "addresses" and start fresh when we receive + * the next address update from a transport. Set to + * GNUNET_NO if we should just add the new address + * to the list and wait for the commit call. + */ + int rebuild; +}; + +struct NeighbourList; + +/** + * For each neighbour we keep a list of messages + * that we still want to transmit to the neighbour. + */ +struct MessageQueue +{ + + /** + * This is a linked list. + */ + struct MessageQueue *next; + + /** + * The message we want to transmit. + */ + struct GNUNET_MessageHeader *message; + + /** + * Client responsible for queueing the message; + * used to check that a client has not two messages + * pending for the same target. Can be NULL. + */ + struct TransportClient *client; + + /** + * Neighbour this entry belongs to. + */ + struct NeighbourList *neighbour; + + /** + * Plugin that we used for the transmission. + * NULL until we scheduled a transmission. + */ + struct TransportPlugin *plugin; + + /** + * Internal message of the transport system that should not be + * included in the usual SEND-SEND_OK transmission confirmation + * traffic management scheme. Typically, "internal_msg" will + * be set whenever "client" is NULL (but it is not strictly + * required). + */ + int internal_msg; + +}; + + +/** + * For a given Neighbour, which plugins are available + * to talk to this peer and what are their costs? + */ +struct ReadyList +{ + + /** + * This is a linked list. + */ + struct ReadyList *next; + + /** + * Which of our transport plugins does this entry + * represent? + */ + struct TransportPlugin *plugin; + + /** + * Neighbour this entry belongs to. + */ + struct NeighbourList *neighbour; + + /** + * Opaque handle (specific to the plugin) for the + * connection to our target; can be NULL. + */ + void *plugin_handle; + + /** + * What was the last latency observed for this plugin + * and peer? Invalid if connected is GNUNET_NO. + */ + struct GNUNET_TIME_Relative latency; + + /** + * If we did not successfully transmit a message to the + * given peer via this connection during the specified + * time, we should consider the connection to be dead. + * This is used in the case that a TCP transport simply + * stalls writing to the stream but does not formerly + * get a signal that the other peer died. + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * Is this plugin currently connected? The first time + * we transmit or send data to a peer via a particular + * plugin, we set this to GNUNET_YES. If we later get + * an error (disconnect notification or transmission + * failure), we set it back to GNUNET_NO. Each time the + * value is set to GNUNET_YES, we increment the + * "connect_attempts" counter. If that one reaches a + * particular threshold, we consider the plugin to not + * be working properly at this time for the given peer + * and remove it from the eligible list. + */ + int connected; + + /** + * How often have we tried to connect using this plugin? + */ + unsigned int connect_attempts; + + /** + * Is this plugin ready to transmit to the specific + * target? GNUNET_NO if not. Initially, all plugins + * are marked ready. If a transmission is in progress, + * "transmit_ready" is set to GNUNET_NO. + */ + int transmit_ready; + +}; + + +/** + * Entry in linked list of all of our current neighbours. + */ +struct NeighbourList +{ + + /** + * This is a linked list. + */ + struct NeighbourList *next; + + /** + * Which of our transports is connected to this peer + * and what is their status? + */ + struct ReadyList *plugins; + + /** + * List of messages we would like to send to this peer; + * must contain at most one message per client. + */ + struct MessageQueue *messages; + + /** + * Identity of this neighbour. + */ + struct GNUNET_PeerIdentity id; + + /** + * ID of task scheduled to run when this peer is about to + * time out (will free resources associated with the peer). + */ + GNUNET_SCHEDULER_TaskIdentifier timeout_task; + + /** + * How long until we should consider this peer dead + * (if we don't receive another message in the + * meantime)? + */ + struct GNUNET_TIME_Absolute peer_timeout; + + /** + * At what time did we reset last_received last? + */ + struct GNUNET_TIME_Absolute last_quota_update; + + /** + * At what time should we try to again add plugins to + * our ready list? + */ + struct GNUNET_TIME_Absolute retry_plugins_time; + + /** + * How many bytes have we received since the "last_quota_update" + * timestamp? + */ + uint64_t last_received; + + /** + * Global quota for outbound traffic for the neighbour in bytes/ms. + */ + uint32_t quota_in; + + /** + * What is the latest version of our HELLO that we have + * sent to this neighbour? + */ + unsigned int hello_version_sent; + + /** + * How often has the other peer (recently) violated the + * inbound traffic limit? Incremented by 10 per violation, + * decremented by 1 per non-violation (for each + * time interval). + */ + unsigned int quota_violation_count; + + /** + * Have we seen an ACK from this neighbour in the past? + * (used to make up a fake ACK for clients connecting after + * the neighbour connected to us). + */ + int saw_ack; + +}; + + +/** + * Linked list of messages to be transmitted to + * the client. Each entry is followed by the + * actual message. + */ +struct ClientMessageQueueEntry +{ + /** + * This is a linked list. + */ + struct ClientMessageQueueEntry *next; +}; + + +/** + * Client connected to the transport service. + */ +struct TransportClient +{ + + /** + * This is a linked list. + */ + struct TransportClient *next; + + /** + * Handle to the client. + */ + struct GNUNET_SERVER_Client *client; + + /** + * Linked list of messages yet to be transmitted to + * the client. + */ + struct ClientMessageQueueEntry *message_queue_head; + + /** + * Tail of linked list of messages yet to be transmitted to the + * client. + */ + struct ClientMessageQueueEntry *message_queue_tail; + + /** + * Is a call to "transmit_send_continuation" pending? If so, we + * must not free this struct (even if the corresponding client + * disconnects) and instead only remove it from the linked list and + * set the "client" field to NULL. + */ + int tcs_pending; + + /** + * Length of the list of messages pending for this client. + */ + unsigned int message_count; + +}; + + +/** + * Message used to ask a peer to validate receipt (to check an address + * from a HELLO). Followed by the address used. Note that the + * recipients response does not affirm that he has this address, + * only that he got the challenge message. + */ +struct ValidationChallengeMessage +{ + + /** + * Type will be GNUNET_MESSAGE_TYPE_TRANSPORT_PING + */ + struct GNUNET_MessageHeader header; + + /** + * What are we signing and why? + */ + struct GNUNET_CRYPTO_RsaSignaturePurpose purpose; + + /** + * Random challenge number (in network byte order). + */ + uint32_t challenge GNUNET_PACKED; + + /** + * Who is the intended recipient? + */ + struct GNUNET_PeerIdentity target; +}; + + +/** + * Message used to validate a HELLO. If this was + * the right recipient, the response is a signature + * of the original validation request. The + * challenge is included in the confirmation to make + * matching of replies to requests possible. + */ +struct ValidationChallengeResponse +{ + + /** + * Type will be GNUNET_MESSAGE_TYPE_TRANSPORT_PONG + */ + struct GNUNET_MessageHeader header; + + /** + * Random challenge number (in network byte order). + */ + uint32_t challenge GNUNET_PACKED; + + /** + * Who signed this message? + */ + struct GNUNET_PeerIdentity sender; + + /** + * Signature. + */ + struct GNUNET_CRYPTO_RsaSignature signature; + +}; + + +/** + * For each HELLO, we may have to validate multiple addresses; + * each address gets its own request entry. + */ +struct ValidationAddress +{ + /** + * This is a linked list. + */ + struct ValidationAddress *next; + + /** + * Our challenge message. Points to after this + * struct, so this field should not be freed. + */ + struct ValidationChallengeMessage *msg; + + /** + * Name of the transport. + */ + char *transport_name; + + /** + * When should this validated address expire? + */ + struct GNUNET_TIME_Absolute expiration; + + /** + * Length of the address we are validating. + */ + size_t addr_len; + + /** + * Set to GNUNET_YES if the challenge was met, + * GNUNET_SYSERR if we know it failed, GNUNET_NO + * if we are waiting on a response. + */ + int ok; +}; + + +/** + * Entry in linked list of all HELLOs awaiting validation. + */ +struct ValidationList +{ + + /** + * This is a linked list. + */ + struct ValidationList *next; + + /** + * Linked list with one entry per address from the HELLO + * that needs to be validated. + */ + struct ValidationAddress *addresses; + + /** + * The public key of the peer. + */ + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded publicKey; + + /** + * When does this record time-out? (assuming the + * challenge goes unanswered) + */ + struct GNUNET_TIME_Absolute timeout; + +}; + + +/** + * HELLOs awaiting validation. + */ +static struct ValidationList *pending_validations; + +/** + * Our HELLO message. + */ +static struct GNUNET_HELLO_Message *our_hello; + +/** + * "version" of "our_hello". Used to see if a given + * neighbour has already been sent the latest version + * of our HELLO message. + */ +static unsigned int our_hello_version; + +/** + * Our public key. + */ +static struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded my_public_key; + +/** + * Our identity. + */ +static struct GNUNET_PeerIdentity my_identity; + +/** + * Our private key. + */ +static struct GNUNET_CRYPTO_RsaPrivateKey *my_private_key; + +/** + * Our scheduler. + */ +struct GNUNET_SCHEDULER_Handle *sched; + +/** + * Our configuration. + */ +struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Linked list of all clients to this service. + */ +static struct TransportClient *clients; + +/** + * All loaded plugins. + */ +static struct TransportPlugin *plugins; + +/** + * Our server. + */ +static struct GNUNET_SERVER_Handle *server; + +/** + * All known neighbours and their HELLOs. + */ +static struct NeighbourList *neighbours; + +/** + * Default bandwidth quota for receiving for new peers in bytes/ms. + */ +static uint32_t default_quota_in; + +/** + * Default bandwidth quota for sending for new peers in bytes/ms. + */ +static uint32_t default_quota_out; + +/** + * Number of neighbours we'd like to have. + */ +static uint32_t max_connect_per_transport; + + +/** + * Find an entry in the neighbour list for a particular peer. + * + * @return NULL if not found. + */ +static struct NeighbourList * +find_neighbour (const struct GNUNET_PeerIdentity *key) +{ + struct NeighbourList *head = neighbours; + while ((head != NULL) && + (0 != memcmp (key, &head->id, sizeof (struct GNUNET_PeerIdentity)))) + head = head->next; + return head; +} + + +/** + * Find an entry in the transport list for a particular transport. + * + * @return NULL if not found. + */ +static struct TransportPlugin * +find_transport (const char *short_name) +{ + struct TransportPlugin *head = plugins; + while ((head != NULL) && (0 != strcmp (short_name, head->short_name))) + head = head->next; + return head; +} + + +/** + * Update the quota values for the given neighbour now. + */ +static void +update_quota (struct NeighbourList *n) +{ + struct GNUNET_TIME_Relative delta; + uint64_t allowed; + uint64_t remaining; + + delta = GNUNET_TIME_absolute_get_duration (n->last_quota_update); + if (delta.value < MIN_QUOTA_REFRESH_TIME) + return; /* not enough time passed for doing quota update */ + allowed = delta.value * n->quota_in; + if (n->last_received < allowed) + { + remaining = allowed - n->last_received; + if (n->quota_in > 0) + remaining /= n->quota_in; + else + remaining = 0; + if (remaining > MAX_BANDWIDTH_CARRY) + remaining = MAX_BANDWIDTH_CARRY; + n->last_received = 0; + n->last_quota_update = GNUNET_TIME_absolute_get (); + n->last_quota_update.value -= remaining; + if (n->quota_violation_count > 0) + n->quota_violation_count--; + } + else + { + n->last_received -= allowed; + n->last_quota_update = GNUNET_TIME_absolute_get (); + if (n->last_received > allowed) + { + /* more than twice the allowed rate! */ + n->quota_violation_count += 10; + } + } +} + + +/** + * Function called to notify a client about the socket + * being ready to queue more data. "buf" will be + * NULL and "size" zero if the socket was closed for + * writing in the meantime. + * + * @param cls closure + * @param size number of bytes available in buf + * @param buf where the callee should write the message + * @return number of bytes written to buf + */ +static size_t +transmit_to_client_callback (void *cls, size_t size, void *buf) +{ + struct TransportClient *client = cls; + struct ClientMessageQueueEntry *q; + uint16_t msize; + size_t tsize; + const struct GNUNET_MessageHeader *msg; + struct GNUNET_NETWORK_TransmitHandle *th; + char *cbuf; + + if (buf == NULL) + { + /* fatal error with client, free message queue! */ + while (NULL != (q = client->message_queue_head)) + { + client->message_queue_head = q->next; + GNUNET_free (q); + } + client->message_queue_tail = NULL; + client->message_count = 0; + return 0; + } + cbuf = buf; + tsize = 0; + while (NULL != (q = client->message_queue_head)) + { + msg = (const struct GNUNET_MessageHeader *) &q[1]; + msize = ntohs (msg->size); + if (msize + tsize > size) + break; + client->message_queue_head = q->next; + if (q->next == NULL) + client->message_queue_tail = NULL; + memcpy (&cbuf[tsize], msg, msize); + tsize += msize; + GNUNET_free (q); + client->message_count--; + } + GNUNET_assert (tsize > 0); + if (NULL != q) + { + th = GNUNET_SERVER_notify_transmit_ready (client->client, + msize, + GNUNET_TIME_UNIT_FOREVER_REL, + &transmit_to_client_callback, + client); + GNUNET_assert (th != NULL); + } + return tsize; +} + + +/** + * Send the specified message to the specified client. Since multiple + * messages may be pending for the same client at a time, this code + * makes sure that no message is lost. + * + * @param client client to transmit the message to + * @param msg the message to send + * @param may_drop can this message be dropped if the + * message queue for this client is getting far too large? + */ +static void +transmit_to_client (struct TransportClient *client, + const struct GNUNET_MessageHeader *msg, int may_drop) +{ + struct ClientMessageQueueEntry *q; + uint16_t msize; + struct GNUNET_NETWORK_TransmitHandle *th; + + if ((client->message_count >= MAX_PENDING) && (GNUNET_YES == may_drop)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _ + ("Dropping message, have %u messages pending (%u is the soft limit)\n"), + client->message_count, MAX_PENDING); + /* TODO: call to statistics... */ + return; + } + client->message_count++; + msize = ntohs (msg->size); + q = GNUNET_malloc (sizeof (struct ClientMessageQueueEntry) + msize); + memcpy (&q[1], msg, msize); + /* append to message queue */ + if (client->message_queue_tail == NULL) + { + client->message_queue_tail = q; + } + else + { + client->message_queue_tail->next = q; + client->message_queue_tail = q; + } + if (client->message_queue_head == NULL) + { + client->message_queue_head = q; + th = GNUNET_SERVER_notify_transmit_ready (client->client, + msize, + GNUNET_TIME_UNIT_FOREVER_REL, + &transmit_to_client_callback, + client); + GNUNET_assert (th != NULL); + } +} + + +/** + * Find alternative plugins for communication. + * + * @param neighbour for which neighbour should we try to find + * more plugins? + */ +static void +try_alternative_plugins (struct NeighbourList *neighbour) +{ + struct ReadyList *rl; + + if ((neighbour->plugins != NULL) && + (neighbour->retry_plugins_time.value > + GNUNET_TIME_absolute_get ().value)) + return; /* don't try right now */ + neighbour->retry_plugins_time + = GNUNET_TIME_relative_to_absolute (PLUGIN_RETRY_FREQUENCY); + + rl = neighbour->plugins; + while (rl != NULL) + { + if (rl->connect_attempts > 0) + rl->connect_attempts--; /* amnesty */ + rl = rl->next; + } + +} + + +/** + * Check the ready list for the given neighbour and + * if a plugin is ready for transmission (and if we + * have a message), do so! + * + * @param neighbour target peer for which to check the plugins + */ +static void try_transmission_to_peer (struct NeighbourList *neighbour); + + +/** + * Function called by the GNUNET_TRANSPORT_TransmitFunction + * upon "completion" of a send request. This tells the API + * that it is now legal to send another message to the given + * peer. + * + * @param cls closure, identifies the entry on the + * message queue that was transmitted and the + * client responsible for queueing the message + * @param rl identifies plugin used for the transmission for + * this neighbour; needs to be re-enabled for + * future transmissions + * @param target the peer receiving the message + * @param result GNUNET_OK on success, if the transmission + * failed, we should not tell the client to transmit + * more messages + */ +static void +transmit_send_continuation (void *cls, + struct ReadyList *rl, + const struct GNUNET_PeerIdentity *target, + int result) +{ + struct MessageQueue *mq = cls; + struct SendOkMessage send_ok_msg; + struct NeighbourList *n; + + GNUNET_assert (mq != NULL); + n = mq->neighbour; + GNUNET_assert (0 == + memcmp (&n->id, target, + sizeof (struct GNUNET_PeerIdentity))); + if (rl == NULL) + { + rl = n->plugins; + while ((rl != NULL) && (rl->plugin != mq->plugin)) + rl = rl->next; + GNUNET_assert (rl != NULL); + } + if (result == GNUNET_OK) + rl->timeout = GNUNET_TIME_relative_to_absolute (IDLE_CONNECTION_TIMEOUT); + else + rl->connected = GNUNET_NO; + if (!mq->internal_msg) + rl->transmit_ready = GNUNET_YES; + if (mq->client != NULL) + { + send_ok_msg.header.size = htons (sizeof (send_ok_msg)); + send_ok_msg.header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_SEND_OK); + send_ok_msg.success = htonl (result); + send_ok_msg.peer = n->id; + transmit_to_client (mq->client, &send_ok_msg.header, GNUNET_NO); + } + GNUNET_free (mq->message); + GNUNET_free (mq); + /* one plugin just became ready again, try transmitting + another message (if available) */ + try_transmission_to_peer (n); +} + + + + +/** + * We could not use an existing (or validated) connection to + * talk to a peer. Try addresses that have not yet been + * validated. + * + * @param n neighbour we want to communicate with + * @return plugin ready to talk, or NULL if none is available + */ +static struct ReadyList * +try_unvalidated_addresses (struct NeighbourList *n) +{ + struct ValidationList *vl; + struct ValidationAddress *va; + struct GNUNET_PeerIdentity id; + struct GNUNET_TIME_Absolute now; + unsigned int total; + unsigned int cnt; + struct ReadyList *rl; + struct TransportPlugin *plugin; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Trying to connect to `%4s' using unvalidated addresses\n", + GNUNET_i2s (&n->id)); +#endif + /* NOTE: this function needs to not only identify the + plugin but also setup "plugin_handle", binding it to the + right address using the plugin's "send_to" API */ + now = GNUNET_TIME_absolute_get (); + vl = pending_validations; + while (vl != NULL) + { + GNUNET_CRYPTO_hash (&vl->publicKey, + sizeof (struct + GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &id.hashPubKey); + if (0 == memcmp (&id, &n->id, sizeof (struct GNUNET_PeerIdentity))) + break; + vl = vl->next; + } + if (vl == NULL) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "No unvalidated address found for peer `%4s'\n", + GNUNET_i2s (&n->id)); +#endif + return NULL; + } + total = 0; + cnt = 0; + va = vl->addresses; + while (va != NULL) + { + cnt++; + if (va->expiration.value > now.value) + total++; + va = va->next; + } + if (total == 0) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "All %u unvalidated addresses for peer have expired\n", + cnt); +#endif + return NULL; + } + total = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, total); + for (va = vl->addresses; va != NULL; va = va->next) + { + if (va->expiration.value <= now.value) + continue; + if (total > 0) + { + total--; + continue; + } +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK, + "Trying unvalidated address of `%s' transport\n", + va->transport_name); +#endif + plugin = find_transport (va->transport_name); + if (plugin == NULL) + { + GNUNET_break (0); + break; + } + rl = GNUNET_malloc (sizeof (struct ReadyList)); + rl->next = n->plugins; + n->plugins = rl; + rl->plugin = plugin; + rl->plugin_handle = plugin->api->send_to (plugin->api->cls, + &n->id, + NULL, + NULL, + GNUNET_TIME_UNIT_ZERO, + &va->msg[1], va->addr_len); + rl->transmit_ready = GNUNET_YES; + return rl; + } + return NULL; +} + + +/** + * Check the ready list for the given neighbour and + * if a plugin is ready for transmission (and if we + * have a message), do so! + */ +static void +try_transmission_to_peer (struct NeighbourList *neighbour) +{ + struct ReadyList *pos; + struct GNUNET_TIME_Relative min_latency; + struct ReadyList *rl; + struct MessageQueue *mq; + struct GNUNET_TIME_Absolute now; + + if (neighbour->messages == NULL) + return; /* nothing to do */ + try_alternative_plugins (neighbour); + min_latency = GNUNET_TIME_UNIT_FOREVER_REL; + rl = NULL; + mq = neighbour->messages; + now = GNUNET_TIME_absolute_get (); + pos = neighbour->plugins; + while (pos != NULL) + { + /* set plugins that are inactive for a long time back to disconnected */ + if ((pos->timeout.value < now.value) && (pos->connected == GNUNET_YES)) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Marking long-time inactive connection to `%4s' as down.\n", + GNUNET_i2s (&neighbour->id)); +#endif + pos->connected = GNUNET_NO; + } + if (((GNUNET_YES == pos->transmit_ready) || + (mq->internal_msg)) && + (pos->connect_attempts < MAX_CONNECT_RETRY) && + ((rl == NULL) || (min_latency.value > pos->latency.value))) + { + rl = pos; + min_latency = pos->latency; + } + pos = pos->next; + } + if (rl == NULL) + rl = try_unvalidated_addresses (neighbour); + if (rl == NULL) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "No plugin ready to transmit message\n"); +#endif + return; /* nobody ready */ + } + if (GNUNET_NO == rl->connected) + { + rl->connect_attempts++; + rl->connected = GNUNET_YES; + } + neighbour->messages = mq->next; + mq->plugin = rl->plugin; + if (!mq->internal_msg) + rl->transmit_ready = GNUNET_NO; +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Giving message of type `%u' for `%4s' to plugin `%s'\n", + ntohs (mq->message->type), + GNUNET_i2s (&neighbour->id), rl->plugin->short_name); +#endif + rl->plugin_handle + = rl->plugin->api->send (rl->plugin->api->cls, + rl->plugin_handle, + rl, + &neighbour->id, + mq->message, + IDLE_CONNECTION_TIMEOUT, + &transmit_send_continuation, mq); +} + + +/** + * Send the specified message to the specified peer. + * + * @param client source of the transmission request (can be NULL) + * @param msg message to send + * @param is_internal is this an internal message + * @param neighbour handle to the neighbour for transmission + */ +static void +transmit_to_peer (struct TransportClient *client, + const struct GNUNET_MessageHeader *msg, + int is_internal, struct NeighbourList *neighbour) +{ + struct MessageQueue *mq; + struct MessageQueue *mqe; + struct GNUNET_MessageHeader *m; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + _("Sending message of type %u to peer `%4s'\n"), + ntohs (msg->type), GNUNET_i2s (&neighbour->id)); +#endif + if (client != NULL) + { + /* check for duplicate submission */ + mq = neighbour->messages; + while (NULL != mq) + { + if (mq->client == client) + { + /* client transmitted to same peer twice + before getting SendOk! */ + GNUNET_break (0); + return; + } + mq = mq->next; + } + } + mq = GNUNET_malloc (sizeof (struct MessageQueue)); + mq->client = client; + m = GNUNET_malloc (ntohs (msg->size)); + memcpy (m, msg, ntohs (msg->size)); + mq->message = m; + mq->neighbour = neighbour; + mq->internal_msg = is_internal; + + /* find tail */ + mqe = neighbour->messages; + if (mqe != NULL) + while (mqe->next != NULL) + mqe = mqe->next; + if (mqe == NULL) + { + /* new head */ + neighbour->messages = mq; + try_transmission_to_peer (neighbour); + } + else + { + /* append */ + mqe->next = mq; + } +} + + +struct GeneratorContext +{ + struct TransportPlugin *plug_pos; + struct AddressList *addr_pos; + struct GNUNET_TIME_Absolute expiration; +}; + + +static size_t +address_generator (void *cls, size_t max, void *buf) +{ + struct GeneratorContext *gc = cls; + size_t ret; + + while ((gc->addr_pos == NULL) && (gc->plug_pos != NULL)) + { + gc->plug_pos = gc->plug_pos->next; + gc->addr_pos = (gc->plug_pos != NULL) ? gc->plug_pos->addresses : NULL; + } + if (NULL == gc->plug_pos) + return 0; + ret = GNUNET_HELLO_add_address (gc->plug_pos->short_name, + gc->expiration, + gc->addr_pos->addr, + gc->addr_pos->addrlen, buf, max); + gc->addr_pos = gc->addr_pos->next; + return ret; +} + + +/** + * Construct our HELLO message from all of the addresses of + * all of the transports. + */ +static void +refresh_hello () +{ + struct GNUNET_HELLO_Message *hello; + struct TransportClient *cpos; + struct NeighbourList *npos; + struct GeneratorContext gc; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK, + "Refreshing my HELLO\n"); +#endif + gc.plug_pos = plugins; + gc.addr_pos = plugins != NULL ? plugins->addresses : NULL; + gc.expiration = GNUNET_TIME_relative_to_absolute (HELLO_ADDRESS_EXPIRATION); + hello = GNUNET_HELLO_create (&my_public_key, &address_generator, &gc); + cpos = clients; + while (cpos != NULL) + { + transmit_to_client (cpos, + (const struct GNUNET_MessageHeader *) hello, + GNUNET_NO); + cpos = cpos->next; + } + + GNUNET_free_non_null (our_hello); + our_hello = hello; + our_hello_version++; + npos = neighbours; + while (npos != NULL) + { + transmit_to_peer (NULL, + (const struct GNUNET_MessageHeader *) our_hello, + GNUNET_YES, npos); + npos = npos->next; + } +} + + +/** + * Task used to clean up expired addresses for a plugin. + * + * @param cls closure + * @param tc context + */ +static void +expire_address_task (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc); + + +/** + * Update the list of addresses for this plugin, + * expiring those that are past their expiration date. + * + * @param plugin addresses of which plugin should be recomputed? + * @param fresh set to GNUNET_YES if a new address was added + * and we need to regenerate the HELLO even if nobody + * expired + */ +static void +update_addresses (struct TransportPlugin *plugin, int fresh) +{ + struct GNUNET_TIME_Relative min_remaining; + struct GNUNET_TIME_Relative remaining; + struct GNUNET_TIME_Absolute now; + struct AddressList *pos; + struct AddressList *prev; + struct AddressList *next; + int expired; + + if (plugin->address_update_task != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) + GNUNET_SCHEDULER_cancel (plugin->env.sched, plugin->address_update_task); + plugin->address_update_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; + now = GNUNET_TIME_absolute_get (); + min_remaining = GNUNET_TIME_UNIT_FOREVER_REL; + expired = GNUNET_NO; + prev = NULL; + pos = plugin->addresses; + while (pos != NULL) + { + next = pos->next; + if (pos->expires.value < now.value) + { + expired = GNUNET_YES; + if (prev == NULL) + plugin->addresses = pos->next; + else + prev->next = pos->next; + GNUNET_free (pos); + } + else + { + remaining = GNUNET_TIME_absolute_get_remaining (pos->expires); + if (remaining.value < min_remaining.value) + min_remaining = remaining; + prev = pos; + } + pos = next; + } + + if (expired || fresh) + refresh_hello (); + if (min_remaining.value < GNUNET_TIME_UNIT_FOREVER_REL.value) + plugin->address_update_task + = GNUNET_SCHEDULER_add_delayed (plugin->env.sched, + GNUNET_NO, + GNUNET_SCHEDULER_PRIORITY_IDLE, + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, + min_remaining, + &expire_address_task, plugin); + +} + + +/** + * Task used to clean up expired addresses for a plugin. + * + * @param cls closure + * @param tc context + */ +static void +expire_address_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct TransportPlugin *plugin = cls; + plugin->address_update_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; + update_addresses (plugin, GNUNET_NO); +} + + +/** + * Function that must be called by each plugin to notify the + * transport service about the addresses under which the transport + * provided by the plugin can be reached. + * + * @param cls closure + * @param name name of the transport that generated the address + * @param addr one of the addresses of the host, NULL for the last address + * the specific address format depends on the transport + * @param addrlen length of the address + * @param expires when should this address automatically expire? + */ +static void +plugin_env_notify_address (void *cls, + const char *name, + const void *addr, + size_t addrlen, + struct GNUNET_TIME_Relative expires) +{ + struct TransportPlugin *p = cls; + struct AddressList *al; + struct GNUNET_TIME_Absolute abex; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Plugin `%s' informs us about a new address\n", name); +#endif + abex = GNUNET_TIME_relative_to_absolute (expires); + GNUNET_assert (p == find_transport (name)); + + al = p->addresses; + while (al != NULL) + { + if ((addrlen == al->addrlen) && (0 == memcmp (addr, &al[1], addrlen))) + { + if (al->expires.value < abex.value) + al->expires = abex; + return; + } + al = al->next; + } + al = GNUNET_malloc (sizeof (struct AddressList) + addrlen); + al->addr = &al[1]; + al->next = p->addresses; + p->addresses = al; + al->expires = abex; + al->addrlen = addrlen; + memcpy (&al[1], addr, addrlen); + update_addresses (p, GNUNET_YES); +} + + +struct LookupHelloContext +{ + GNUNET_TRANSPORT_AddressCallback iterator; + + void *iterator_cls; +}; + + +static int +lookup_address_callback (void *cls, + const char *tname, + struct GNUNET_TIME_Absolute expiration, + const void *addr, size_t addrlen) +{ + struct LookupHelloContext *lhc = cls; + lhc->iterator (lhc->iterator_cls, tname, addr, addrlen); + return GNUNET_OK; +} + + +static void +lookup_hello_callback (void *cls, + const struct GNUNET_PeerIdentity *peer, + const struct GNUNET_HELLO_Message *h, uint32_t trust) +{ + struct LookupHelloContext *lhc = cls; + + if (peer == NULL) + { + lhc->iterator (lhc->iterator_cls, NULL, NULL, 0); + GNUNET_free (lhc); + return; + } + if (h == NULL) + return; + GNUNET_HELLO_iterate_addresses (h, + GNUNET_NO, &lookup_address_callback, lhc); +} + + +/** + * Function that allows a transport to query the known + * network addresses for a given peer. + * + * @param cls closure + * @param timeout after how long should we time out? + * @param target which peer are we looking for? + * @param iter function to call for each known address + * @param iter_cls closure for iter + */ +static void +plugin_env_lookup_address (void *cls, + struct GNUNET_TIME_Relative timeout, + const struct GNUNET_PeerIdentity *target, + GNUNET_TRANSPORT_AddressCallback iter, + void *iter_cls) +{ + struct LookupHelloContext *lhc; + + lhc = GNUNET_malloc (sizeof (struct LookupHelloContext)); + lhc->iterator = iter; + lhc->iterator_cls = iter_cls; + GNUNET_PEERINFO_for_all (cfg, + sched, + target, 0, timeout, &lookup_hello_callback, &lhc); +} + + +/** + * Notify all of our clients about a peer connecting. + */ +static void +notify_clients_connect (const struct GNUNET_PeerIdentity *peer, + struct GNUNET_TIME_Relative latency) +{ + struct ConnectInfoMessage cim; + struct TransportClient *cpos; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Informing clients about peer `%4s' connecting to us\n", + GNUNET_i2s (peer)); +#endif + cim.header.size = htons (sizeof (struct ConnectInfoMessage)); + cim.header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_CONNECT); + cim.quota_out = htonl (default_quota_out); + cim.latency = GNUNET_TIME_relative_hton (latency); + memcpy (&cim.id, peer, sizeof (struct GNUNET_PeerIdentity)); + cpos = clients; + while (cpos != NULL) + { + transmit_to_client (cpos, &cim.header, GNUNET_NO); + cpos = cpos->next; + } +} + + +/** + * Notify all of our clients about a peer disconnecting. + */ +static void +notify_clients_disconnect (const struct GNUNET_PeerIdentity *peer) +{ + struct DisconnectInfoMessage dim; + struct TransportClient *cpos; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Informing clients about peer `%4s' disconnecting\n", + GNUNET_i2s (peer)); +#endif + dim.header.size = htons (sizeof (struct DisconnectInfoMessage)); + dim.header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_DISCONNECT); + dim.reserved = htonl (0); + memcpy (&dim.peer, peer, sizeof (struct GNUNET_PeerIdentity)); + cpos = clients; + while (cpos != NULL) + { + transmit_to_client (cpos, &dim.header, GNUNET_NO); + cpos = cpos->next; + } +} + + +/** + * Copy any validated addresses to buf. + * + * @return 0 once all addresses have been + * returned + */ +static size_t +list_validated_addresses (void *cls, size_t max, void *buf) +{ + struct ValidationAddress **va = cls; + size_t ret; + + while ((NULL != *va) && ((*va)->ok != GNUNET_YES)) + *va = (*va)->next; + if (NULL == *va) + return 0; + ret = GNUNET_HELLO_add_address ((*va)->transport_name, + (*va)->expiration, + &(*va)->msg[1], (*va)->addr_len, buf, max); + *va = (*va)->next; + return ret; +} + + +/** + * HELLO validation cleanup task. + */ +static void +cleanup_validation (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct ValidationAddress *va; + struct ValidationList *pos; + struct ValidationList *prev; + struct GNUNET_TIME_Absolute now; + struct GNUNET_HELLO_Message *hello; + struct GNUNET_PeerIdentity pid; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK, + "HELLO validation cleanup background task running...\n"); +#endif + now = GNUNET_TIME_absolute_get (); + prev = NULL; + pos = pending_validations; + while (pos != NULL) + { + if (pos->timeout.value < now.value) + { + if (prev == NULL) + pending_validations = pos->next; + else + prev->next = pos->next; + va = pos->addresses; + hello = GNUNET_HELLO_create (&pos->publicKey, + &list_validated_addresses, &va); + GNUNET_CRYPTO_hash (&pos->publicKey, + sizeof (struct + GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &pid.hashPubKey); +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Creating persistent `%s' message for peer `%4s' based on confirmed addresses.\n", + "HELLO", GNUNET_i2s (&pid)); +#endif + GNUNET_PEERINFO_add_peer (cfg, sched, &pid, hello); + GNUNET_free (hello); + while (NULL != (va = pos->addresses)) + { + pos->addresses = va->next; + GNUNET_free (va->transport_name); + GNUNET_free (va); + } + GNUNET_free (pos); + if (prev == NULL) + pos = pending_validations; + else + pos = prev->next; + continue; + } + prev = pos; + pos = pos->next; + } + + /* finally, reschedule cleanup if needed; list is + ordered by timeout, so we need the last element... */ + pos = pending_validations; + while ((pos != NULL) && (pos->next != NULL)) + pos = pos->next; + if (NULL != pos) + GNUNET_SCHEDULER_add_delayed (sched, + GNUNET_NO, + GNUNET_SCHEDULER_PRIORITY_IDLE, + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, + GNUNET_TIME_absolute_get_remaining (pos-> + timeout), + &cleanup_validation, NULL); +} + + +struct CheckHelloValidatedContext +{ + /** + * Plugin for which we are validating. + */ + struct TransportPlugin *plugin; + + /** + * Hello that we are validating. + */ + struct GNUNET_HELLO_Message *hello; + + /** + * Validation list being build. + */ + struct ValidationList *e; +}; + + +/** + * Append the given address to the list of entries + * that need to be validated. + */ +static int +run_validation (void *cls, + const char *tname, + struct GNUNET_TIME_Absolute expiration, + const void *addr, size_t addrlen) +{ + struct ValidationList *e = cls; + struct TransportPlugin *tp; + struct ValidationAddress *va; + struct ValidationChallengeMessage *vcm; + + tp = find_transport (tname); + if (tp == NULL) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO | + GNUNET_ERROR_TYPE_BULK, + _ + ("Transport `%s' not loaded, will not try to validate peer address using this transport.\n"), + tname); + return GNUNET_OK; + } + va = GNUNET_malloc (sizeof (struct ValidationAddress) + + sizeof (struct ValidationChallengeMessage) + addrlen); + va->next = e->addresses; + e->addresses = va; + vcm = (struct ValidationChallengeMessage *) &va[1]; + va->msg = vcm; + va->transport_name = GNUNET_strdup (tname); + va->addr_len = addrlen; + vcm->header.size = + htons (sizeof (struct ValidationChallengeMessage) + addrlen); + vcm->header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_PING); + vcm->purpose.size = + htonl (sizeof (struct ValidationChallengeMessage) + addrlen - + sizeof (struct GNUNET_MessageHeader)); + vcm->purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_TRANSPORT_HELLO); + vcm->challenge = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, + (unsigned int) -1); + /* Note: vcm->target is set in check_hello_validated */ + memcpy (&vcm[1], addr, addrlen); + return GNUNET_OK; +} + + +/** + * Check if addresses in validated hello "h" overlap with + * those in "chvc->hello" and update "chvc->hello" accordingly, + * removing those addresses that have already been validated. + */ +static void +check_hello_validated (void *cls, + const struct GNUNET_PeerIdentity *peer, + const struct GNUNET_HELLO_Message *h, uint32_t trust) +{ + struct CheckHelloValidatedContext *chvc = cls; + struct ValidationAddress *va; + struct TransportPlugin *tp; + int first_call; + + first_call = GNUNET_NO; + if (chvc->e == NULL) + { + first_call = GNUNET_YES; + chvc->e = GNUNET_malloc (sizeof (struct ValidationList)); + GNUNET_HELLO_get_key (h != NULL ? h : chvc->hello, &chvc->e->publicKey); + chvc->e->timeout = + GNUNET_TIME_relative_to_absolute (HELLO_VERIFICATION_TIMEOUT); + chvc->e->next = pending_validations; + pending_validations = chvc->e; + } + if (h != NULL) + { + GNUNET_HELLO_iterate_new_addresses (chvc->hello, + h, + GNUNET_TIME_absolute_get (), + &run_validation, chvc->e); + } + else if (GNUNET_YES == first_call) + { + /* no existing HELLO, all addresses are new */ + GNUNET_HELLO_iterate_addresses (chvc->hello, + GNUNET_NO, &run_validation, chvc->e); + } + if (h != NULL) + return; /* wait for next call */ + /* finally, transmit validation attempts */ + va = chvc->e->addresses; + while (va != NULL) + { + GNUNET_CRYPTO_hash (&chvc->e->publicKey, + sizeof (struct + GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &va->msg->target.hashPubKey); +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Establishing `%s' connection to validate `%s' of `%4s' (sending our `%s')\n", + va->transport_name, + "HELLO", GNUNET_i2s (&va->msg->target), "HELLO"); +#endif + tp = find_transport (va->transport_name); + GNUNET_assert (tp != NULL); + if (NULL == + tp->api->send_to (tp->api->cls, + &va->msg->target, + (const struct GNUNET_MessageHeader *) our_hello, + &va->msg->header, + HELLO_VERIFICATION_TIMEOUT, + &va->msg[1], va->addr_len)) + va->ok = GNUNET_SYSERR; + va = va->next; + } + if (chvc->e->next == NULL) + GNUNET_SCHEDULER_add_delayed (sched, + GNUNET_NO, + GNUNET_SCHEDULER_PRIORITY_IDLE, + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, + GNUNET_TIME_absolute_get_remaining (chvc-> + e-> + timeout), + &cleanup_validation, NULL); + GNUNET_free (chvc); +} + + +/** + * Process HELLO-message. + * + * @param plugin transport involved, may be NULL + * @param message the actual message + * @return GNUNET_OK if the HELLO was well-formed, GNUNET_SYSERR otherwise + */ +static int +process_hello (struct TransportPlugin *plugin, + const struct GNUNET_MessageHeader *message) +{ + struct ValidationList *e; + uint16_t hsize; + struct GNUNET_PeerIdentity target; + const struct GNUNET_HELLO_Message *hello; + struct CheckHelloValidatedContext *chvc; + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded publicKey; + + hsize = ntohs (message->size); + if ((ntohs (message->type) != GNUNET_MESSAGE_TYPE_HELLO) || + (hsize < sizeof (struct GNUNET_MessageHeader))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* first, check if load is too high */ + if (GNUNET_OS_load_cpu_get (cfg) > 100) + { + /* TODO: call to stats? */ + return GNUNET_OK; + } + hello = (const struct GNUNET_HELLO_Message *) message; + if (GNUNET_OK != GNUNET_HELLO_get_key (hello, &publicKey)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_hash (&publicKey, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &target.hashPubKey); +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Processing `%s' message for `%4s'\n", + "HELLO", GNUNET_i2s (&target)); +#endif + /* check if a HELLO for this peer is already on the validation list */ + e = pending_validations; + while (e != NULL) + { + if (0 == memcmp (&e->publicKey, + &publicKey, + sizeof (struct + GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded))) + { + /* TODO: call to stats? */ + return GNUNET_OK; + } + e = e->next; + } + chvc = GNUNET_malloc (sizeof (struct CheckHelloValidatedContext) + hsize); + chvc->plugin = plugin; + chvc->hello = (struct GNUNET_HELLO_Message *) &chvc[1]; + memcpy (chvc->hello, hello, hsize); + /* finally, check if HELLO was previously validated + (continuation will then schedule actual validation) */ + GNUNET_PEERINFO_for_all (cfg, + sched, + &target, + 0, + HELLO_VERIFICATION_TIMEOUT, + &check_hello_validated, chvc); + return GNUNET_OK; +} + + +/** + * Handle PING-message. If the plugin that gave us the message is + * able to queue the PONG immediately, we only queue one PONG. + * Otherwise we send at most TWO PONG messages, one via an unconfirmed + * transport and one via a confirmed transport. Both addresses are + * selected randomly among those available. + * + * @param plugin plugin that gave us the message + * @param sender claimed sender of the PING + * @param plugin_context context that might be used to send response + * @param message the actual message + */ +static void +process_ping (struct TransportPlugin *plugin, + const struct GNUNET_PeerIdentity *sender, + void *plugin_context, + const struct GNUNET_MessageHeader *message) +{ + const struct ValidationChallengeMessage *vcm; + struct ValidationChallengeResponse vcr; + uint16_t msize; + struct NeighbourList *n; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK, + "Processing PING\n"); +#endif + msize = ntohs (message->size); + if (msize < sizeof (struct ValidationChallengeMessage)) + { + GNUNET_break_op (0); + return; + } + vcm = (const struct ValidationChallengeMessage *) message; + if (0 != memcmp (&vcm->target, + &my_identity, sizeof (struct GNUNET_PeerIdentity))) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + _("Received `%s' message not destined for me!\n"), "PING"); + /* TODO: call statistics */ + return; + } + if ((ntohl (vcm->purpose.size) != + msize - sizeof (struct GNUNET_MessageHeader)) + || (ntohl (vcm->purpose.purpose) != + GNUNET_SIGNATURE_PURPOSE_TRANSPORT_HELLO)) + { + GNUNET_break_op (0); + return; + } + msize -= sizeof (struct ValidationChallengeMessage); + if (GNUNET_OK != + plugin->api->address_suggested (plugin->api->cls, &vcm[1], msize)) + { + GNUNET_break_op (0); + return; + } + vcr.header.size = htons (sizeof (struct ValidationChallengeResponse)); + vcr.header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_PONG); + vcr.challenge = vcm->challenge; + vcr.sender = my_identity; + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_rsa_sign (my_private_key, + &vcm->purpose, &vcr.signature)); +#if EXTRA_CHECKS + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_rsa_verify + (GNUNET_SIGNATURE_PURPOSE_TRANSPORT_HELLO, &vcm->purpose, + &vcr.signature, &my_public_key)); +#endif +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK, + "Trying to transmit PONG using inbound connection\n"); +#endif + n = find_neighbour (sender); + transmit_to_peer (NULL, &vcr.header, GNUNET_YES, n); +} + + +/** + * Handle PONG-message. + * + * @param message the actual message + */ +static void +process_pong (struct TransportPlugin *plugin, + const struct GNUNET_MessageHeader *message) +{ + const struct ValidationChallengeResponse *vcr; + struct ValidationList *pos; + struct GNUNET_PeerIdentity peer; + struct ValidationAddress *va; + int all_done; + int matched; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK, + "Processing PONG\n"); +#endif + vcr = (const struct ValidationChallengeResponse *) message; + pos = pending_validations; + while (pos != NULL) + { + GNUNET_CRYPTO_hash (&pos->publicKey, + sizeof (struct + GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &peer.hashPubKey); + if (0 == + memcmp (&peer, &vcr->sender, sizeof (struct GNUNET_PeerIdentity))) + break; + pos = pos->next; + } + if (pos == NULL) + { + /* TODO: call statistics (unmatched PONG) */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _ + ("Received `%s' message but have no record of a matching `%s' message. Ignoring.\n"), + "PONG", "PING"); + return; + } + all_done = GNUNET_YES; + matched = GNUNET_NO; + va = pos->addresses; + while (va != NULL) + { + if (va->msg->challenge == vcr->challenge) + { + if (GNUNET_OK != + GNUNET_CRYPTO_rsa_verify + (GNUNET_SIGNATURE_PURPOSE_TRANSPORT_HELLO, &va->msg->purpose, + &vcr->signature, &pos->publicKey)) + { + /* this could rarely happen if we used the same + challenge number for the peer for two different + transports / addresses, but the likelihood is + very small... */ + GNUNET_break_op (0); + } + else + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Confirmed validity of peer address.\n"); +#endif + va->ok = GNUNET_YES; + va->expiration = + GNUNET_TIME_relative_to_absolute (HELLO_ADDRESS_EXPIRATION); + matched = GNUNET_YES; + } + } + if (va->ok != GNUNET_YES) + all_done = GNUNET_NO; + va = va->next; + } + if (GNUNET_NO == matched) + { + /* TODO: call statistics (unmatched PONG) */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _ + ("Received `%s' message but have no record of a matching `%s' message. Ignoring.\n"), + "PONG", "PING"); + } + if (GNUNET_YES == all_done) + { + pos->timeout.value = 0; + GNUNET_SCHEDULER_add_delayed (sched, + GNUNET_NO, + GNUNET_SCHEDULER_PRIORITY_IDLE, + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, + GNUNET_TIME_UNIT_ZERO, + &cleanup_validation, NULL); + } +} + + +/** + * The peer specified by the given neighbour has timed-out. Update + * our state and do the necessary notifications. Also notifies + * our clients that the neighbour is now officially gone. + * + * @param n the neighbour list entry for the peer + */ +static void +disconnect_neighbour (struct NeighbourList *n) +{ + struct ReadyList *rpos; + struct NeighbourList *npos; + struct NeighbourList *nprev; + struct MessageQueue *mq; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK, + "Disconnecting from neighbour\n"); +#endif + /* remove n from neighbours list */ + nprev = NULL; + npos = neighbours; + while ((npos != NULL) && (npos != n)) + { + nprev = npos; + npos = npos->next; + } + GNUNET_assert (npos != NULL); + if (nprev == NULL) + neighbours = n->next; + else + nprev->next = n->next; + + /* notify all clients about disconnect */ + notify_clients_disconnect (&n->id); + + /* clean up all plugins, cancel connections & pending transmissions */ + while (NULL != (rpos = n->plugins)) + { + n->plugins = rpos->next; + GNUNET_assert (rpos->neighbour == n); + rpos->plugin->api->cancel (rpos->plugin->api->cls, + rpos->plugin_handle, rpos, &n->id); + GNUNET_free (rpos); + } + + /* free all messages on the queue */ + while (NULL != (mq = n->messages)) + { + n->messages = mq->next; + GNUNET_assert (mq->neighbour == n); + GNUNET_free (mq); + } + + /* finally, free n itself */ + GNUNET_free (n); +} + + +/** + * Add an entry for each of our transport plugins + * (that are able to send) to the list of plugins + * for this neighbour. + * + * @param neighbour to initialize + */ +static void +add_plugins (struct NeighbourList *neighbour) +{ + struct TransportPlugin *tp; + struct ReadyList *rl; + + neighbour->retry_plugins_time + = GNUNET_TIME_relative_to_absolute (PLUGIN_RETRY_FREQUENCY); + tp = plugins; + while (tp != NULL) + { + if (tp->api->send != NULL) + { + rl = GNUNET_malloc (sizeof (struct ReadyList)); + rl->next = neighbour->plugins; + neighbour->plugins = rl; + rl->plugin = tp; + rl->neighbour = neighbour; + rl->transmit_ready = GNUNET_YES; + } + tp = tp->next; + } +} + + +static void +neighbour_timeout_task (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct NeighbourList *n = cls; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK, + "Neighbour has timed out!\n"); +#endif + n->timeout_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; + disconnect_neighbour (n); +} + + + +/** + * Create a fresh entry in our neighbour list for the given peer. + * Will try to transmit our current HELLO to the new neighbour. Also + * notifies our clients about the new "connection". + * + * @param peer the peer for which we create the entry + * @return the new neighbour list entry + */ +static struct NeighbourList * +setup_new_neighbour (const struct GNUNET_PeerIdentity *peer) +{ + struct NeighbourList *n; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK, + "Setting up new neighbour `%4s', sending our HELLO to introduce ourselves\n", + GNUNET_i2s (peer)); +#endif + GNUNET_assert (our_hello != NULL); + n = GNUNET_malloc (sizeof (struct NeighbourList)); + n->next = neighbours; + neighbours = n; + n->id = *peer; + n->last_quota_update = GNUNET_TIME_absolute_get (); + n->peer_timeout = + GNUNET_TIME_relative_to_absolute (IDLE_CONNECTION_TIMEOUT); + n->quota_in = default_quota_in; + add_plugins (n); + n->hello_version_sent = our_hello_version; + n->timeout_task = GNUNET_SCHEDULER_add_delayed (sched, + GNUNET_NO, + GNUNET_SCHEDULER_PRIORITY_IDLE, + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, + IDLE_CONNECTION_TIMEOUT, + &neighbour_timeout_task, n); + transmit_to_peer (NULL, + (const struct GNUNET_MessageHeader *) our_hello, + GNUNET_YES, n); + notify_clients_connect (peer, GNUNET_TIME_UNIT_FOREVER_REL); + return n; +} + + +/** + * Function called by the plugin for each received message. + * Update data volumes, possibly notify plugins about + * reducing the rate at which they read from the socket + * and generally forward to our receive callback. + * + * @param plugin_context value to pass to this plugin + * to respond to the given peer (use is optional, + * but may speed up processing) + * @param service_context value passed to the transport-service + * to identify the neighbour; will be NULL on the first + * call for a given peer + * @param latency estimated latency for communicating with the + * given peer + * @param peer (claimed) identity of the other peer + * @param message the message, NULL if peer was disconnected + * @return the new service_context that the plugin should use + * for future receive calls for messages from this + * particular peer + */ +static struct ReadyList * +plugin_env_receive (void *cls, + void *plugin_context, + struct ReadyList *service_context, + struct GNUNET_TIME_Relative latency, + const struct GNUNET_PeerIdentity *peer, + const struct GNUNET_MessageHeader *message) +{ + const struct GNUNET_MessageHeader ack = { + htons (sizeof (struct GNUNET_MessageHeader)), + htons (GNUNET_MESSAGE_TYPE_TRANSPORT_ACK) + }; + struct TransportPlugin *plugin = cls; + struct TransportClient *cpos; + struct InboundMessage *im; + uint16_t msize; + struct NeighbourList *n; + + if (service_context != NULL) + { + n = service_context->neighbour; + GNUNET_assert (n != NULL); + } + else + { + n = find_neighbour (peer); + if (n == NULL) + { + if (message == NULL) + return NULL; /* disconnect of peer already marked down */ + n = setup_new_neighbour (peer); + } + service_context = n->plugins; + while ((service_context != NULL) && (plugin != service_context->plugin)) + service_context = service_context->next; + GNUNET_assert ((plugin->api->send == NULL) || + (service_context != NULL)); + } + if (message == NULL) + { + if ((service_context != NULL) && + (service_context->plugin_handle == plugin_context)) + { + service_context->connected = GNUNET_NO; + service_context->plugin_handle = NULL; + } + /* TODO: call stats */ + return NULL; + } +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK, + "Processing message of type `%u' received by plugin...\n", + ntohs (message->type)); +#endif + if (service_context != NULL) + { + if (service_context->connected == GNUNET_NO) + { + service_context->connected = GNUNET_YES; + service_context->transmit_ready = GNUNET_YES; + service_context->connect_attempts++; + } + service_context->timeout + = GNUNET_TIME_relative_to_absolute (IDLE_CONNECTION_TIMEOUT); + service_context->plugin_handle = plugin_context; + service_context->latency = latency; + } + /* update traffic received amount ... */ + msize = ntohs (message->size); + n->last_received += msize; + GNUNET_SCHEDULER_cancel (sched, n->timeout_task); + n->peer_timeout = + GNUNET_TIME_relative_to_absolute (IDLE_CONNECTION_TIMEOUT); + n->timeout_task = + GNUNET_SCHEDULER_add_delayed (sched, GNUNET_NO, + GNUNET_SCHEDULER_PRIORITY_IDLE, + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, + IDLE_CONNECTION_TIMEOUT, + &neighbour_timeout_task, n); + update_quota (n); + if (n->quota_violation_count > QUOTA_VIOLATION_DROP_THRESHOLD) + { + /* dropping message due to frequent inbound volume violations! */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING | + GNUNET_ERROR_TYPE_BULK, + _ + ("Dropping incoming message due to repeated bandwidth quota violations.\n")); + /* TODO: call stats */ + return service_context; + } + switch (ntohs (message->type)) + { + case GNUNET_MESSAGE_TYPE_HELLO: +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Receiving `%s' message from other peer.\n", "HELLO"); +#endif + process_hello (plugin, message); +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending `%s' message to connecting peer.\n", "ACK"); +#endif + transmit_to_peer (NULL, &ack, GNUNET_YES, n); + break; + case GNUNET_MESSAGE_TYPE_TRANSPORT_PING: + process_ping (plugin, peer, plugin_context, message); + break; + case GNUNET_MESSAGE_TYPE_TRANSPORT_PONG: + process_pong (plugin, message); + break; + case GNUNET_MESSAGE_TYPE_TRANSPORT_ACK: + n->saw_ack = GNUNET_YES; + /* intentional fall-through! */ + default: +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received message of type %u from other peer, sending to all clients.\n", + ntohs (message->type)); +#endif + /* transmit message to all clients */ + im = GNUNET_malloc (sizeof (struct InboundMessage) + msize); + im->header.size = htons (sizeof (struct InboundMessage) + msize); + im->header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_RECV); + im->latency = GNUNET_TIME_relative_hton (latency); + im->peer = *peer; + memcpy (&im[1], message, msize); + + cpos = clients; + while (cpos != NULL) + { + transmit_to_client (cpos, &im->header, GNUNET_YES); + cpos = cpos->next; + } + GNUNET_free (im); + } + return service_context; +} + + +/** + * Handle START-message. This is the first message sent to us + * by any client which causes us to add it to our list. + * + * @param cls closure (always NULL) + * @param server the server handling the message + * @param client identification of the client + * @param message the actual message + */ +static void +handle_start (void *cls, + struct GNUNET_SERVER_Handle *server, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message) +{ + struct TransportClient *c; + struct ConnectInfoMessage cim; + struct NeighbourList *n; + struct InboundMessage *im; + struct GNUNET_MessageHeader *ack; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received `%s' request from client\n", "START"); +#endif + c = clients; + while (c != NULL) + { + if (c->client == client) + { + /* client already on our list! */ + GNUNET_break (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return; + } + c = c->next; + } + c = GNUNET_malloc (sizeof (struct TransportClient)); + c->next = clients; + clients = c; + c->client = client; + if (our_hello != NULL) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending our own HELLO to new client\n"); +#endif + transmit_to_client (c, + (const struct GNUNET_MessageHeader *) our_hello, + GNUNET_NO); + /* tell new client about all existing connections */ + cim.header.size = htons (sizeof (struct ConnectInfoMessage)); + cim.header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_CONNECT); + cim.quota_out = htonl (default_quota_out); + cim.latency = GNUNET_TIME_relative_hton (GNUNET_TIME_UNIT_ZERO); /* FIXME? */ + im = GNUNET_malloc (sizeof (struct InboundMessage) + + sizeof (struct GNUNET_MessageHeader)); + im->header.size = htons (sizeof (struct InboundMessage) + + sizeof (struct GNUNET_MessageHeader)); + im->header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_RECV); + im->latency = GNUNET_TIME_relative_hton (GNUNET_TIME_UNIT_ZERO); /* FIXME? */ + ack = (struct GNUNET_MessageHeader *) &im[1]; + ack->size = htons (sizeof (struct GNUNET_MessageHeader)); + ack->type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_ACK); + for (n = neighbours; n != NULL; n = n->next) + { + cim.id = n->id; + transmit_to_client (c, &cim.header, GNUNET_NO); + if (n->saw_ack) + { + im->peer = n->id; + transmit_to_client (c, &im->header, GNUNET_NO); + } + } + GNUNET_free (im); + } + GNUNET_SERVER_receive_done (client, GNUNET_OK); +} + + +/** + * Handle HELLO-message. + * + * @param cls closure (always NULL) + * @param server the server handling the message + * @param client identification of the client + * @param message the actual message + */ +static void +handle_hello (void *cls, + struct GNUNET_SERVER_Handle *server, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message) +{ + int ret; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received `%s' request from client\n", "HELLO"); +#endif + ret = process_hello (NULL, message); + GNUNET_SERVER_receive_done (client, ret); +} + + +/** + * Handle SEND-message. + * + * @param cls closure (always NULL) + * @param server the server handling the message + * @param client identification of the client + * @param message the actual message + */ +static void +handle_send (void *cls, + struct GNUNET_SERVER_Handle *server, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message) +{ + struct TransportClient *tc; + struct NeighbourList *n; + const struct OutboundMessage *obm; + const struct GNUNET_MessageHeader *obmm; + uint16_t size; + uint16_t msize; + + size = ntohs (message->size); + if (size < + sizeof (struct OutboundMessage) + sizeof (struct GNUNET_MessageHeader)) + { + GNUNET_break (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return; + } + obm = (const struct OutboundMessage *) message; +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received `%s' request from client with target `%4s'\n", + "SEND", GNUNET_i2s (&obm->peer)); +#endif + obmm = (const struct GNUNET_MessageHeader *) &obm[1]; + msize = ntohs (obmm->size); + if (size != msize + sizeof (struct OutboundMessage)) + { + GNUNET_break (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return; + } + n = find_neighbour (&obm->peer); + if (n == NULL) + n = setup_new_neighbour (&obm->peer); + tc = clients; + while ((tc != NULL) && (tc->client != client)) + tc = tc->next; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Client asked to transmit %u-byte message of type %u to `%4s'\n", + ntohs (obmm->size), + ntohs (obmm->type), GNUNET_i2s (&obm->peer)); +#endif + transmit_to_peer (tc, obmm, GNUNET_NO, n); + GNUNET_SERVER_receive_done (client, GNUNET_OK); +} + + +/** + * Handle SET_QUOTA-message. + * + * @param cls closure (always NULL) + * @param server the server handling the message + * @param client identification of the client + * @param message the actual message + */ +static void +handle_set_quota (void *cls, + struct GNUNET_SERVER_Handle *server, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message) +{ + const struct QuotaSetMessage *qsm = + (const struct QuotaSetMessage *) message; + struct NeighbourList *n; + struct TransportPlugin *p; + struct ReadyList *rl; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received `%s' request from client for peer `%4s'\n", + "SET_QUOTA", GNUNET_i2s (&qsm->peer)); +#endif + n = find_neighbour (&qsm->peer); + if (n == NULL) + { + GNUNET_SERVER_receive_done (client, GNUNET_OK); + return; + } + update_quota (n); + if (n->quota_in < ntohl (qsm->quota_in)) + n->last_quota_update = GNUNET_TIME_absolute_get (); + n->quota_in = ntohl (qsm->quota_in); + rl = n->plugins; + while (rl != NULL) + { + p = rl->plugin; + p->api->set_receive_quota (p->api->cls, + &qsm->peer, ntohl (qsm->quota_in)); + rl = rl->next; + } + GNUNET_SERVER_receive_done (client, GNUNET_OK); +} + + +/** + * Handle TRY_CONNECT-message. + * + * @param cls closure (always NULL) + * @param server the server handling the message + * @param client identification of the client + * @param message the actual message + */ +static void +handle_try_connect (void *cls, + struct GNUNET_SERVER_Handle *server, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message) +{ + const struct TryConnectMessage *tcm; + + tcm = (const struct TryConnectMessage *) message; +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received `%s' request from client asking to connect to `%4s'\n", + "TRY_CONNECT", GNUNET_i2s (&tcm->peer)); +#endif + if (NULL == find_neighbour (&tcm->peer)) + setup_new_neighbour (&tcm->peer); + GNUNET_SERVER_receive_done (client, GNUNET_OK); +} + + +/** + * List of handlers for the messages understood by this + * service. + */ +static struct GNUNET_SERVER_MessageHandler handlers[] = { + {&handle_start, NULL, + GNUNET_MESSAGE_TYPE_TRANSPORT_START, 0}, + {&handle_hello, NULL, + GNUNET_MESSAGE_TYPE_HELLO, 0}, + {&handle_send, NULL, + GNUNET_MESSAGE_TYPE_TRANSPORT_SEND, 0}, + {&handle_set_quota, NULL, + GNUNET_MESSAGE_TYPE_TRANSPORT_SET_QUOTA, sizeof (struct QuotaSetMessage)}, + {&handle_try_connect, NULL, + GNUNET_MESSAGE_TYPE_TRANSPORT_TRY_CONNECT, + sizeof (struct TryConnectMessage)}, + {NULL, NULL, 0, 0} +}; + + +/** + * Setup the environment for this plugin. + */ +static void +create_environment (struct TransportPlugin *plug) +{ + plug->env.cfg = cfg; + plug->env.sched = sched; + plug->env.my_public_key = &my_public_key; + plug->env.cls = plug; + plug->env.receive = &plugin_env_receive; + plug->env.lookup = &plugin_env_lookup_address; + plug->env.notify_address = &plugin_env_notify_address; + plug->env.default_quota_in = default_quota_in; + plug->env.max_connections = max_connect_per_transport; +} + + +/** + * Start the specified transport (load the plugin). + */ +static void +start_transport (struct GNUNET_SERVER_Handle *server, const char *name) +{ + struct TransportPlugin *plug; + char *libname; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Loading `%s' transport plugin\n"), name); + GNUNET_asprintf (&libname, "libgnunet_plugin_transport_%s", name); + plug = GNUNET_malloc (sizeof (struct TransportPlugin)); + create_environment (plug); + plug->short_name = GNUNET_strdup (name); + plug->lib_name = libname; + plug->next = plugins; + plugins = plug; + plug->api = GNUNET_PLUGIN_load (libname, &plug->env); + if (plug->api == NULL) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to load transport plugin for `%s'\n"), name); + GNUNET_free (plug->short_name); + plugins = plug->next; + GNUNET_free (libname); + GNUNET_free (plug); + } +} + + +/** + * Called whenever a client is disconnected. Frees our + * resources associated with that client. + * + * @param cls closure + * @param client identification of the client + */ +static void +client_disconnect_notification (void *cls, + struct GNUNET_SERVER_Client *client) +{ + struct TransportClient *pos; + struct TransportClient *prev; + struct ClientMessageQueueEntry *mqe; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK, + "Client disconnected, cleaning up.\n"); +#endif + prev = NULL; + pos = clients; + while ((pos != NULL) && (pos->client != client)) + { + prev = pos; + pos = pos->next; + } + if (pos == NULL) + return; + while (NULL != (mqe = pos->message_queue_head)) + { + pos->message_queue_head = mqe->next; + GNUNET_free (mqe); + } + pos->message_queue_head = NULL; + if (prev == NULL) + clients = pos->next; + else + prev->next = pos->next; + if (GNUNET_YES == pos->tcs_pending) + { + pos->client = NULL; + return; + } + GNUNET_free (pos); +} + + +/** + * Initiate transport service. + * + * @param cls closure + * @param s scheduler to use + * @param serv the initialized server + * @param c configuration to use + */ +static void +run (void *cls, + struct GNUNET_SCHEDULER_Handle *s, + struct GNUNET_SERVER_Handle *serv, struct GNUNET_CONFIGURATION_Handle *c) +{ + char *plugs; + char *pos; + int no_transports; + unsigned long long qin; + unsigned long long qout; + unsigned long long tneigh; + char *keyfile; + + sched = s; + cfg = c; + /* parse configuration */ + if ((GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (c, + "TRANSPORT", + "DEFAULT_QUOTA_IN", + &qin)) || + (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (c, + "TRANSPORT", + "DEFAULT_QUOTA_OUT", + &qout)) || + (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (c, + "TRANSPORT", + "NEIGHBOUR_LIMIT", + &tneigh)) || + (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (c, + "GNUNETD", + "HOSTKEY", &keyfile))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _ + ("Transport service is lacking key configuration settings. Exiting.\n")); + GNUNET_SCHEDULER_shutdown (s); + return; + } + max_connect_per_transport = (uint32_t) tneigh; + default_quota_in = (uint32_t) qin; + default_quota_out = (uint32_t) qout; + my_private_key = GNUNET_CRYPTO_rsa_key_create_from_file (keyfile); + GNUNET_free (keyfile); + if (my_private_key == NULL) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _ + ("Transport service could not access hostkey. Exiting.\n")); + GNUNET_SCHEDULER_shutdown (s); + return; + } + GNUNET_CRYPTO_rsa_key_get_public (my_private_key, &my_public_key); + GNUNET_CRYPTO_hash (&my_public_key, + sizeof (my_public_key), &my_identity.hashPubKey); + /* setup notification */ + server = serv; + GNUNET_SERVER_disconnect_notify (server, + &client_disconnect_notification, NULL); + /* load plugins... */ + no_transports = 1; + if (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (c, + "TRANSPORT", "PLUGINS", &plugs)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Starting transport plugins `%s'\n"), plugs); + pos = strtok (plugs, " "); + while (pos != NULL) + { + start_transport (server, pos); + no_transports = 0; + pos = strtok (NULL, " "); + } + GNUNET_free (plugs); + } + if (no_transports) + refresh_hello (); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Transport service ready.\n")); + /* process client requests */ + GNUNET_SERVER_add_handlers (server, handlers); +} + + +/** + * Function called when the service shuts + * down. Unloads our plugins. + * + * @param cls closure + * @param cfg configuration to use + */ +static void +unload_plugins (void *cls, struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct TransportPlugin *plug; + struct AddressList *al; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transport service is unloading plugins...\n"); +#endif + while (NULL != (plug = plugins)) + { + plugins = plug->next; + GNUNET_break (NULL == GNUNET_PLUGIN_unload (plug->lib_name, plug->api)); + GNUNET_free (plug->lib_name); + GNUNET_free (plug->short_name); + while (NULL != (al = plug->addresses)) + { + plug->addresses = al->next; + GNUNET_free (al); + } + GNUNET_free (plug); + } + if (my_private_key != NULL) + GNUNET_CRYPTO_rsa_key_free (my_private_key); +} + + +/** + * The main function for the transport service. + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + return (GNUNET_OK == + GNUNET_SERVICE_run (argc, + argv, + "transport", + &run, NULL, &unload_plugins, NULL)) ? 0 : 1; +} + +/* end of gnunet-service-transport.c */ diff --git a/src/transport/gnunet-transport.c b/src/transport/gnunet-transport.c new file mode 100644 index 000000000..52475e3eb --- /dev/null +++ b/src/transport/gnunet-transport.c @@ -0,0 +1,42 @@ +/* + This file is part of GNUnet. + (C) 2009 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file server/gnunet-transport.c + * @brief Tool to help configure the transports. + * @author Christian Grothoff + * + * This utility can be used to test if a transport mechanism for + * GNUnet is properly configured. + */ + +#include "platform.h" +#include "gnunet_program_lib.h" +#include "gnunet_protocols.h" +#include "gnunet_transport_service.h" + +int +main (int argc, char *const *argv) +{ + return 0; +} + + +/* end of gnunet-transport.c */ diff --git a/src/transport/plugin_transport.h b/src/transport/plugin_transport.h new file mode 100644 index 000000000..57df9affe --- /dev/null +++ b/src/transport/plugin_transport.h @@ -0,0 +1,468 @@ +/* + This file is part of GNUnet + (C) 2009 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file transport/plugin_transport.h + * @brief API for the transport services. This header + * specifies the struct that is given to the plugin's entry + * method and the other struct that must be returned. + * Note that the destructors of transport plugins will + * be given the value returned by the constructor + * and is expected to return a NULL pointer. + * + * TODO: + * - consider moving DATA message (latency measurement) + * to service; avoids encapsulation overheads and + * would enable latency measurements for non-bidi + * transports. + * - + * + * @author Christian Grothoff + */ +#ifndef PLUGIN_TRANSPORT_H +#define PLUGIN_TRANSPORT_H + +#include "gnunet_configuration_lib.h" +#include "gnunet_scheduler_lib.h" +#include "gnunet_transport_service.h" + +/** + * Opaque internal context for a particular peer of the transport + * service. Plugins will be given a pointer to this type and, if + * cheaply possible, should pass this pointer back to the transport + * service whenever additional messages from the same peer are + * received. + */ +struct ReadyList; + +/** + * Function called by the transport for each received message. + * This function should also be called with "NULL" for the + * message to signal that the other peer disconnected. + * + * @param cls closure + * @param plugin_context value to pass to this plugin + * to respond to the given peer (use is optional, + * but may speed up processing) + * @param service_context value passed to the transport-service + * to identify the neighbour; will be NULL on the first + * call for a given peer + * @param latency estimated latency for communicating with the + * given peer; should be set to GNUNET_TIME_UNIT_FOREVER_REL + * until the transport has seen messages transmitted in + * BOTH directions (and hence been able to do an actual + * round-trip observation); a non-FOREVER latency is also used + * by the transport to know that communication in both directions + * using this one plugin actually works + * @param peer (claimed) identity of the other peer + * @param message the message, NULL if peer was disconnected + * @return the new service_context that the plugin should use + * for future receive calls for messages from this + * particular peer + */ +typedef struct ReadyList * + (*GNUNET_TRANSPORT_PluginReceiveCallback) (void *cls, + void *plugin_context, + struct ReadyList * + service_context, + struct GNUNET_TIME_Relative + latency, + const struct GNUNET_PeerIdentity + * peer, + const struct GNUNET_MessageHeader + * message); + + +/** + * Function that will be called for each address the transport + * is aware that it might be reachable under. + * + * @param cls closure + * @param name name of the transport that generated the address + * @param addr one of the addresses of the host, NULL for the last address + * the specific address format depends on the transport + * @param addrlen length of the address + * @param expires when should this address automatically expire? + */ +typedef void (*GNUNET_TRANSPORT_AddressNotification) (void *cls, + const char *name, + const void *addr, + size_t addrlen, + struct + GNUNET_TIME_Relative + expires); + + +/** + * Function that will be called for each address obtained from the HELLO. + * + * @param cls closure + * @param name name of the transport that generated the address + * @param addr one of the addresses of the host, NULL for the last address + * the specific address format depends on the transport + * @param addrlen length of the address + */ +typedef void (*GNUNET_TRANSPORT_AddressCallback) (void *cls, + const char *name, + const void *addr, + size_t addrlen); + + +/** + * Function that allows a transport to query the known + * network addresses for a given peer. + * + * @param cls closure + * @param timeout after how long should we time out? + * @param target which peer are we looking for? + * @param iter function to call for each known address + * @param iter_cls closure for iter + */ +typedef void (*GNUNET_TRANSPORT_LookupAddress) (void *cls, + struct GNUNET_TIME_Relative + timeout, + const struct + GNUNET_PeerIdentity * target, + GNUNET_TRANSPORT_AddressCallback + iter, void *iter_cls); + + +/** + * The transport service will pass a pointer to a struct + * of this type as the first and only argument to the + * entry point of each transport plugin. + */ +struct GNUNET_TRANSPORT_PluginEnvironment +{ + /** + * Configuration to use. + */ + struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Scheduler to use. + */ + struct GNUNET_SCHEDULER_Handle *sched; + + /** + * Our public key. + */ + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *my_public_key; + + /** + * Closure for the various callbacks. + */ + void *cls; + + /** + * Function that should be called by the transport plugin + * whenever a message is received. + */ + GNUNET_TRANSPORT_PluginReceiveCallback receive; + + /** + * Address lookup function. + */ + GNUNET_TRANSPORT_LookupAddress lookup; + + /** + * Function that must be called by each plugin to notify the + * transport service about the addresses under which the transport + * provided by the plugin can be reached. + */ + GNUNET_TRANSPORT_AddressNotification notify_address; + + /** + * What is the default quota (in terms of incoming bytes per + * ms) for new connections? + */ + uint32_t default_quota_in; + + /** + * What is the maximum number of connections that this transport + * should allow? Transports that do not have sessions (such as + * UDP) can ignore this value. + */ + uint32_t max_connections; + +}; + + +/** + * Function that can be used by the transport service to transmit + * a message using the plugin using a fresh connection (even if + * we already have a connection to this peer, this function is + * required to establish a new one). + * + * @param cls closure + * @param target who should receive this message + * @param msg1 first message to transmit + * @param msg2 second message to transmit (can be NULL) + * @param timeout how long should we try to transmit these? + * @param addrlen length of the address + * @param addr the address + * @return session instance if the transmission has been scheduled + * NULL if the address format is invalid + */ +typedef void * + (*GNUNET_TRANSPORT_TransmitToAddressFunction) (void *cls, + const struct + GNUNET_PeerIdentity * target, + const struct + GNUNET_MessageHeader * msg1, + const struct + GNUNET_MessageHeader * msg2, + struct GNUNET_TIME_Relative + timeout, const void *addr, + size_t addrlen); + +/** + * Function called by the GNUNET_TRANSPORT_TransmitFunction + * upon "completion". + * + * @param cls closure + * @param service_context value passed to the transport-service + * to identify the neighbour + * @param target who was the recipient of the message? + * @param result GNUNET_OK on success + * GNUNET_SYSERR if the target disconnected; + * disconnect will ALSO be signalled using + * the ReceiveCallback. + */ +typedef void + (*GNUNET_TRANSPORT_TransmitContinuation) (void *cls, + struct ReadyList * + service_context, + const struct GNUNET_PeerIdentity * + target, int result); + +/** + * Function that can be used by the transport service to transmit + * a message using the plugin. Note that in the case of a + * peer disconnecting, the continuation MUST be called + * prior to the disconnect notification itself. This function + * will be called with this peer's HELLO message to initiate + * a fresh connection to another peer. + * + * @param cls closure + * @param plugin_context value we were asked to pass to this plugin + * to respond to the given peer (use is optional, + * but may speed up processing), can be NULL + * @param service_context value passed to the transport-service + * to identify the neighbour; NULL is used to indicate + * an urgent message. If the urgent message can not be + * scheduled for immediate transmission, the plugin is to + * call the continuation with failure immediately + * @param target who should receive this message + * @param msg the message to transmit + * @param timeout how long to wait at most for the transmission + * @param cont continuation to call once the message has + * been transmitted (or if the transport is ready + * for the next transmission call; or if the + * peer disconnected...); can be NULL + * @param cont_cls closure for cont + * @return plugin_context that should be used next time for + * sending messages to the specified peer + */ +typedef void * + (*GNUNET_TRANSPORT_TransmitFunction) (void *cls, + void *plugin_context, + struct ReadyList * service_context, + const struct GNUNET_PeerIdentity * + target, + const struct GNUNET_MessageHeader * + msg, + struct GNUNET_TIME_Relative timeout, + GNUNET_TRANSPORT_TransmitContinuation + cont, void *cont_cls); + + +/** + * Function that can be called to force a disconnect from the + * specified neighbour. This should also cancel all previously + * scheduled transmissions. Obviously the transmission may have been + * partially completed already, which is OK. The plugin is supposed + * to close the connection (if applicable) and no longer call the + * transmit continuation(s). + * + * Finally, plugin MUST NOT call the services's receive function to + * notify the service that the connection to the specified target was + * closed after a getting this call. + * + * @param cls closure + * @param plugin_context value we were asked to pass to this plugin + * to respond to the given peer (use is optional, + * but may speed up processing), can be NULL (if + * NULL was returned from the transmit function) + * @param service_context must correspond to the service context + * of the corresponding Transmit call; the plugin should + * not cancel a send call made with a different service + * context pointer! Never NULL. + * @param target peer for which the last transmission is + * to be cancelled + */ +typedef void + (*GNUNET_TRANSPORT_CancelFunction) (void *cls, + void *plugin_context, + struct ReadyList * service_context, + const struct GNUNET_PeerIdentity * + target); + + +/** + * Function called by the pretty printer for the resolved address for + * each human-readable address obtained. + * + * @param cls closure + * @param hostname one of the names for the host, NULL + * on the last call to the callback + */ +typedef void (*GNUNET_TRANSPORT_AddressStringCallback) (void *cls, + const char *address); + + +/** + * Convert the transports address to a nice, human-readable + * format. + * + * @param cls closure + * @param name name of the transport that generated the address + * @param addr one of the addresses of the host, NULL for the last address + * the specific address format depends on the transport + * @param addrlen length of the address + * @param numeric should (IP) addresses be displayed in numeric form? + * @param timeout after how long should we give up? + * @param asc function to call on each string + * @param asc_cls closure for asc + */ +typedef void + (*GNUNET_TRANSPORT_AddressPrettyPrinter) (void *cls, + const char *type, + const void *addr, + size_t addrlen, + int numeric, + struct GNUNET_TIME_Relative + timeout, + GNUNET_TRANSPORT_AddressStringCallback + asc, void *asc_cls); + + +/** + * Set a quota for receiving data from the given peer; this is a + * per-transport limit. The transport should limit its read/select + * calls to stay below the quota (in terms of incoming data). + * + * @param cls closure + * @param peer the peer for whom the quota is given + * @param quota_in quota for receiving/sending data in bytes per ms + */ +typedef void + (*GNUNET_TRANSPORT_SetQuota) (void *cls, + const struct GNUNET_PeerIdentity * target, + uint32_t quota_in); + + +/** + * Another peer has suggested an address for this + * peer and transport plugin. Check that this could be a valid + * address. If so, consider adding it to the list + * of addresses. + * + * @param addr pointer to the address + * @param addrlen length of addr + * @return GNUNET_OK if this is a plausible address for this peer + * and transport + */ +typedef int + (*GNUNET_TRANSPORT_SuggestAddress) (void *cls, + const void *addr, size_t addrlen); + +/** + * Each plugin is required to return a pointer to a struct of this + * type as the return value from its entry point. + */ +struct GNUNET_TRANSPORT_PluginFunctions +{ + + /** + * Closure for all of the callbacks. + */ + void *cls; + + /** + * Function used to send a single message to a particular + * peer using the specified address. Used to validate + * HELLOs. + */ + GNUNET_TRANSPORT_TransmitToAddressFunction send_to; + + /** + * Function that the transport service will use to transmit data to + * another peer. May be null for plugins that only support + * receiving data. After this call, the plugin call the specified + * continuation with success or error before notifying us about the + * target having disconnected. + */ + GNUNET_TRANSPORT_TransmitFunction send; + + /** + * Function that can be used to force the plugin to disconnect + * from the given peer and cancel all previous transmissions + * (and their continuationc). + */ + GNUNET_TRANSPORT_CancelFunction cancel; + + /** + * Function to pretty-print addresses. NOTE: this function is not + * yet used by transport-service, but will be used in the future + * once the transport-API has been completed. + */ + GNUNET_TRANSPORT_AddressPrettyPrinter address_pretty_printer; + + /** + * Function that the transport service can use to try to enforce a + * quota for the number of bytes received via this transport. + * Transports that can not refuse incoming data (such as UDP) + * are free to ignore these calls. + */ + GNUNET_TRANSPORT_SetQuota set_receive_quota; + + /** + * Function that will be called if another peer suggested that + * we should use a particular address (since he is reaching + * us at that address) for this transport. + */ + GNUNET_TRANSPORT_SuggestAddress address_suggested; + + /** + * Relative cost of this transport compared to others. This + * is supposed to be a static cost estimate which determines + * which plugins should not even be attempted if other, + * cheaper transports are already working. The idea is that + * the costs have roughly this relationship: + *
+   * TCP < UDP < HTTP == HTTPS < SMTP
+   * 
+ */ + unsigned int cost_estimate; +}; + + +#endif diff --git a/src/transport/plugin_transport_http.c b/src/transport/plugin_transport_http.c new file mode 100644 index 000000000..eb69f4386 --- /dev/null +++ b/src/transport/plugin_transport_http.c @@ -0,0 +1,2085 @@ +/* + This file is part of GNUnet + (C) 2003, 2004, 2005, 2006, 2007, 2008 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file transports/http.c + * @brief Implementation of the HTTP transport service + * @author Christian Grothoff + */ + +#include "platform.h" +#include "gnunet_util.h" +#include "gnunet_protocols.h" +#include "gnunet_transport.h" +#include "gnunet_stats_service.h" +#include "gnunet_upnp_service.h" +#include +#include +#include +#include "ip.h" + +#define DEBUG_HTTP GNUNET_NO + +/** + * Disable GET (for debugging only!). Must be GNUNET_YES + * in production use! + */ +#define DO_GET GNUNET_YES + +/** + * After how much time of the core not being associated with a http + * connection anymore do we close it? + * + * Needs to be larger than SECONDS_INACTIVE_DROP in + * core's connection.s + */ +#define HTTP_TIMEOUT (600 * GNUNET_CRON_SECONDS) + +/** + * How often do we re-issue GET requests? + */ +#define HTTP_GET_REFRESH (5 * GNUNET_CRON_SECONDS) + +/** + * Default maximum size of the HTTP read and write buffer. + */ +#define HTTP_BUF_SIZE (64 * 1024) + +/** + * Text of the response sent back after the last bytes of a PUT + * request have been received (just to formally obey the HTTP + * protocol). + */ +#define HTTP_PUT_RESPONSE "Thank you!" + +#define MY_TRANSPORT_NAME "HTTP" +#include "common.c" + +/** + * Client-side data per PUT request. + */ +struct HTTPPutData +{ + /** + * This is a linked list. + */ + struct HTTPPutData *next; + + /** + * Handle to our CURL request. + */ + CURL *curl_put; + + /** + * Last time we made progress with the PUT. + */ + GNUNET_CronTime last_activity; + + /** + * The message we are sending. + */ + char *msg; + + /** + * Size of msg. + */ + unsigned int size; + + /** + * Current position in msg. + */ + unsigned int pos; + + /** + * Are we done sending? Set to 1 after we + * completed sending and started to receive + * a response ("Thank you!") or once the + * timeout has been reached. + */ + int done; + +}; + +/** + * Server-side data per PUT request. + */ +struct MHDPutData +{ + /** + * This is a linked list. + */ + struct MHDPutData *next; + + /** + * MHD connection handle for this request. + */ + struct MHD_Connection *session; + + /** + * Last time we received data on this PUT + * connection. + */ + GNUNET_CronTime last_activity; + + /** + * Read buffer for the header (from PUT) + */ + char rbuff1[sizeof (GNUNET_MessageHeader)]; + + /** + * The read buffer (used only receiving PUT data). + */ + char *rbuff2; + + /** + * Number of valid bytes in rbuff1 + */ + unsigned int rpos1; + + /** + * Number of valid bytes in rbuff2 + */ + unsigned int rpos2; + + + /** + * Size of the rbuff2 buffer. + */ + unsigned int rsize2; + + /** + * Should we sent a response for this PUT yet? + */ + int ready; + + /** + * Have we sent a response for this PUT yet? + */ + int done; + +}; + +/** + * Server-side data for a GET request. + */ +struct MHDGetData +{ + + /** + * This is a linked list. + */ + struct MHDGetData *next; + + /** + * MHD connection handle for this request. + */ + struct MHD_Connection *session; + + /** + * GET session response handle + */ + struct MHD_Response *get; + + /** + * My HTTP session. + */ + struct HTTPSession *httpsession; + + /** + * The write buffer (for sending GET response) + */ + char *wbuff; + + /** + * What was the last time we were able to + * transmit data using the current get handle? + */ + GNUNET_CronTime last_get_activity; + + /** + * Current write position in wbuff + */ + unsigned int woff; + + /** + * Number of valid bytes in wbuff (starting at woff) + */ + unsigned int wpos; + + /** + * Size of the write buffer. + */ + unsigned int wsize; + +}; + +/** + * Transport Session handle. + */ +typedef struct HTTPSession +{ + + /** + * GNUNET_TSession for this session. + */ + GNUNET_TSession *tsession; + + /** + * To whom are we talking to. + */ + GNUNET_PeerIdentity sender; + + /** + * number of users of this session + */ + unsigned int users; + + /** + * Has this session been destroyed? + */ + int destroyed; + + /** + * Are we client or server? Determines which of the + * structs in the union below is being used for this + * connection! + */ + int is_client; + + /** + * Is MHD still using this session handle? + */ + int is_mhd_active; + + /** + * Data maintained for the http client-server connection + * (depends on if we are client or server). + */ + union + { + + struct + { + /** + * Active PUT requests (linked list). + */ + struct MHDPutData *puts; + +#if DO_GET + /** + * Active GET requests (linked list; most + * recent received GET is the head of the list). + */ + struct MHDGetData *gets; +#endif + + } server; + + struct + { + + /** + * Address of the other peer. + */ + HostAddress address; + +#if DO_GET + /** + * Last time the GET was active. + */ + GNUNET_CronTime last_get_activity; + + /** + * What was the last time we were able to + * transmit data using the current get handle? + */ + GNUNET_CronTime last_get_initiated; + + /** + * GET operation + */ + CURL *get; + + /** + * Read buffer for the header (from GET). + */ + char rbuff1[sizeof (GNUNET_MessageHeader)]; + + /** + * The read buffer (used only receiving GET data). + */ + char *rbuff2; + + /** + * Number of valid bytes in rbuff1 + */ + unsigned int rpos1; + + /** + * Number of valid bytes in rbuff2 + */ + unsigned int rpos2; + + /** + * Current size of the read buffer rbuff2. + */ + unsigned int rsize2; +#endif + + /** + * URL of the get and put operations. + */ + char *url; + + /** + * Linked list of PUT operations. + */ + struct HTTPPutData *puts; + + } client; + + } cs; + +} HTTPSession; + +/* *********** globals ************* */ + +static int stat_bytesReceived; + +static int stat_bytesSent; + +static int stat_bytesDropped; + +static int stat_get_issued; + +static int stat_get_received; + +static int stat_put_issued; + +static int stat_put_received; + +static int stat_select_calls; + +static int stat_send_calls; + +static int stat_connect_calls; + +static int stat_curl_send_callbacks; + +static int stat_curl_receive_callbacks; + +static int stat_mhd_access_callbacks; + +static int stat_mhd_read_callbacks; + +static int stat_mhd_close_callbacks; + +static int stat_connect_calls; + +/** + * How many requests do we have currently pending + * (with libcurl)? + */ +static unsigned int http_requests_pending; + +static int signal_pipe[2]; + +static char *proxy; + +/** + * Daemon for listening for new connections. + */ +static struct MHD_Daemon *mhd_daemon; + +/** + * Curl multi for managing client operations. + */ +static CURLM *curl_multi; + +/** + * Set to GNUNET_YES while the transport is running. + */ +static int http_running; + +/** + * Thread running libcurl activities. + */ +static struct GNUNET_ThreadHandle *curl_thread; + +/** + * Array of currently active HTTP sessions. + */ +static GNUNET_TSession **tsessions; + +/** + * Number of valid entries in tsessions. + */ +static unsigned int tsessionCount; + +/** + * Sie of the tsessions array. + */ +static unsigned int tsessionArrayLength; + +/** + * Lock for concurrent access to all structures used + * by http, including CURL. + */ +static struct GNUNET_Mutex *lock; + + +/** + * Signal select thread that its selector + * set may have changed. + */ +static void +signal_select () +{ + static char c; + WRITE (signal_pipe[1], &c, sizeof (c)); +} + +/** + * Check if we are allowed to connect to the given IP. + */ +static int +acceptPolicyCallback (void *cls, + const struct sockaddr *addr, socklen_t addr_len) +{ + if (GNUNET_NO != is_rejected_tester (addr, addr_len)) + return MHD_NO; + return MHD_YES; +} + +/** + * Disconnect from a remote node. May only be called + * on sessions that were acquired by the caller first. + * For the core, aquiration means to call associate or + * connect. The number of disconnects must match the + * number of calls to connect+associate. + * + * Sessions are actually discarded in cleanup_connections. + * + * + * @param tsession the session that is closed + * @return GNUNET_OK on success, GNUNET_SYSERR if the operation failed + */ +static int +httpDisconnect (GNUNET_TSession * tsession) +{ + HTTPSession *httpsession = tsession->internal; + if (httpsession == NULL) + { + GNUNET_free (tsession); + return GNUNET_OK; + } + GNUNET_mutex_lock (lock); + httpsession->users--; + GNUNET_mutex_unlock (lock); + return GNUNET_OK; +} + +static void +destroy_tsession (GNUNET_TSession * tsession) +{ + HTTPSession *httpsession = tsession->internal; + struct HTTPPutData *pos; + struct HTTPPutData *next; +#if DO_GET + struct MHDGetData *gpos; + struct MHDGetData *gnext; +#endif + struct MHD_Response *r; + int i; + + GNUNET_mutex_lock (lock); + for (i = 0; i < tsessionCount; i++) + { + if (tsessions[i] == tsession) + { + tsessions[i] = tsessions[--tsessionCount]; + break; + } + } + if (httpsession->is_client) + { +#if DO_GET + curl_multi_remove_handle (curl_multi, httpsession->cs.client.get); + http_requests_pending--; + signal_select (); + curl_easy_cleanup (httpsession->cs.client.get); + GNUNET_array_grow (httpsession->cs.client.rbuff2, + httpsession->cs.client.rsize2, 0); +#endif + GNUNET_free_non_null (httpsession->cs.client.url); + pos = httpsession->cs.client.puts; + while (pos != NULL) + { + next = pos->next; + curl_multi_remove_handle (curl_multi, pos->curl_put); + http_requests_pending--; + signal_select (); + curl_easy_cleanup (pos->curl_put); + GNUNET_free (pos->msg); + GNUNET_free (pos); + pos = next; + } + GNUNET_free (httpsession); + GNUNET_free (tsession); + } + else + { + httpsession->destroyed = GNUNET_YES; + GNUNET_GE_BREAK (NULL, httpsession->cs.server.puts == NULL); +#if DO_GET + gpos = httpsession->cs.server.gets; + while (gpos != NULL) + { + GNUNET_array_grow (gpos->wbuff, gpos->wsize, 0); + r = gpos->get; + gpos->get = NULL; + gnext = gpos->next; + MHD_destroy_response (r); + gpos = gnext; + } + httpsession->cs.server.gets = NULL; +#endif + GNUNET_free (httpsession->tsession); + GNUNET_free (httpsession); + } + GNUNET_mutex_unlock (lock); +} + +/** + * MHD is done handling a request. Cleanup + * the respective transport state. + */ +static void +requestCompletedCallback (void *unused, + struct MHD_Connection *session, + void **httpSessionCache) +{ + HTTPSession *httpsession = *httpSessionCache; + struct MHDPutData *pprev; + struct MHDPutData *ppos; +#if DO_GET + struct MHDGetData *gprev; + struct MHDGetData *gpos; +#endif + + if (stats != NULL) + stats->change (stat_mhd_close_callbacks, 1); + if (httpsession == NULL) + return; /* oops */ + GNUNET_GE_ASSERT (NULL, !httpsession->is_client); + pprev = NULL; + ppos = httpsession->cs.server.puts; + while (ppos != NULL) + { + if (ppos->session == session) + { + ppos->last_activity = 0; + signal_select (); + return; + } + pprev = ppos; + ppos = ppos->next; + } +#if DO_GET + gprev = NULL; + gpos = httpsession->cs.server.gets; + while (gpos != NULL) + { + if (gpos->session == session) + { + gpos->last_get_activity = 0; + signal_select (); + return; + } + gprev = gpos; + gpos = gpos->next; + } +#endif + httpsession->is_mhd_active--; +} + +/** + * A (core) Session is to be associated with a transport session. The + * transport service may want to know in order to call back on the + * core if the connection is being closed. Associate can also be + * called to test if it would be possible to associate the session + * later, in this case the argument session is NULL. This can be used + * to test if the connection must be closed by the core or if the core + * can assume that it is going to be self-managed (if associate + * returns GNUNET_OK and session was NULL, the transport layer is responsible + * for eventually freeing resources associated with the tesession). If + * session is not NULL, the core takes responsbility for eventually + * calling disconnect. + * + * @param tsession the session handle passed along + * from the call to receive that was made by the transport + * layer + * @return GNUNET_OK if the session could be associated, + * GNUNET_SYSERR if not. + */ +static int +httpAssociate (GNUNET_TSession * tsession) +{ + HTTPSession *httpSession; + + if (tsession == NULL) + { + GNUNET_GE_BREAK (NULL, 0); + return GNUNET_SYSERR; + } + httpSession = tsession->internal; + GNUNET_mutex_lock (lock); + if (httpSession->destroyed == GNUNET_YES) + { + GNUNET_mutex_unlock (lock); + return GNUNET_SYSERR; + } + httpSession->users++; + GNUNET_mutex_unlock (lock); + return GNUNET_OK; +} + +/** + * Add a new session to the array watched by the select thread. Grows + * the array if needed. If the caller wants to do anything useful + * with the return value, it must have the lock before + * calling. It is ok to call this function without holding lock if + * the return value is ignored. + */ +static unsigned int +addTSession (GNUNET_TSession * tsession) +{ + unsigned int i; + + GNUNET_mutex_lock (lock); + if (tsessionCount == tsessionArrayLength) + GNUNET_array_grow (tsessions, tsessionArrayLength, + tsessionArrayLength * 2); + i = tsessionCount; + tsessions[tsessionCount++] = tsession; + GNUNET_mutex_unlock (lock); + return i; +} + +#if DO_GET +/** + * Callback for processing GET requests if our side is the + * MHD HTTP server. + * + * @param cls the HTTP session + * @param pos read-offset in the stream + * @param buf where to write the data + * @param max how much data to write (at most) + * @return number of bytes written, 0 is allowed! + */ +static int +contentReaderCallback (void *cls, uint64_t pos, char *buf, int max) +{ + struct MHDGetData *mgd = cls; + + if (stats != NULL) + stats->change (stat_mhd_read_callbacks, 1); + GNUNET_mutex_lock (lock); + if (mgd->wpos < max) + max = mgd->wpos; + memcpy (buf, &mgd->wbuff[mgd->woff], max); + mgd->wpos -= max; + mgd->woff += max; + if (max > 0) + mgd->last_get_activity = GNUNET_get_time (); + if (mgd->wpos == 0) + mgd->woff = 0; + GNUNET_mutex_unlock (lock); +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP returns %u bytes in MHD's GET handler.\n", max); +#endif + if (stats != NULL) + stats->change (stat_bytesSent, max); + if ((max == 0) && (mgd->httpsession->cs.server.gets != mgd)) + return -1; /* end of response (another GET replaces this one) */ + return max; +} +#endif + +#if DO_GET +/** + * Notification that libmicrohttpd no longer needs the + * response object. + */ +static void +contentReaderFreeCallback (void *cls) +{ + struct MHDGetData *mgd = cls; + + GNUNET_GE_ASSERT (NULL, mgd->get == NULL); + GNUNET_array_grow (mgd->wbuff, mgd->wsize, 0); + GNUNET_free (mgd); +} +#endif + +/** + * Process GET or PUT request received via MHD. For + * GET, queue response that will send back our pending + * messages. For PUT, process incoming data and send + * to GNUnet core. In either case, check if a session + * already exists and create a new one if not. + */ +static int +accessHandlerCallback (void *cls, + struct MHD_Connection *session, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t * upload_data_size, void **httpSessionCache) +{ + GNUNET_TSession *tsession; + struct MHDPutData *put; + struct MHDGetData *get; + HTTPSession *httpSession; + struct MHD_Response *response; + GNUNET_HashCode client; + int i; + unsigned int have; + GNUNET_MessageHeader *hdr; + GNUNET_TransportPacket *mp; + unsigned int cpy; + unsigned int poff; + + if (stats != NULL) + stats->change (stat_mhd_access_callbacks, 1); +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP/MHD receives `%s' request.\n", method); +#endif + /* convert URL to sender peer id */ + if ((strlen (url) < 2) + || (GNUNET_OK != GNUNET_enc_to_hash (&url[1], &client))) + { + /* invalid request */ + /* GNUNET_GE_BREAK_OP (NULL, 0); -- this happens a lot, most likely + somebody scanning for MyDoom.X-opened backdoors */ + return MHD_NO; + } + + /* check if we already have a session for this */ + httpSession = *httpSessionCache; + if (httpSession == NULL) + { + /* new http connection */ + if (stats != NULL) + { + if (0 == strcasecmp (MHD_HTTP_METHOD_PUT, method)) + stats->change (stat_put_received, 1); + else + stats->change (stat_get_received, 1); + } + GNUNET_mutex_lock (lock); + for (i = 0; i < tsessionCount; i++) + { + tsession = tsessions[i]; + httpSession = tsession->internal; + if ((0 == + memcmp (&httpSession->sender, &client, + sizeof (GNUNET_HashCode))) + && (httpSession->is_client == GNUNET_NO)) + break; + tsession = NULL; + httpSession = NULL; + } + GNUNET_mutex_unlock (lock); + } + /* create new session if necessary */ + if (httpSession == NULL) + { +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP/MHD creates new session for request from `%s'.\n", + &url[1]); +#endif + httpSession = GNUNET_malloc (sizeof (HTTPSession)); + memset (httpSession, 0, sizeof (HTTPSession)); + httpSession->sender.hashPubKey = client; + httpSession->users = 0; /* MHD */ + tsession = GNUNET_malloc (sizeof (GNUNET_TSession)); + memset (tsession, 0, sizeof (GNUNET_TSession)); + tsession->ttype = GNUNET_TRANSPORT_PROTOCOL_NUMBER_HTTP; + tsession->internal = httpSession; + tsession->peer.hashPubKey = client; + httpSession->tsession = tsession; + addTSession (tsession); + } + if (*httpSessionCache == NULL) + { + httpSession->is_mhd_active++; + *httpSessionCache = httpSession; + } + GNUNET_mutex_lock (lock); +#if DO_GET + if (0 == strcasecmp (MHD_HTTP_METHOD_GET, method)) + { +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP/MHD receives GET request from `%s'.\n", &url[1]); +#endif + + /* handle get; create response object if we do not + have one already */ + get = GNUNET_malloc (sizeof (struct MHDGetData)); + memset (get, 0, sizeof (struct MHDGetData)); + get->next = httpSession->cs.server.gets; + httpSession->cs.server.gets = get; + get->session = session; + get->httpsession = httpSession; + get->last_get_activity = GNUNET_get_time (); + get->get = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN, + 64 * 1024, + contentReaderCallback, + get, + contentReaderFreeCallback); + MHD_queue_response (session, MHD_HTTP_OK, get->get); + GNUNET_mutex_unlock (lock); + return MHD_YES; + } +#endif + if (0 == strcasecmp (MHD_HTTP_METHOD_PUT, method)) + { +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP/MHD receives PUT request from `%s' with %u bytes.\n", + &url[1], *upload_data_size); +#endif + put = httpSession->cs.server.puts; + while ((put != NULL) && (put->session != session)) + put = put->next; + if (put == NULL) + { + put = GNUNET_malloc (sizeof (struct MHDPutData)); + memset (put, 0, sizeof (struct MHDPutData)); + put->next = httpSession->cs.server.puts; + httpSession->cs.server.puts = put; + put->session = session; + } + put->last_activity = GNUNET_get_time (); + + /* handle put (upload_data!) */ + poff = 0; + have = *upload_data_size; + if (stats != NULL) + stats->change (stat_bytesReceived, have); + *upload_data_size = 0; /* we will always process everything */ + if ((have == 0) && (put->done == GNUNET_NO) + && (put->ready == GNUNET_YES)) + { + put->done = GNUNET_YES; + /* end of upload, send response! */ +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP/MHD queues dummy response to completed PUT request.\n"); +#endif + response = + MHD_create_response_from_data (strlen (HTTP_PUT_RESPONSE), + HTTP_PUT_RESPONSE, MHD_NO, MHD_NO); + MHD_queue_response (session, MHD_HTTP_OK, response); + MHD_destroy_response (response); + GNUNET_mutex_unlock (lock); + return MHD_YES; + } + while (have > 0) + { + put->ready = GNUNET_NO; + if (put->rpos1 < sizeof (GNUNET_MessageHeader)) + { + cpy = sizeof (GNUNET_MessageHeader) - put->rpos1; + if (cpy > have) + cpy = have; + memcpy (&put->rbuff1[put->rpos1], &upload_data[poff], cpy); + put->rpos1 += cpy; + have -= cpy; + poff += cpy; + put->rpos2 = 0; + } + if (put->rpos1 < sizeof (GNUNET_MessageHeader)) + break; + hdr = (GNUNET_MessageHeader *) put->rbuff1; + GNUNET_array_grow (put->rbuff2, + put->rsize2, + ntohs (hdr->size) - + sizeof (GNUNET_MessageHeader)); + if (put->rpos2 < ntohs (hdr->size) - sizeof (GNUNET_MessageHeader)) + { + cpy = + ntohs (hdr->size) - sizeof (GNUNET_MessageHeader) - + put->rpos2; + if (cpy > have) + cpy = have; + memcpy (&put->rbuff2[put->rpos2], &upload_data[poff], cpy); + have -= cpy; + poff += cpy; + put->rpos2 += cpy; + } + if (put->rpos2 < ntohs (hdr->size) - sizeof (GNUNET_MessageHeader)) + break; + mp = GNUNET_malloc (sizeof (GNUNET_TransportPacket)); + mp->msg = put->rbuff2; + mp->sender = httpSession->sender; + mp->tsession = httpSession->tsession; + mp->size = ntohs (hdr->size) - sizeof (GNUNET_MessageHeader); +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP/MHD passes %u bytes to core (received via PUT request).\n", + mp->size); +#endif + coreAPI->receive (mp); + put->rbuff2 = NULL; + put->rpos2 = 0; + put->rsize2 = 0; + put->rpos1 = 0; + put->ready = GNUNET_YES; + } + GNUNET_mutex_unlock (lock); + return MHD_YES; + } + GNUNET_mutex_unlock (lock); + GNUNET_GE_BREAK_OP (NULL, 0); /* invalid request */ + return MHD_NO; +} + +#if DO_GET +/** + * Process downloaded bits (from GET via CURL). + */ +static size_t +receiveContentCallback (void *ptr, size_t size, size_t nmemb, void *ctx) +{ + HTTPSession *httpSession = ctx; + const char *inbuf = ptr; + size_t have = size * nmemb; + size_t poff = 0; + size_t cpy; + GNUNET_MessageHeader *hdr; + GNUNET_TransportPacket *mp; + + if (stats != NULL) + stats->change (stat_curl_receive_callbacks, 1); + httpSession->cs.client.last_get_activity = GNUNET_get_time (); +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP/CURL receives %u bytes as response to GET.\n", + size * nmemb); +#endif + while (have > 0) + { + if (httpSession->cs.client.rpos1 < sizeof (GNUNET_MessageHeader)) + { + cpy = sizeof (GNUNET_MessageHeader) - httpSession->cs.client.rpos1; + if (cpy > have) + cpy = have; + memcpy (&httpSession->cs.client. + rbuff1[httpSession->cs.client.rpos1], &inbuf[poff], cpy); + httpSession->cs.client.rpos1 += cpy; + have -= cpy; + poff += cpy; + httpSession->cs.client.rpos2 = 0; + } + if (httpSession->cs.client.rpos1 < sizeof (GNUNET_MessageHeader)) + break; + hdr = (GNUNET_MessageHeader *) httpSession->cs.client.rbuff1; + GNUNET_array_grow (httpSession->cs.client.rbuff2, + httpSession->cs.client.rsize2, + ntohs (hdr->size) - sizeof (GNUNET_MessageHeader)); + if (httpSession->cs.client.rpos2 < + ntohs (hdr->size) - sizeof (GNUNET_MessageHeader)) + { + cpy = + ntohs (hdr->size) - sizeof (GNUNET_MessageHeader) - + httpSession->cs.client.rpos2; + if (cpy > have) + cpy = have; + memcpy (&httpSession->cs.client. + rbuff2[httpSession->cs.client.rpos2], &inbuf[poff], cpy); + have -= cpy; + poff += cpy; + httpSession->cs.client.rpos2 += cpy; + } + if (httpSession->cs.client.rpos2 < + ntohs (hdr->size) - sizeof (GNUNET_MessageHeader)) + break; + mp = GNUNET_malloc (sizeof (GNUNET_TransportPacket)); + mp->msg = httpSession->cs.client.rbuff2; + mp->sender = httpSession->sender; + mp->tsession = httpSession->tsession; + mp->size = ntohs (hdr->size) - sizeof (GNUNET_MessageHeader); + coreAPI->receive (mp); + httpSession->cs.client.rbuff2 = NULL; + httpSession->cs.client.rpos2 = 0; + httpSession->cs.client.rsize2 = 0; + httpSession->cs.client.rpos1 = 0; + } + if (stats != NULL) + stats->change (stat_bytesReceived, size * nmemb); + return size * nmemb; +} +#endif + +/** + * Provide bits for upload: we're using CURL for a PUT request + * and now need to provide data from the message we are transmitting. + */ +static size_t +sendContentCallback (void *ptr, size_t size, size_t nmemb, void *ctx) +{ + struct HTTPPutData *put = ctx; + size_t max = size * nmemb; + + if (stats != NULL) + stats->change (stat_curl_send_callbacks, 1); + put->last_activity = GNUNET_get_time (); + if (max > put->size - put->pos) + max = put->size - put->pos; + memcpy (ptr, &put->msg[put->pos], max); + put->pos += max; +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP/CURL sends %u bytes in PUT request.\n", max); +#endif + if (stats != NULL) + stats->change (stat_bytesSent, max); + return max; +} + +#define CURL_EASY_SETOPT(c, a, b) do { ret = curl_easy_setopt(c, a, b); if (ret != CURLE_OK) GNUNET_GE_LOG(coreAPI->ectx, GNUNET_GE_WARNING | GNUNET_GE_USER | GNUNET_GE_BULK, _("%s failed at %s:%d: `%s'\n"), "curl_easy_setopt", __FILE__, __LINE__, curl_easy_strerror(ret)); } while (0); +#define IP_BUF_LEN 128 + +static void +create_session_url (HTTPSession * httpSession) +{ + char buf[IP_BUF_LEN]; + char *url; + GNUNET_EncName enc; + unsigned short available; + const char *obr; + const char *cbr; + const HostAddress *haddr = + (const HostAddress *) &httpSession->cs.client.address; + + url = httpSession->cs.client.url; + if (url == NULL) + { + GNUNET_hash_to_enc (&coreAPI->my_identity->hashPubKey, &enc); + available = ntohs (haddr->availability) & available_protocols; + if (available == (VERSION_AVAILABLE_IPV4 | VERSION_AVAILABLE_IPV6)) + { + if (GNUNET_random_u32 (GNUNET_RANDOM_QUALITY_WEAK, 2) == 0) + available = VERSION_AVAILABLE_IPV4; + else + available = VERSION_AVAILABLE_IPV6; + } + if ((available & VERSION_AVAILABLE_IPV4) > 0) + { + if (NULL == inet_ntop (AF_INET, &haddr->ipv4, buf, IP_BUF_LEN)) + { + /* log? */ + return; + } + obr = ""; + cbr = ""; + } + else if ((available & VERSION_AVAILABLE_IPV6) > 0) + { + if (NULL == inet_ntop (AF_INET6, &haddr->ipv6, buf, IP_BUF_LEN)) + { + /* log? */ + return; + } + obr = "["; + cbr = "]"; + } + else + return; /* error */ + url = GNUNET_malloc (64 + sizeof (GNUNET_EncName) + strlen (buf)); + GNUNET_snprintf (url, + 64 + sizeof (GNUNET_EncName), + "http://%s%s%s:%u/%s", obr, buf, cbr, + ntohs (haddr->port), &enc); + httpSession->cs.client.url = url; + } +} + +#if DO_GET +/** + * Try to do a GET on the other peer of the given + * http session. + * + * @return GNUNET_OK on success, GNUNET_SYSERR on error + */ +static int +create_curl_get (HTTPSession * httpSession) +{ + CURL *curl_get; + CURLcode ret; + CURLMcode mret; + GNUNET_CronTime now; + + if (httpSession->cs.client.url == NULL) + return GNUNET_SYSERR; + curl_get = httpSession->cs.client.get; + if (curl_get != NULL) + { + GNUNET_mutex_lock (lock); + curl_multi_remove_handle (curl_multi, curl_get); + http_requests_pending--; + signal_select (); + curl_easy_cleanup (curl_get); + GNUNET_mutex_unlock (lock); + httpSession->cs.client.get = NULL; + } + curl_get = curl_easy_init (); + if (curl_get == NULL) + return GNUNET_SYSERR; + /* create GET */ + CURL_EASY_SETOPT (curl_get, CURLOPT_FAILONERROR, 1); + CURL_EASY_SETOPT (curl_get, CURLOPT_URL, httpSession->cs.client.url); + if (strlen (proxy) > 0) + CURL_EASY_SETOPT (curl_get, CURLOPT_PROXY, proxy); + CURL_EASY_SETOPT (curl_get, CURLOPT_BUFFERSIZE, 32 * 1024); + if (0 == strncmp (httpSession->cs.client.url, "http", 4)) + CURL_EASY_SETOPT (curl_get, CURLOPT_USERAGENT, "GNUnet-http"); +#if 0 + CURL_EASY_SETOPT (curl_get, CURLOPT_VERBOSE, 1); +#endif + CURL_EASY_SETOPT (curl_get, CURLOPT_CONNECTTIMEOUT, 150L); + /* NOTE: use of CONNECTTIMEOUT without also + setting NOSIGNAL results in really weird + crashes on my system! */ + CURL_EASY_SETOPT (curl_get, CURLOPT_NOSIGNAL, 1); + CURL_EASY_SETOPT (curl_get, CURLOPT_TIMEOUT, 150L); + CURL_EASY_SETOPT (curl_get, CURLOPT_WRITEFUNCTION, &receiveContentCallback); + CURL_EASY_SETOPT (curl_get, CURLOPT_WRITEDATA, httpSession); + CURL_EASY_SETOPT (curl_get, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + if (ret != CURLE_OK) + { + curl_easy_cleanup (curl_get); + return GNUNET_SYSERR; + } + GNUNET_mutex_lock (lock); + mret = curl_multi_add_handle (curl_multi, curl_get); + http_requests_pending++; + GNUNET_mutex_unlock (lock); + if (stats != NULL) + stats->change (stat_get_issued, 1); + if (mret != CURLM_OK) + { + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_ERROR | GNUNET_GE_ADMIN | GNUNET_GE_USER | + GNUNET_GE_BULK, _("%s failed at %s:%d: `%s'\n"), + "curl_multi_add_handle", __FILE__, __LINE__, + curl_multi_strerror (mret)); + curl_easy_cleanup (curl_get); + return GNUNET_SYSERR; + } + signal_select (); + now = GNUNET_get_time (); + httpSession->cs.client.last_get_activity = now; + httpSession->cs.client.get = curl_get; + httpSession->cs.client.last_get_initiated = now; +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP/CURL initiated GET request.\n"); +#endif + return GNUNET_OK; +} +#endif + +/** + * Establish a connection to a remote node. + * + * @param hello the hello-Message for the target node + * @param tsessionPtr the session handle that is set + * @return GNUNET_OK on success, GNUNET_SYSERR if the operation failed + */ +static int +httpConnect (const GNUNET_MessageHello * hello, + GNUNET_TSession ** tsessionPtr, int may_reuse) +{ + const HostAddress *haddr = (const HostAddress *) &hello[1]; + GNUNET_TSession *tsession; + HTTPSession *httpSession; + int i; + + if (stats != NULL) + stats->change (stat_connect_calls, 1); + /* check if we have a session pending for this peer */ + tsession = NULL; + if (may_reuse) + { + GNUNET_mutex_lock (lock); + for (i = 0; i < tsessionCount; i++) + { + if (0 == memcmp (&hello->senderIdentity, + &tsessions[i]->peer, sizeof (GNUNET_PeerIdentity))) + { + tsession = tsessions[i]; + break; + } + } + if ((tsession != NULL) && (GNUNET_OK == httpAssociate (tsession))) + { + *tsessionPtr = tsession; + GNUNET_mutex_unlock (lock); + return GNUNET_OK; + } + GNUNET_mutex_unlock (lock); + } + /* no session pending, initiate a new one! */ + httpSession = GNUNET_malloc (sizeof (HTTPSession)); + memset (httpSession, 0, sizeof (HTTPSession)); + httpSession->sender = hello->senderIdentity; + httpSession->users = 1; /* us only, core has not seen this tsession! */ + httpSession->is_client = GNUNET_YES; + httpSession->cs.client.address = *haddr; + tsession = GNUNET_malloc (sizeof (GNUNET_TSession)); + memset (tsession, 0, sizeof (GNUNET_TSession)); + httpSession->tsession = tsession; + tsession->ttype = GNUNET_TRANSPORT_PROTOCOL_NUMBER_HTTP; + tsession->internal = httpSession; + tsession->peer = hello->senderIdentity; + create_session_url (httpSession); +#if DO_GET + if (GNUNET_OK != create_curl_get (httpSession)) + { + GNUNET_free (tsession); + GNUNET_free (httpSession); + return GNUNET_SYSERR; + } +#endif + /* PUTs will be created as needed */ + addTSession (tsession); + *tsessionPtr = tsession; +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP/CURL initiated connection to `%s'.\n", + httpSession->cs.client.url); +#endif + return GNUNET_OK; +} + +/** + * We received the "Thank you!" response to a PUT. + * Discard the data (not useful) and mark the PUT + * operation as completed. + */ +static size_t +discardContentCallback (void *data, size_t size, size_t nmemb, void *put_cls) +{ + struct HTTPPutData *put = put_cls; + /* this condition should pretty much always be + true; just checking here in case the PUT + response comes early somehow */ + if (put->pos == put->size) + put->done = GNUNET_YES; + return size * nmemb; +} + +/** + * Create a new PUT request for the given PUT data. + */ +static int +create_curl_put (HTTPSession * httpSession, struct HTTPPutData *put) +{ + CURL *curl_put; + CURLcode ret; + CURLMcode mret; + long size; + + /* we should have initiated a GET earlier, + so URL must not be NULL here */ + if (httpSession->cs.client.url == NULL) + return GNUNET_SYSERR; + curl_put = curl_easy_init (); + if (curl_put == NULL) + return GNUNET_SYSERR; + CURL_EASY_SETOPT (curl_put, CURLOPT_FAILONERROR, 1); + CURL_EASY_SETOPT (curl_put, CURLOPT_URL, httpSession->cs.client.url); + if (strlen (proxy) > 0) + CURL_EASY_SETOPT (curl_put, CURLOPT_PROXY, proxy); + CURL_EASY_SETOPT (curl_put, CURLOPT_BUFFERSIZE, put->size); + if (0 == strncmp (httpSession->cs.client.url, "http", 4)) + CURL_EASY_SETOPT (curl_put, CURLOPT_USERAGENT, "GNUnet-http"); + CURL_EASY_SETOPT (curl_put, CURLOPT_UPLOAD, 1); +#if 0 + CURL_EASY_SETOPT (curl_put, CURLOPT_VERBOSE, 1); +#endif + CURL_EASY_SETOPT (curl_put, CURLOPT_CONNECTTIMEOUT, 150L); + /* NOTE: use of CONNECTTIMEOUT without also + setting NOSIGNAL results in really weird + crashes on my system! */ + CURL_EASY_SETOPT (curl_put, CURLOPT_NOSIGNAL, 1); + CURL_EASY_SETOPT (curl_put, CURLOPT_TIMEOUT, 150L); + size = put->size; + CURL_EASY_SETOPT (curl_put, CURLOPT_INFILESIZE, size); + CURL_EASY_SETOPT (curl_put, CURLOPT_READFUNCTION, &sendContentCallback); + CURL_EASY_SETOPT (curl_put, CURLOPT_READDATA, put); + CURL_EASY_SETOPT (curl_put, CURLOPT_WRITEFUNCTION, &discardContentCallback); + CURL_EASY_SETOPT (curl_put, CURLOPT_WRITEDATA, put); + CURL_EASY_SETOPT (curl_put, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + if (ret != CURLE_OK) + { + curl_easy_cleanup (curl_put); + return GNUNET_SYSERR; + } + GNUNET_mutex_lock (lock); + mret = curl_multi_add_handle (curl_multi, curl_put); + http_requests_pending++; + GNUNET_mutex_unlock (lock); + if (stats != NULL) + stats->change (stat_put_issued, 1); + if (mret != CURLM_OK) + { + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_ERROR | GNUNET_GE_ADMIN | GNUNET_GE_USER | + GNUNET_GE_BULK, _("%s failed at %s:%d: `%s'\n"), + "curl_multi_add_handle", __FILE__, __LINE__, + curl_multi_strerror (mret)); + return GNUNET_SYSERR; + } + signal_select (); + put->curl_put = curl_put; +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP/CURL initiated PUT request to `%s'.\n", + httpSession->cs.client.url); +#endif + return GNUNET_OK; +} + + +/** + * Test if the transport would even try to send + * a message of the given size and importance + * for the given session.
+ * This function is used to check if the core should + * even bother to construct (and encrypt) this kind + * of message. + * + * @return GNUNET_YES if the transport would try (i.e. queue + * the message or call the OS to send), + * GNUNET_NO if the transport would just drop the message, + * GNUNET_SYSERR if the size/session is invalid + */ +static int +httpTestWouldTry (GNUNET_TSession * tsession, const unsigned int size, + int important) +{ + HTTPSession *httpSession = tsession->internal; + struct MHDGetData *get; + int ret; + + if (size >= GNUNET_MAX_BUFFER_SIZE - sizeof (GNUNET_MessageHeader)) + { + GNUNET_GE_BREAK (coreAPI->ectx, 0); + return GNUNET_SYSERR; + } + if (size == 0) + { + GNUNET_GE_BREAK (coreAPI->ectx, 0); + return GNUNET_SYSERR; + } + if (httpSession->is_client) + { + /* client */ + if ((important != GNUNET_YES) && (httpSession->cs.client.puts != NULL)) + return GNUNET_NO; + return GNUNET_YES; + } + else + { + /* server */ + GNUNET_mutex_lock (lock); + get = httpSession->cs.server.gets; + if (get == NULL) + ret = GNUNET_NO; + else + { + if (get->wsize == 0) + ret = GNUNET_YES; + else if ((get->wpos + size > get->wsize) + && (important != GNUNET_YES)) + ret = GNUNET_NO; + else + ret = GNUNET_YES; + } + GNUNET_mutex_unlock (lock); + return ret; + } +} + + +/** + * Send a message to the specified remote node. + * + * @param tsession the GNUNET_MessageHello identifying the remote node + * @param msg the message + * @param size the size of the message + * @return GNUNET_SYSERR on error, GNUNET_OK on success, GNUNET_NO if queue is full + */ +static int +httpSend (GNUNET_TSession * tsession, + const void *msg, unsigned int size, int important) +{ + HTTPSession *httpSession = tsession->internal; + struct HTTPPutData *putData; + GNUNET_MessageHeader *hdr; +#if DO_GET + struct MHDGetData *getData; + char *tmp; +#endif + + if (stats != NULL) + stats->change (stat_send_calls, 1); + if (httpSession->is_client) + { + /* we need to do a PUT (we are the client) */ + if (size >= GNUNET_MAX_BUFFER_SIZE) + return GNUNET_SYSERR; + if (size == 0) + { + GNUNET_GE_BREAK (NULL, 0); + return GNUNET_SYSERR; + } + if (important != GNUNET_YES) + { + GNUNET_mutex_lock (lock); + if (httpSession->cs.client.puts != NULL) + { + /* do not queue more than one unimportant PUT at a time */ + signal_select (); /* do clean up now! */ + GNUNET_mutex_unlock (lock); + if (stats != NULL) + stats->change (stat_bytesDropped, size); + + return GNUNET_NO; + } + GNUNET_mutex_unlock (lock); + } + putData = GNUNET_malloc (sizeof (struct HTTPPutData)); + memset (putData, 0, sizeof (struct HTTPPutData)); + putData->msg = GNUNET_malloc (size + sizeof (GNUNET_MessageHeader)); + hdr = (GNUNET_MessageHeader *) putData->msg; + hdr->size = htons (size + sizeof (GNUNET_MessageHeader)); + hdr->type = htons (0); + memcpy (&putData->msg[sizeof (GNUNET_MessageHeader)], msg, size); + putData->size = size + sizeof (GNUNET_MessageHeader); + putData->last_activity = GNUNET_get_time (); + if (GNUNET_OK != create_curl_put (httpSession, putData)) + { + GNUNET_free (putData->msg); + GNUNET_free (putData); + return GNUNET_SYSERR; + } + GNUNET_mutex_lock (lock); + putData->next = httpSession->cs.client.puts; + httpSession->cs.client.puts = putData; + GNUNET_mutex_unlock (lock); + return GNUNET_OK; + } + + /* httpSession->isClient == false, respond to a GET (we + hopefully have one or will have one soon) */ +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP/MHD queues %u bytes to be sent as response to GET as soon as possible.\n", + size); +#endif +#if DO_GET + GNUNET_mutex_lock (lock); + getData = httpSession->cs.server.gets; + if (getData == NULL) + { + GNUNET_mutex_unlock (lock); + return GNUNET_SYSERR; + } + if (getData->wsize == 0) + GNUNET_array_grow (getData->wbuff, getData->wsize, HTTP_BUF_SIZE); + size += sizeof (GNUNET_MessageHeader); + if (getData->wpos + size > getData->wsize) + { + /* need to grow or discard */ + if (!important) + { + GNUNET_mutex_unlock (lock); + return GNUNET_NO; + } + tmp = GNUNET_malloc (getData->wpos + size); + memcpy (tmp, &getData->wbuff[getData->woff], getData->wpos); + hdr = (GNUNET_MessageHeader *) & tmp[getData->wpos]; + hdr->type = htons (0); + hdr->size = htons (size); + memcpy (&hdr[1], msg, size - sizeof (GNUNET_MessageHeader)); + GNUNET_free (getData->wbuff); + getData->wbuff = tmp; + getData->wsize = getData->wpos + size; + getData->woff = 0; + getData->wpos = getData->wpos + size; + } + else + { + /* fits without growing */ + if (getData->wpos + getData->woff + size > getData->wsize) + { + /* need to compact first */ + memmove (getData->wbuff, + &getData->wbuff[getData->woff], getData->wpos); + getData->woff = 0; + } + /* append */ + hdr = + (GNUNET_MessageHeader *) & getData->wbuff[getData->woff + + getData->wpos]; + hdr->size = htons (size); + hdr->type = htons (0); + memcpy (&hdr[1], msg, size - sizeof (GNUNET_MessageHeader)); + getData->wpos += size; + } + signal_select (); + GNUNET_mutex_unlock (lock); +#endif + return GNUNET_OK; +} + +/** + * Function called to cleanup dead connections + * (completed PUTs, GETs that have timed out, + * etc.). Also re-vives GETs that have timed out + * if we are still interested in the connection. + */ +static void +cleanup_connections () +{ + int i; + HTTPSession *s; + struct HTTPPutData *prev; + struct HTTPPutData *pos; + struct MHDPutData *mpos; + struct MHDPutData *mprev; +#if DO_GET + struct MHD_Response *r; + struct MHDGetData *gpos; + struct MHDGetData *gnext; +#endif + GNUNET_CronTime now; + + GNUNET_mutex_lock (lock); + now = GNUNET_get_time (); + for (i = 0; i < tsessionCount; i++) + { + s = tsessions[i]->internal; + if (s->is_client) + { + if ((s->cs.client.puts == NULL) && (s->users == 0) +#if DO_GET + && (s->cs.client.last_get_activity + HTTP_TIMEOUT < now) +#endif + ) + { +#if DO_GET +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | + GNUNET_GE_USER, + "HTTP transport destroys old (%llu ms) unused client session\n", + now - s->cs.client.last_get_activity); +#endif +#endif + destroy_tsession (tsessions[i]); + i--; + continue; + } + + prev = NULL; + pos = s->cs.client.puts; + while (pos != NULL) + { + if (pos->last_activity + HTTP_TIMEOUT < now) + pos->done = GNUNET_YES; + if (pos->done) + { + if (prev == NULL) + s->cs.client.puts = pos->next; + else + prev->next = pos->next; + GNUNET_free (pos->msg); + curl_multi_remove_handle (curl_multi, pos->curl_put); + http_requests_pending--; + signal_select (); + curl_easy_cleanup (pos->curl_put); + GNUNET_free (pos); + if (prev == NULL) + pos = s->cs.client.puts; + else + pos = prev->next; + continue; + } + prev = pos; + pos = pos->next; + } +#if DO_GET + if ((s->cs.client.last_get_activity + HTTP_TIMEOUT < now) && + ((s->users > 0) || (s->cs.client.puts != NULL)) && + ((s->cs.client.last_get_initiated + HTTP_GET_REFRESH > now) || + (s->cs.client.get == NULL)) && + ((s->cs.client.get == NULL) || + (s->cs.client.last_get_activity + HTTP_GET_REFRESH / 2 < now))) + create_curl_get (s); +#endif + } + else + { + mpos = s->cs.server.puts; + mprev = NULL; + while (mpos != NULL) + { + if (mpos->last_activity == 0) + { + if (mprev == NULL) + s->cs.server.puts = mpos->next; + else + mprev->next = mpos->next; + GNUNET_array_grow (mpos->rbuff2, mpos->rsize2, 0); + GNUNET_free (mpos); + if (mprev == NULL) + mpos = s->cs.server.puts; + else + mpos = mprev->next; + continue; + } + mprev = mpos; + mpos = mpos->next; + } + + /* ! s->is_client */ +#if DO_GET + gpos = s->cs.server.gets; + while (gpos != NULL) + { + gnext = gpos->next; + gpos->next = NULL; + if ((gpos->last_get_activity + HTTP_TIMEOUT < now) || + (gpos != s->cs.server.gets)) + { + if (gpos == s->cs.server.gets) + s->cs.server.gets = NULL; + r = gpos->get; + gpos->get = NULL; + MHD_destroy_response (r); + } + gpos = gnext; + } +#endif + if ( +#if DO_GET + (s->cs.server.gets == NULL) && +#endif + (s->is_mhd_active == 0) && (s->users == 0)) + { +#if DO_GET +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | + GNUNET_GE_USER, + "HTTP transport destroys unused server session\n"); +#endif +#endif + destroy_tsession (tsessions[i]); + i--; + continue; + } + } + } + GNUNET_mutex_unlock (lock); +} + +/** + * Thread that runs the CURL and MHD requests. + */ +static void * +curl_runner (void *unused) +{ + CURLMcode mret; + fd_set rs; + fd_set ws; + fd_set es; + int max; + struct timeval tv; + int running; + unsigned long long timeout; + long ms; + int have_tv; + char buf[128]; /* for reading from pipe */ + int ret; + +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP transport select thread started\n"); +#endif + while (GNUNET_YES == http_running) + { + max = 0; + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + GNUNET_mutex_lock (lock); + mret = curl_multi_fdset (curl_multi, &rs, &ws, &es, &max); + GNUNET_mutex_unlock (lock); + if (mret != CURLM_OK) + { + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_ERROR | GNUNET_GE_ADMIN | GNUNET_GE_USER | + GNUNET_GE_BULK, _("%s failed at %s:%d: `%s'\n"), + "curl_multi_fdset", __FILE__, __LINE__, + curl_multi_strerror (mret)); + break; + } + if (mhd_daemon != NULL) + MHD_get_fdset (mhd_daemon, &rs, &ws, &es, &max); + timeout = 0; + have_tv = MHD_NO; + if (mhd_daemon != NULL) + have_tv = MHD_get_timeout (mhd_daemon, &timeout); + GNUNET_mutex_lock (lock); + if ((CURLM_OK == curl_multi_timeout (curl_multi, &ms)) && + (ms != -1) && ((ms < timeout) || (have_tv == MHD_NO))) + { + timeout = ms; + have_tv = MHD_YES; + } + GNUNET_mutex_unlock (lock); + FD_SET (signal_pipe[0], &rs); + if (max < signal_pipe[0]) + max = signal_pipe[0]; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + if (stats != NULL) + stats->change (stat_select_calls, 1); + ret = + SELECT (max + 1, &rs, &ws, &es, (have_tv == MHD_YES) ? &tv : NULL); + if (ret == -1) + { + GNUNET_GE_LOG_STRERROR (coreAPI->ectx, + GNUNET_GE_ERROR | GNUNET_GE_ADMIN | + GNUNET_GE_DEVELOPER, "select"); + } + if (GNUNET_YES != http_running) + break; + running = 0; + do + { + GNUNET_mutex_lock (lock); + mret = curl_multi_perform (curl_multi, &running); + GNUNET_mutex_unlock (lock); + } + while ((mret == CURLM_CALL_MULTI_PERFORM) + && (http_running == GNUNET_YES)); + if (FD_ISSET (signal_pipe[0], &rs)) + read (signal_pipe[0], buf, sizeof (buf)); + if ((mret != CURLM_OK) && (mret != CURLM_CALL_MULTI_PERFORM)) + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_ERROR | GNUNET_GE_ADMIN | GNUNET_GE_USER | + GNUNET_GE_BULK, _("%s failed at %s:%d: `%s'\n"), + "curl_multi_perform", __FILE__, __LINE__, + curl_multi_strerror (mret)); + if (mhd_daemon != NULL) + MHD_run (mhd_daemon); + cleanup_connections (); + } +#if DEBUG_HTTP + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "HTTP transport select thread exits.\n"); +#endif + return NULL; +} + + +/** + * Start the server process to receive inbound traffic. + * @return GNUNET_OK on success, GNUNET_SYSERR if the operation failed + */ +static int +startTransportServer () +{ + unsigned short port; + + if ((curl_multi != NULL) || (http_running == GNUNET_YES)) + return GNUNET_SYSERR; + curl_multi = curl_multi_init (); + if (curl_multi == NULL) + return GNUNET_SYSERR; + port = get_port (); + if ((mhd_daemon == NULL) && (port != 0)) + { + if (GNUNET_YES != + GNUNET_GC_get_configuration_value_yesno (cfg, "GNUNETD", + "DISABLE-IPV6", + GNUNET_YES)) + { + mhd_daemon = MHD_start_daemon (MHD_USE_IPv6, + port, + &acceptPolicyCallback, + NULL, &accessHandlerCallback, NULL, + MHD_OPTION_CONNECTION_TIMEOUT, + (unsigned int) HTTP_TIMEOUT, + MHD_OPTION_CONNECTION_MEMORY_LIMIT, + (unsigned int) 1024 * 128, + MHD_OPTION_CONNECTION_LIMIT, + (unsigned int) 128, + MHD_OPTION_PER_IP_CONNECTION_LIMIT, + (unsigned int) 8, + MHD_OPTION_NOTIFY_COMPLETED, + &requestCompletedCallback, NULL, + MHD_OPTION_END); + } + if (mhd_daemon == NULL) + { + /* try without IPv6 */ + mhd_daemon = MHD_start_daemon (MHD_NO_FLAG, + port, + &acceptPolicyCallback, + NULL, &accessHandlerCallback, NULL, + MHD_OPTION_CONNECTION_TIMEOUT, + (unsigned int) HTTP_TIMEOUT, + MHD_OPTION_CONNECTION_MEMORY_LIMIT, + (unsigned int) 1024 * 128, + MHD_OPTION_CONNECTION_LIMIT, + (unsigned int) 128, + MHD_OPTION_PER_IP_CONNECTION_LIMIT, + (unsigned int) 8, + MHD_OPTION_NOTIFY_COMPLETED, + &requestCompletedCallback, NULL, + MHD_OPTION_END); + } + else + { + available_protocols |= VERSION_AVAILABLE_IPV6; + } + if (mhd_daemon != NULL) + available_protocols |= VERSION_AVAILABLE_IPV4; + } + if (port == 0) + { + /* NAT */ + available_protocols |= VERSION_AVAILABLE_IPV4; + if (GNUNET_YES != + GNUNET_GC_get_configuration_value_yesno (cfg, "GNUNETD", + "DISABLE-IPV6", + GNUNET_YES)) + available_protocols |= VERSION_AVAILABLE_IPV6; + } + if (0 != PIPE (signal_pipe)) + { + MHD_stop_daemon (mhd_daemon); + curl_multi_cleanup (curl_multi); + curl_multi = NULL; + mhd_daemon = NULL; + return GNUNET_SYSERR; + } + GNUNET_pipe_make_nonblocking (coreAPI->ectx, signal_pipe[0]); + GNUNET_pipe_make_nonblocking (coreAPI->ectx, signal_pipe[1]); + http_running = GNUNET_YES; + curl_thread = GNUNET_thread_create (&curl_runner, NULL, 32 * 1024); + if (curl_thread == NULL) + GNUNET_GE_DIE_STRERROR (coreAPI->ectx, + GNUNET_GE_FATAL | GNUNET_GE_ADMIN | + GNUNET_GE_IMMEDIATE, "pthread_create"); + return GNUNET_OK; +} + +/** + * Shutdown the server process (stop receiving inbound + * traffic). May be restarted later! + */ +static int +stopTransportServer () +{ + void *unused; + int i; + HTTPSession *s; + + if ((http_running == GNUNET_NO) || (curl_multi == NULL)) + return GNUNET_SYSERR; + http_running = GNUNET_NO; + signal_select (); + GNUNET_thread_stop_sleep (curl_thread); + GNUNET_thread_join (curl_thread, &unused); + CLOSE (signal_pipe[0]); + CLOSE (signal_pipe[1]); + if (mhd_daemon != NULL) + { + MHD_stop_daemon (mhd_daemon); + mhd_daemon = NULL; + } + cleanup_connections (); + for (i = 0; i < tsessionCount; i++) + { + s = tsessions[i]->internal; + if (s->users == 0) + { + destroy_tsession (tsessions[i]); + i--; + } + } + curl_multi_cleanup (curl_multi); + curl_multi = NULL; + return GNUNET_OK; +} + +/* ******************** public API ******************** */ + +/** + * The exported method. Makes the core api available + * via a global and returns the udp transport API. + */ +GNUNET_TransportAPI * +inittransport_http (GNUNET_CoreAPIForTransport * core) +{ + GNUNET_GE_ASSERT (coreAPI->ectx, sizeof (HostAddress) == 24); + coreAPI = core; + cfg = coreAPI->cfg; + lock = GNUNET_mutex_create (GNUNET_YES); + if (0 != GNUNET_GC_attach_change_listener (coreAPI->cfg, + &reload_configuration, NULL)) + { + GNUNET_mutex_destroy (lock); + lock = NULL; + return NULL; + } + if (0 != curl_global_init (CURL_GLOBAL_WIN32)) + { + GNUNET_GE_BREAK (NULL, 0); + GNUNET_GC_detach_change_listener (coreAPI->cfg, &reload_configuration, + NULL); + GNUNET_mutex_destroy (lock); + lock = NULL; + return NULL; + } + tsessionCount = 0; + tsessionArrayLength = 0; + GNUNET_array_grow (tsessions, tsessionArrayLength, 32); + if (GNUNET_GC_get_configuration_value_yesno (coreAPI->cfg, + "HTTP", "UPNP", + GNUNET_YES) == GNUNET_YES) + { + upnp = coreAPI->service_request ("upnp"); + + if (upnp == NULL) + { + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_ERROR | GNUNET_GE_USER | + GNUNET_GE_IMMEDIATE, + _ + ("The UPnP service could not be loaded. To disable UPnP, set the " + "configuration option \"UPNP\" in section \"%s\" to \"NO\"\n"), + "HTTP"); + } + } + stats = coreAPI->service_request ("stats"); + if (stats != NULL) + { + stat_bytesReceived + = stats->create (gettext_noop ("# bytes received via HTTP")); + stat_bytesSent = stats->create (gettext_noop ("# bytes sent via HTTP")); + stat_bytesDropped + = stats->create (gettext_noop ("# bytes dropped by HTTP (outgoing)")); + stat_get_issued = stats->create (gettext_noop ("# HTTP GET issued")); + stat_get_received + = stats->create (gettext_noop ("# HTTP GET received")); + stat_put_issued = stats->create (gettext_noop ("# HTTP PUT issued")); + stat_put_received + = stats->create (gettext_noop ("# HTTP PUT received")); + stat_select_calls + = stats->create (gettext_noop ("# HTTP select calls")); + + stat_send_calls = stats->create (gettext_noop ("# HTTP send calls")); + + stat_curl_send_callbacks + = stats->create (gettext_noop ("# HTTP curl send callbacks")); + stat_curl_receive_callbacks + = stats->create (gettext_noop ("# HTTP curl receive callbacks")); + stat_mhd_access_callbacks + = stats->create (gettext_noop ("# HTTP mhd access callbacks")); + stat_mhd_read_callbacks + = stats->create (gettext_noop ("# HTTP mhd read callbacks")); + stat_mhd_close_callbacks + = stats->create (gettext_noop ("# HTTP mhd close callbacks")); + stat_connect_calls + = stats->create (gettext_noop ("# HTTP connect calls")); + } + GNUNET_GC_get_configuration_value_string (coreAPI->cfg, + "GNUNETD", "HTTP-PROXY", "", + &proxy); + + myAPI.protocol_number = GNUNET_TRANSPORT_PROTOCOL_NUMBER_HTTP; + myAPI.mtu = 0; + myAPI.cost = 20000; /* about equal to udp */ + myAPI.hello_verify = &verify_hello; + myAPI.hello_create = &create_hello; + myAPI.connect = &httpConnect; + myAPI.associate = &httpAssociate; + myAPI.send = &httpSend; + myAPI.disconnect = &httpDisconnect; + myAPI.server_start = &startTransportServer; + myAPI.server_stop = &stopTransportServer; + myAPI.hello_to_address = &hello_to_address; + myAPI.send_now_test = &httpTestWouldTry; + + return &myAPI; +} + +void +donetransport_http () +{ + curl_global_cleanup (); + GNUNET_free_non_null (proxy); + proxy = NULL; + GNUNET_array_grow (tsessions, tsessionArrayLength, 0); + do_shutdown (); +} + +/* end of http.c */ diff --git a/src/transport/plugin_transport_smtp.c b/src/transport/plugin_transport_smtp.c new file mode 100644 index 000000000..f7cc530e4 --- /dev/null +++ b/src/transport/plugin_transport_smtp.c @@ -0,0 +1,906 @@ +/* + This file is part of GNUnet + (C) 2003, 2004, 2005, 2006, 2007 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file transports/smtp.c + * @brief Implementation of the SMTP transport service + * @author Christian Grothoff + * @author Renaldo Ferreira + */ + +#include "platform.h" +#include "gnunet_util.h" +#include "gnunet_directories.h" +#include "gnunet_protocols.h" +#include "gnunet_transport.h" +#include "gnunet_stats_service.h" +#include +#include + + +/** + * The default maximum size of each outbound SMTP message. + */ +#define SMTP_MESSAGE_SIZE 65528 + +#define DEBUG_SMTP GNUNET_NO + +#define FILTER_STRING_SIZE 64 + +/* how long can a line in base64 encoded + mime text be? (in characters, excluding "\n") */ +#define MAX_CHAR_PER_LINE 76 + +#define EBUF_LEN 128 + +/** + * Host-Address in a SMTP network. + */ +typedef struct +{ + + /** + * Filter line that every sender must include in the E-mails such + * that the receiver can effectively filter out the GNUnet traffic + * from the E-mail. + */ + char filter[FILTER_STRING_SIZE]; + + /** + * Claimed E-mail address of the sender. + * Format is "foo@bar.com" with null termination, padded to be + * of a multiple of 8 bytes long. + */ + char senderAddress[0]; + +} EmailAddress; + +/** + * Encapsulation of a GNUnet message in the SMTP mail body (before + * base64 encoding). + */ +typedef struct +{ + GNUNET_MessageHeader header; + + /** + * What is the identity of the sender (GNUNET_hash of public key) + */ + GNUNET_PeerIdentity sender; + +} SMTPMessage; + +/* *********** globals ************* */ + +/** + * apis (our advertised API and the core api ) + */ +static GNUNET_CoreAPIForTransport *coreAPI; + +static struct GNUNET_GE_Context *ectx; + +/** + * Thread that listens for inbound messages + */ +static struct GNUNET_ThreadHandle *dispatchThread; + +/** + * Flag to indicate that server has been shut down. + */ +static int smtp_shutdown = GNUNET_YES; + +/** + * Set to the SMTP server hostname (and port) for outgoing messages. + */ +static char *smtp_server_name; + +static char *pipename; + +/** + * Lock for uses of libesmtp (not thread-safe). + */ +static struct GNUNET_Mutex *lock; + +/** + * Old handler for SIGPIPE (kept to be able to restore). + */ +static struct sigaction old_handler; + +static char *email; + +static GNUNET_TransportAPI smtpAPI; + +static GNUNET_Stats_ServiceAPI *stats; + +static int stat_bytesReceived; + +static int stat_bytesSent; + +static int stat_bytesDropped; + +/** + * How many e-mails are we allowed to send per hour? + */ +static unsigned long long rate_limit; + +static GNUNET_CronTime last_transmission; + +/** ******************** Base64 encoding ***********/ + +#define FILLCHAR '=' +static char *cvt = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; + +/** + * Encode into Base64. + * + * @param data the data to encode + * @param len the length of the input + * @param output where to write the output (*output should be NULL, + * is allocated) + * @return the size of the output + */ +static unsigned int +base64_encode (const char *data, unsigned int len, char **output) +{ + unsigned int i; + char c; + unsigned int ret; + char *opt; + +/* (*output)[ret++] = '\r'; \*/ +#define CHECKLINE \ + if ( (ret % MAX_CHAR_PER_LINE) == 0) { \ + (*output)[ret++] = '\n'; \ + } + ret = 0; + opt = GNUNET_malloc (2 + (((len * 4 / 3) + 8) * (MAX_CHAR_PER_LINE + 2)) / + MAX_CHAR_PER_LINE); + /* message must start with \r\n for libesmtp */ + *output = opt; + opt[0] = '\r'; + opt[1] = '\n'; + ret += 2; + for (i = 0; i < len; ++i) + { + c = (data[i] >> 2) & 0x3f; + opt[ret++] = cvt[(int) c]; + CHECKLINE; + c = (data[i] << 4) & 0x3f; + if (++i < len) + c |= (data[i] >> 4) & 0x0f; + opt[ret++] = cvt[(int) c]; + CHECKLINE; + if (i < len) + { + c = (data[i] << 2) & 0x3f; + if (++i < len) + c |= (data[i] >> 6) & 0x03; + opt[ret++] = cvt[(int) c]; + CHECKLINE; + } + else + { + ++i; + opt[ret++] = FILLCHAR; + CHECKLINE; + } + if (i < len) + { + c = data[i] & 0x3f; + opt[ret++] = cvt[(int) c]; + CHECKLINE; + } + else + { + opt[ret++] = FILLCHAR; + CHECKLINE; + } + } + opt[ret++] = FILLCHAR; + return ret; +} + +#define cvtfind(a)( (((a) >= 'A')&&((a) <= 'Z'))? (a)-'A'\ + :(((a)>='a')&&((a)<='z')) ? (a)-'a'+26\ + :(((a)>='0')&&((a)<='9')) ? (a)-'0'+52\ + :((a) == '+') ? 62\ + :((a) == '/') ? 63 : -1) +/** + * Decode from Base64. + * + * @param data the data to encode + * @param len the length of the input + * @param output where to write the output (*output should be NULL, + * is allocated) + * @return the size of the output + */ +static unsigned int +base64_decode (const char *data, unsigned int len, char **output) +{ + unsigned int i; + char c; + char c1; + unsigned int ret = 0; + +#define CHECK_CRLF while (data[i] == '\r' || data[i] == '\n') {\ + GNUNET_GE_LOG(ectx, GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, "ignoring CR/LF\n"); \ + i++; \ + if (i >= len) goto END; \ + } + + *output = GNUNET_malloc ((len * 3 / 4) + 8); +#if DEBUG_SMTP + GNUNET_GE_LOG (ectx, GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "base64_decode decoding len=%d\n", len); +#endif + for (i = 0; i < len; ++i) + { + CHECK_CRLF; + if (data[i] == FILLCHAR) + break; + c = (char) cvtfind (data[i]); + ++i; + CHECK_CRLF; + c1 = (char) cvtfind (data[i]); + c = (c << 2) | ((c1 >> 4) & 0x3); + (*output)[ret++] = c; + if (++i < len) + { + CHECK_CRLF; + c = data[i]; + if (FILLCHAR == c) + break; + c = (char) cvtfind (c); + c1 = ((c1 << 4) & 0xf0) | ((c >> 2) & 0xf); + (*output)[ret++] = c1; + } + if (++i < len) + { + CHECK_CRLF; + c1 = data[i]; + if (FILLCHAR == c1) + break; + + c1 = (char) cvtfind (c1); + c = ((c << 6) & 0xc0) | c1; + (*output)[ret++] = c; + } + } +END: + return ret; +} + +/* ********************* the real stuff ******************* */ + +#define strAUTOncmp(a,b) strncmp(a,b,strlen(b)) + +/** + * Listen to the pipe, decode messages and send to core. + */ +static void * +listenAndDistribute (void *unused) +{ + char *line; + unsigned int linesize; + SMTPMessage *mp; + FILE *fdes; + char *retl; + char *out; + unsigned int size; + GNUNET_TransportPacket *coreMP; + int fd; + unsigned int pos; + + linesize = ((GNUNET_MAX_BUFFER_SIZE * 4 / 3) + 8) * (MAX_CHAR_PER_LINE + 2) / MAX_CHAR_PER_LINE; /* maximum size of a line supported */ + line = GNUNET_malloc (linesize + 2); /* 2 bytes for off-by-one errors, just to be safe... */ + +#define READLINE(l,limit) \ + do { retl = fgets(l, (limit), fdes); \ + if ( (retl == NULL) || (smtp_shutdown == GNUNET_YES)) {\ + goto END; \ + }\ + if (coreAPI->load_monitor != NULL) \ + GNUNET_network_monitor_notify_transmission(coreAPI->load_monitor, GNUNET_ND_DOWNLOAD, strlen(retl)); \ + } while (0) + + + while (smtp_shutdown == GNUNET_NO) + { + fd = OPEN (pipename, O_RDONLY | O_ASYNC); + if (fd == -1) + { + if (smtp_shutdown == GNUNET_NO) + GNUNET_thread_sleep (5 * GNUNET_CRON_SECONDS); + continue; + } + fdes = fdopen (fd, "r"); + while (smtp_shutdown == GNUNET_NO) + { + /* skip until end of header */ + do + { + READLINE (line, linesize); + } + while ((line[0] != '\r') && (line[0] != '\n')); /* expect newline */ + READLINE (line, linesize); /* read base64 encoded message; decode, process */ + pos = 0; + while (1) + { + pos = strlen (line) - 1; /* ignore new line */ + READLINE (&line[pos], linesize - pos); /* read base64 encoded message; decode, process */ + if ((line[pos] == '\r') || (line[pos] == '\n')) + break; /* empty line => end of message! */ + } + size = base64_decode (line, pos, &out); + if (size < sizeof (SMTPMessage)) + { + GNUNET_GE_BREAK (ectx, 0); + GNUNET_free (out); + goto END; + } + + mp = (SMTPMessage *) & out[size - sizeof (SMTPMessage)]; + if (ntohs (mp->header.size) != size) + { + GNUNET_GE_LOG (ectx, + GNUNET_GE_WARNING | GNUNET_GE_BULK | + GNUNET_GE_USER, + _ + ("Received malformed message via %s. Ignored.\n"), + "SMTP"); +#if DEBUG_SMTP + GNUNET_GE_LOG (ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | + GNUNET_GE_USER, + "Size returned by base64=%d, in the msg=%d.\n", + size, ntohl (mp->size)); +#endif + GNUNET_free (out); + goto END; + } + if (stats != NULL) + stats->change (stat_bytesReceived, size); + coreMP = GNUNET_malloc (sizeof (GNUNET_TransportPacket)); + coreMP->msg = out; + coreMP->size = size - sizeof (SMTPMessage); + coreMP->tsession = NULL; + coreMP->sender = mp->sender; +#if DEBUG_SMTP + GNUNET_GE_LOG (ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "SMTP message passed to the core.\n"); +#endif + + coreAPI->receive (coreMP); + } + END: +#if DEBUG_SMTP + GNUNET_GE_LOG (ectx, + GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER, + "SMTP message processed.\n"); +#endif + if (fdes != NULL) + fclose (fdes); + } + GNUNET_free (line); + return NULL; +} + +/* *************** API implementation *************** */ + +/** + * Verify that a hello-Message is correct (a node is reachable at that + * address). Since the reply will be asynchronous, a method must be + * called on success. + * + * @param hello the hello message to verify + * (the signature/crc have been verified before) + * @return GNUNET_OK on success, GNUNET_SYSERR on error + */ +static int +api_verify_hello (const GNUNET_MessageHello * hello) +{ + const EmailAddress *maddr; + + maddr = (const EmailAddress *) &hello[1]; + if ((ntohs (hello->header.size) != + sizeof (GNUNET_MessageHello) + ntohs (hello->senderAddressSize)) || + (maddr->senderAddress[ntohs (hello->senderAddressSize) - 1 - + FILTER_STRING_SIZE] != '\0')) + { + GNUNET_GE_BREAK (ectx, 0); + return GNUNET_SYSERR; /* obviously invalid */ + } + if (NULL == strstr (maddr->filter, ": ")) + return GNUNET_SYSERR; + return GNUNET_OK; +} + +/** + * Create a hello-Message for the current node. The hello is created + * without signature and without a timestamp. The GNUnet core will + * GNUNET_RSA_sign the message and add an expiration time. + * + * @return hello on success, NULL on error + */ +static GNUNET_MessageHello * +api_create_hello () +{ + GNUNET_MessageHello *msg; + char *filter; + EmailAddress *haddr; + int i; + + GNUNET_GC_get_configuration_value_string (coreAPI->cfg, + "SMTP", "FILTER", + "X-mailer: GNUnet", &filter); + if (NULL == strstr (filter, ": ")) + { + GNUNET_GE_LOG (ectx, + GNUNET_GE_WARNING | GNUNET_GE_BULK | GNUNET_GE_USER, + _("SMTP filter string to invalid, lacks ': '\n")); + GNUNET_free (filter); + return NULL; + } + + if (strlen (filter) > FILTER_STRING_SIZE) + { + filter[FILTER_STRING_SIZE] = '\0'; + GNUNET_GE_LOG (ectx, + GNUNET_GE_WARNING | GNUNET_GE_BULK | GNUNET_GE_USER, + _("SMTP filter string to long, capped to `%s'\n"), + filter); + } + i = (strlen (email) + 8) & (~7); /* make multiple of 8 */ + msg = + GNUNET_malloc (sizeof (GNUNET_MessageHello) + sizeof (EmailAddress) + i); + memset (msg, 0, sizeof (GNUNET_MessageHello) + sizeof (EmailAddress) + i); + haddr = (EmailAddress *) & msg[1]; + memset (&haddr->filter[0], 0, FILTER_STRING_SIZE); + strcpy (&haddr->filter[0], filter); + memcpy (&haddr->senderAddress[0], email, strlen (email) + 1); + msg->senderAddressSize = htons (strlen (email) + 1 + sizeof (EmailAddress)); + msg->protocol = htons (GNUNET_TRANSPORT_PROTOCOL_NUMBER_SMTP); + msg->MTU = htonl (smtpAPI.mtu); + msg->header.size = htons (GNUNET_sizeof_hello (msg)); + if (api_verify_hello (msg) == GNUNET_SYSERR) + GNUNET_GE_ASSERT (ectx, 0); + GNUNET_free (filter); + return msg; +} + +struct GetMessageClosure +{ + unsigned int esize; + unsigned int pos; + char *ebody; +}; + +static const char * +get_message (void **buf, int *len, void *cls) +{ + struct GetMessageClosure *gmc = cls; + + *buf = NULL; + if (len == NULL) + { + gmc->pos = 0; + return NULL; + } + if (gmc->pos == gmc->esize) + return NULL; /* done */ + *len = gmc->esize; + gmc->pos = gmc->esize; + return gmc->ebody; +} + +/** + * Send a message to the specified remote node. + * + * @param tsession the GNUNET_MessageHello identifying the remote node + * @param message what to send + * @param size the size of the message + * @return GNUNET_SYSERR on error, GNUNET_OK on success + */ +static int +api_send (GNUNET_TSession * tsession, + const void *msg, const unsigned int size, int important) +{ + const GNUNET_MessageHello *hello; + const EmailAddress *haddr; + char *m; + char *filter; + char *fvalue; + SMTPMessage *mp; + struct GetMessageClosure gm_cls; + smtp_session_t session; + smtp_message_t message; + smtp_recipient_t recipient; +#define EBUF_LEN 128 + char ebuf[EBUF_LEN]; + GNUNET_CronTime now; + + if (smtp_shutdown == GNUNET_YES) + return GNUNET_SYSERR; + if ((size == 0) || (size > smtpAPI.mtu)) + { + GNUNET_GE_BREAK (ectx, 0); + return GNUNET_SYSERR; + } + now = GNUNET_get_time (); + if ((important != GNUNET_YES) && + ((now - last_transmission) * rate_limit) < GNUNET_CRON_HOURS) + return GNUNET_NO; /* rate too high */ + last_transmission = now; + + hello = (const GNUNET_MessageHello *) tsession->internal; + if (hello == NULL) + return GNUNET_SYSERR; + GNUNET_mutex_lock (lock); + session = smtp_create_session (); + if (session == NULL) + { + GNUNET_GE_LOG (ectx, + GNUNET_GE_ERROR | GNUNET_GE_ADMIN | GNUNET_GE_USER | + GNUNET_GE_IMMEDIATE, + _("SMTP: `%s' failed: %s.\n"), + "smtp_create_session", + smtp_strerror (smtp_errno (), ebuf, EBUF_LEN)); + GNUNET_mutex_unlock (lock); + return GNUNET_SYSERR; + } + if (0 == smtp_set_server (session, smtp_server_name)) + { + GNUNET_GE_LOG (ectx, + GNUNET_GE_ERROR | GNUNET_GE_ADMIN | GNUNET_GE_USER | + GNUNET_GE_IMMEDIATE, + _("SMTP: `%s' failed: %s.\n"), + "smtp_set_server", + smtp_strerror (smtp_errno (), ebuf, EBUF_LEN)); + smtp_destroy_session (session); + GNUNET_mutex_unlock (lock); + return GNUNET_SYSERR; + } + haddr = (const EmailAddress *) &hello[1]; + message = smtp_add_message (session); + if (message == NULL) + { + GNUNET_GE_LOG (ectx, + GNUNET_GE_WARNING | GNUNET_GE_ADMIN | GNUNET_GE_USER | + GNUNET_GE_BULK, + _("SMTP: `%s' failed: %s.\n"), + "smtp_add_message", + smtp_strerror (smtp_errno (), ebuf, EBUF_LEN)); + smtp_destroy_session (session); + GNUNET_mutex_unlock (lock); + return GNUNET_SYSERR; + } + smtp_set_header (message, "To", NULL, haddr->senderAddress); + smtp_set_header (message, "From", NULL, email); + + filter = GNUNET_strdup (haddr->filter); + fvalue = strstr (filter, ": "); + GNUNET_GE_ASSERT (NULL, NULL != fvalue); + fvalue[0] = '\0'; + fvalue += 2; + if (0 == smtp_set_header (message, filter, fvalue)) + { + GNUNET_GE_LOG (ectx, + GNUNET_GE_WARNING | GNUNET_GE_ADMIN | GNUNET_GE_USER | + GNUNET_GE_BULK, + _("SMTP: `%s' failed: %s.\n"), + "smtp_set_header", + smtp_strerror (smtp_errno (), ebuf, EBUF_LEN)); + smtp_destroy_session (session); + GNUNET_mutex_unlock (lock); + GNUNET_free (filter); + return GNUNET_SYSERR; + } + GNUNET_free (filter); + m = GNUNET_malloc (size + sizeof (SMTPMessage)); + memcpy (m, msg, size); + mp = (SMTPMessage *) & m[size]; + mp->header.size = htons (size + sizeof (SMTPMessage)); + mp->header.type = htons (0); + mp->sender = *coreAPI->my_identity; + gm_cls.ebody = NULL; + gm_cls.pos = 0; + gm_cls.esize = + base64_encode (m, size + sizeof (SMTPMessage), &gm_cls.ebody); + GNUNET_free (m); + if (0 == smtp_size_set_estimate (message, gm_cls.esize)) + { + GNUNET_GE_LOG (ectx, + GNUNET_GE_WARNING | GNUNET_GE_ADMIN | GNUNET_GE_USER | + GNUNET_GE_BULK, + _("SMTP: `%s' failed: %s.\n"), + "smtp_size_set_estimate", + smtp_strerror (smtp_errno (), ebuf, EBUF_LEN)); + } + if (0 == smtp_set_messagecb (message, &get_message, &gm_cls)) + { + GNUNET_GE_LOG (ectx, + GNUNET_GE_WARNING | GNUNET_GE_ADMIN | GNUNET_GE_USER | + GNUNET_GE_BULK, + _("SMTP: `%s' failed: %s.\n"), + "smtp_set_messagecb", + smtp_strerror (smtp_errno (), ebuf, EBUF_LEN)); + smtp_destroy_session (session); + GNUNET_mutex_unlock (lock); + GNUNET_free (gm_cls.ebody); + return GNUNET_SYSERR; + } + recipient = smtp_add_recipient (message, haddr->senderAddress); + if (recipient == NULL) + { + GNUNET_GE_LOG (ectx, + GNUNET_GE_WARNING | GNUNET_GE_ADMIN | GNUNET_GE_USER | + GNUNET_GE_BULK, + _("SMTP: `%s' failed: %s.\n"), + "smtp_add_recipient", + smtp_strerror (smtp_errno (), ebuf, EBUF_LEN)); + smtp_destroy_session (session); + GNUNET_mutex_unlock (lock); + return GNUNET_SYSERR; + } + if (0 == smtp_start_session (session)) + { + GNUNET_GE_LOG (ectx, + GNUNET_GE_WARNING | GNUNET_GE_ADMIN | GNUNET_GE_USER | + GNUNET_GE_BULK, + _("SMTP: `%s' failed: %s.\n"), + "smtp_start_session", + smtp_strerror (smtp_errno (), ebuf, EBUF_LEN)); + smtp_destroy_session (session); + GNUNET_mutex_unlock (lock); + GNUNET_free (gm_cls.ebody); + return GNUNET_SYSERR; + } + if (stats != NULL) + stats->change (stat_bytesSent, size); + if (coreAPI->load_monitor != NULL) + GNUNET_network_monitor_notify_transmission (coreAPI->load_monitor, + GNUNET_ND_UPLOAD, + gm_cls.esize); + smtp_message_reset_status (message); /* this is needed to plug a 28-byte/message memory leak in libesmtp */ + smtp_destroy_session (session); + GNUNET_mutex_unlock (lock); + GNUNET_free (gm_cls.ebody); + return GNUNET_OK; +} + +/** + * Establish a connection to a remote node. + * @param helo the hello-Message for the target node + * @param tsessionPtr the session handle that is to be set + * @return GNUNET_OK on success, GNUNET_SYSERR if the operation failed + */ +static int +api_connect (const GNUNET_MessageHello * hello, + GNUNET_TSession ** tsessionPtr, int may_reuse) +{ + GNUNET_TSession *tsession; + + tsession = GNUNET_malloc (sizeof (GNUNET_TSession)); + tsession->internal = GNUNET_malloc (GNUNET_sizeof_hello (hello)); + tsession->peer = hello->senderIdentity; + memcpy (tsession->internal, hello, GNUNET_sizeof_hello (hello)); + tsession->ttype = smtpAPI.protocol_number; + (*tsessionPtr) = tsession; + return GNUNET_OK; +} + +/** + * Disconnect from a remote node. + * + * @param tsession the session that is closed + * @return GNUNET_OK on success, GNUNET_SYSERR if the operation failed + */ +static int +api_disconnect (GNUNET_TSession * tsession) +{ + if (tsession != NULL) + { + if (tsession->internal != NULL) + GNUNET_free (tsession->internal); + GNUNET_free (tsession); + } + return GNUNET_OK; +} + +/** + * Start the server process to receive inbound traffic. + * @return GNUNET_OK on success, GNUNET_SYSERR if the operation failed + */ +static int +api_start_transport_server () +{ + smtp_shutdown = GNUNET_NO; + /* initialize SMTP network */ + dispatchThread = + GNUNET_thread_create (&listenAndDistribute, NULL, 1024 * 4); + if (dispatchThread == NULL) + { + GNUNET_GE_DIE_STRERROR (ectx, + GNUNET_GE_ADMIN | GNUNET_GE_BULK | + GNUNET_GE_FATAL, "pthread_create"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + +/** + * Shutdown the server process (stop receiving inbound traffic). Maybe + * restarted later! + */ +static int +api_stop_transport_server () +{ + void *unused; + + smtp_shutdown = GNUNET_YES; + GNUNET_thread_stop_sleep (dispatchThread); + GNUNET_thread_join (dispatchThread, &unused); + return GNUNET_OK; +} + +/** + * Convert SMTP hello to an IP address (always fails). + */ +static int +api_hello_to_address (const GNUNET_MessageHello * hello, + void **sa, unsigned int *sa_len) +{ + return GNUNET_SYSERR; +} + +/** + * Always fails. + */ +static int +api_associate (GNUNET_TSession * tsession) +{ + return GNUNET_SYSERR; /* SMTP connections can never be associated */ +} + +/** + * Always succeeds (for now; we should look at adding + * frequency limits to SMTP in the future!). + */ +static int +api_test_would_try (GNUNET_TSession * tsession, const unsigned int size, + int important) +{ + return GNUNET_OK; /* we always try... */ +} + +/** + * The exported method. Makes the core api available via a global and + * returns the smtp transport API. + */ +GNUNET_TransportAPI * +inittransport_smtp (GNUNET_CoreAPIForTransport * core) +{ + + + unsigned long long mtu; + struct sigaction sa; + + coreAPI = core; + ectx = core->ectx; + if (!GNUNET_GC_have_configuration_value (coreAPI->cfg, "SMTP", "EMAIL")) + { + GNUNET_GE_LOG (ectx, + GNUNET_GE_ERROR | GNUNET_GE_BULK | GNUNET_GE_USER, + _ + ("No email-address specified, can not start SMTP transport.\n")); + return NULL; + } + GNUNET_GC_get_configuration_value_number (coreAPI->cfg, + "SMTP", + "MTU", + 1200, + SMTP_MESSAGE_SIZE, + SMTP_MESSAGE_SIZE, &mtu); + GNUNET_GC_get_configuration_value_number (coreAPI->cfg, + "SMTP", + "RATELIMIT", + 0, 0, 1024 * 1024, &rate_limit); + stats = coreAPI->service_request ("stats"); + if (stats != NULL) + { + stat_bytesReceived + = stats->create (gettext_noop ("# bytes received via SMTP")); + stat_bytesSent = stats->create (gettext_noop ("# bytes sent via SMTP")); + stat_bytesDropped + = stats->create (gettext_noop ("# bytes dropped by SMTP (outgoing)")); + } + GNUNET_GC_get_configuration_value_filename (coreAPI->cfg, + "SMTP", + "PIPE", + GNUNET_DEFAULT_DAEMON_VAR_DIRECTORY + "/smtp-pipe", &pipename); + UNLINK (pipename); + if (0 != mkfifo (pipename, S_IWUSR | S_IRUSR | S_IWGRP | S_IWOTH)) + { + GNUNET_GE_LOG_STRERROR (ectx, + GNUNET_GE_ADMIN | GNUNET_GE_BULK | + GNUNET_GE_FATAL, "mkfifo"); + GNUNET_free (pipename); + coreAPI->service_release (stats); + stats = NULL; + return NULL; + } + /* we need to allow the mailer program to send us messages; + easiest done by giving it write permissions (see Mantis #1142) */ + if (0 != chmod (pipename, S_IWUSR | S_IRUSR | S_IWGRP | S_IWOTH)) + GNUNET_GE_LOG_STRERROR (ectx, + GNUNET_GE_ADMIN | GNUNET_GE_BULK | + GNUNET_GE_WARNING, "chmod"); + GNUNET_GC_get_configuration_value_string (coreAPI->cfg, + "SMTP", "EMAIL", NULL, &email); + lock = GNUNET_mutex_create (GNUNET_NO); + GNUNET_GC_get_configuration_value_string (coreAPI->cfg, + "SMTP", + "SERVER", + "localhost:25", + &smtp_server_name); + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, &old_handler); + + smtpAPI.protocol_number = GNUNET_TRANSPORT_PROTOCOL_NUMBER_SMTP; + smtpAPI.mtu = mtu - sizeof (SMTPMessage); + smtpAPI.cost = 50; + smtpAPI.hello_verify = &api_verify_hello; + smtpAPI.hello_create = &api_create_hello; + smtpAPI.connect = &api_connect; + smtpAPI.send = &api_send; + smtpAPI.associate = &api_associate; + smtpAPI.disconnect = &api_disconnect; + smtpAPI.server_start = &api_start_transport_server; + smtpAPI.server_stop = &api_stop_transport_server; + smtpAPI.hello_to_address = &api_hello_to_address; + smtpAPI.send_now_test = &api_test_would_try; + return &smtpAPI; +} + +void +donetransport_smtp () +{ + sigaction (SIGPIPE, &old_handler, NULL); + GNUNET_free (smtp_server_name); + if (stats != NULL) + { + coreAPI->service_release (stats); + stats = NULL; + } + GNUNET_mutex_destroy (lock); + lock = NULL; + UNLINK (pipename); + GNUNET_free (pipename); + pipename = NULL; + GNUNET_free (email); + email = NULL; +} + +/* end of smtp.c */ diff --git a/src/transport/plugin_transport_tcp.c b/src/transport/plugin_transport_tcp.c new file mode 100644 index 000000000..c87056e71 --- /dev/null +++ b/src/transport/plugin_transport_tcp.c @@ -0,0 +1,1782 @@ +/* + This file is part of GNUnet + (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file transport/plugin_transport_tcp.c + * @brief Implementation of the TCP transport service + * @author Christian Grothoff + */ + +#include "platform.h" +#include "gnunet_hello_lib.h" +#include "gnunet_network_lib.h" +#include "gnunet_os_lib.h" +#include "gnunet_peerinfo_service.h" +#include "gnunet_protocols.h" +#include "gnunet_resolver_service.h" +#include "gnunet_server_lib.h" +#include "gnunet_service_lib.h" +#include "gnunet_statistics_service.h" +#include "gnunet_transport_service.h" +#include "plugin_transport.h" +#include "transport.h" + +#define DEBUG_TCP GNUNET_NO + +/** + * After how long do we expire an address that we + * learned from another peer if it is not reconfirmed + * by anyone? + */ +#define LEARNED_ADDRESS_EXPIRATION GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS, 6) + +/** + * How long until we give up on transmitting the welcome message? + */ +#define WELCOME_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30) + +/** + * How long until we give up on transmitting the welcome message? + */ +#define HOSTNAME_RESOLVE_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5) + +/** + * For how many messages back to we keep transmission times? + */ +#define ACK_LOG_SIZE 32 + +/** + * Initial handshake message for a session. This header + * is followed by the address that the other peer used to + * connect to us (so that we may learn it) or the address + * that the other peer got from the accept call. + */ +struct WelcomeMessage +{ + struct GNUNET_MessageHeader header; + + /** + * Identity of the node connecting (TCP client) + */ + struct GNUNET_PeerIdentity clientIdentity; + +}; + + +/** + * Encapsulation for normal TCP traffic. + */ +struct DataMessage +{ + struct GNUNET_MessageHeader header; + + /** + * For alignment. + */ + uint32_t reserved GNUNET_PACKED; + + /** + * Number of the last message that was received from the other peer. + */ + uint64_t ack_in GNUNET_PACKED; + + /** + * Number of this outgoing message. + */ + uint64_t ack_out GNUNET_PACKED; + + /** + * How long was sending this ack delayed by the other peer + * (estimate). The receiver of this message can use the delay + * between sending his message number 'ack' and receiving this ack + * minus the delay as an estimate of the round-trip time. + */ + struct GNUNET_TIME_RelativeNBO delay; + +}; + + +/** + * Encapsulation of all of the state of the plugin. + */ +struct Plugin; + + +/** + * Information kept for each message that is yet to + * be transmitted. + */ +struct PendingMessage +{ + + /** + * This is a linked list. + */ + struct PendingMessage *next; + + /** + * The pending message, pointer to the end + * of this struct, do not free! + */ + struct GNUNET_MessageHeader *msg; + + + /** + * Continuation function to call once the message + * has been sent. Can be NULL if there is no + * continuation to call. + */ + GNUNET_TRANSPORT_TransmitContinuation transmit_cont; + + /** + * Closure for transmit_cont. + */ + void *transmit_cont_cls; + + /** + * Timeout value for the pending message. + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * GNUNET_YES if this is a welcome message; + * otherwise this should be a DATA message. + */ + int is_welcome; + +}; + + +/** + * Session handle for TCP connections. + */ +struct Session +{ + + /** + * Stored in a linked list. + */ + struct Session *next; + + /** + * Pointer to the global plugin struct. + */ + struct Plugin *plugin; + + /** + * The client (used to identify this connection) + */ + struct GNUNET_SERVER_Client *client; + + /** + * gnunet-service-transport context for this connection. + */ + struct ReadyList *service_context; + + /** + * Messages currently pending for transmission + * to this peer, if any. + */ + struct PendingMessage *pending_messages; + + /** + * Handle for pending transmission request. + */ + struct GNUNET_NETWORK_TransmitHandle *transmit_handle; + + /** + * To whom are we talking to (set to our identity + * if we are still waiting for the welcome message) + */ + struct GNUNET_PeerIdentity target; + + /** + * At what time did we reset last_received last? + */ + struct GNUNET_TIME_Absolute last_quota_update; + + /** + * Address of the other peer if WE initiated the connection + * (and hence can be sure what it is), otherwise NULL. + */ + void *connect_addr; + + /** + * How many bytes have we received since the "last_quota_update" + * timestamp? + */ + uint64_t last_received; + + /** + * Our current latency estimate (in ms). + */ + double latency_estimate; + + /** + * Time when we generated the last ACK_LOG_SIZE acks. + * (the "last" refers to the "out_msg_counter" here) + */ + struct GNUNET_TIME_Absolute gen_time[ACK_LOG_SIZE]; + + /** + * Our current sequence number. + */ + uint64_t out_msg_counter; + + /** + * Highest received incoming sequence number. + */ + uint64_t max_in_msg_counter; + + /** + * Number of bytes per ms that this peer is allowed + * to send to us. + */ + uint32_t quota_in; + + /** + * Length of connect_addr, can be 0. + */ + size_t connect_alen; + + /** + * Are we still expecting the welcome message? (GNUNET_YES/GNUNET_NO) + */ + int expecting_welcome; + + /** + * Are we still trying to connect? + */ + int still_connecting; + +}; + + +/** + * Encapsulation of all of the state of the plugin. + */ +struct Plugin +{ + /** + * Our environment. + */ + struct GNUNET_TRANSPORT_PluginEnvironment *env; + + /** + * The listen socket. + */ + struct GNUNET_NETWORK_SocketHandle *lsock; + + /** + * List of open TCP sessions. + */ + struct Session *sessions; + + /** + * Handle for the statistics service. + */ + struct GNUNET_STATISTICS_Handle *statistics; + + /** + * Handle to the network service. + */ + struct GNUNET_SERVICE_Context *service; + + /** + * Handle to the server for this service. + */ + struct GNUNET_SERVER_Handle *server; + + /** + * Copy of the handler array where the closures are + * set to this struct's instance. + */ + struct GNUNET_SERVER_MessageHandler *handlers; + + /** + * ID of task used to update our addresses when one expires. + */ + GNUNET_SCHEDULER_TaskIdentifier address_update_task; + + /** + * Port that we are actually listening on. + */ + uint16_t open_port; + + /** + * Port that the user said we would have visible to the + * rest of the world. + */ + uint16_t adv_port; + +}; + + +/** + * Find the session handle for the given peer. + */ +static struct Session * +find_session_by_target (struct Plugin *plugin, + const struct GNUNET_PeerIdentity *target) +{ + struct Session *ret; + + ret = plugin->sessions; + while ((ret != NULL) && + (0 != memcmp (target, + &ret->target, sizeof (struct GNUNET_PeerIdentity)))) + ret = ret->next; + return ret; +} + + +/** + * Find the session handle for the given peer. + */ +static struct Session * +find_session_by_client (struct Plugin *plugin, + const struct GNUNET_SERVER_Client *client) +{ + struct Session *ret; + + ret = plugin->sessions; + while ((ret != NULL) && (client != ret->client)) + ret = ret->next; + return ret; +} + + +/** + * Create a welcome message. + */ +static struct PendingMessage * +create_welcome (size_t addrlen, const void *addr, struct Plugin *plugin) +{ + struct PendingMessage *pm; + struct WelcomeMessage *welcome; + + pm = GNUNET_malloc (sizeof (struct PendingMessage) + + sizeof (struct WelcomeMessage) + addrlen); + pm->msg = (struct GNUNET_MessageHeader *) &pm[1]; + welcome = (struct WelcomeMessage *) &pm[1]; + welcome->header.size = htons (sizeof (struct WelcomeMessage) + addrlen); + welcome->header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_TCP_WELCOME); + GNUNET_CRYPTO_hash (plugin->env->my_public_key, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &welcome->clientIdentity.hashPubKey); + memcpy (&welcome[1], addr, addrlen); + pm->timeout = GNUNET_TIME_relative_to_absolute (WELCOME_TIMEOUT); + pm->is_welcome = GNUNET_YES; + return pm; +} + + +/** + * Create a new session using the specified address + * for the welcome message. + * + * @param plugin us + * @param target peer to connect to + * @param client client to use + * @param addrlen IPv4 or IPv6 + * @param addr either struct sockaddr_in or struct sockaddr_in6 + * @return NULL connection failed / invalid address + */ +static struct Session * +create_session (struct Plugin *plugin, + const struct GNUNET_PeerIdentity *target, + struct GNUNET_SERVER_Client *client, + const void *addr, size_t addrlen) +{ + struct Session *ret; + + ret = GNUNET_malloc (sizeof (struct Session)); + ret->plugin = plugin; + ret->next = plugin->sessions; + plugin->sessions = ret; + ret->client = client; + ret->target = *target; + ret->last_quota_update = GNUNET_TIME_absolute_get (); + ret->quota_in = plugin->env->default_quota_in; + ret->expecting_welcome = GNUNET_YES; + ret->pending_messages = create_welcome (addrlen, addr, plugin); + return ret; +} + + +/** + * Create a new session connecting to the specified + * target at the specified address. + * + * @param plugin us + * @param target peer to connect to + * @param addrlen IPv4 or IPv6 + * @param addr either struct sockaddr_in or struct sockaddr_in6 + * @return NULL connection failed / invalid address + */ +static struct Session * +connect_and_create_session (struct Plugin *plugin, + const struct GNUNET_PeerIdentity *target, + const void *addr, size_t addrlen) +{ + struct GNUNET_SERVER_Client *client; + struct GNUNET_NETWORK_SocketHandle *conn; + struct Session *session; + int af; + char buf[INET6_ADDRSTRLEN]; + uint16_t port; + + session = plugin->sessions; + while (session != NULL) + { + if ((0 == memcmp (target, + &session->target, + sizeof (struct GNUNET_PeerIdentity))) && + (session->connect_alen == addrlen) && + (0 == memcmp (session->connect_addr, addr, addrlen))) + return session; /* already exists! */ + session = session->next; + } + + if (addrlen == sizeof (struct sockaddr_in)) + { + af = AF_INET; + inet_ntop (af, + &((struct sockaddr_in *) addr)->sin_addr, buf, sizeof (buf)); + port = ntohs (((struct sockaddr_in *) addr)->sin_port); + } + else if (addrlen == sizeof (struct sockaddr_in6)) + { + af = AF_INET6; + inet_ntop (af, + &((struct sockaddr_in6 *) addr)->sin6_addr, + buf, sizeof (buf)); + port = ntohs (((struct sockaddr_in6 *) addr)->sin6_port); + } + else + { + GNUNET_break_op (0); + return NULL; /* invalid address */ + } + conn = GNUNET_NETWORK_socket_create_from_sockaddr (plugin->env->sched, + af, + addr, + addrlen, + GNUNET_SERVER_MAX_MESSAGE_SIZE); + if (conn == NULL) + { +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", + "Failed to create connection to peer at `%s:%u'.\n", + buf, port); +#endif + return NULL; + } + client = GNUNET_SERVER_connect_socket (plugin->server, conn); + GNUNET_assert (client != NULL); + session = create_session (plugin, target, client, addr, addrlen); + session->connect_alen = addrlen; + session->connect_addr = GNUNET_malloc (addrlen); + memcpy (session->connect_addr, addr, addrlen); +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", + "Creating new session %p with `%s:%u' based on `%s' request.\n", + session, buf, port, "send_to"); +#endif + return session; +} + + +/** + * If we have pending messages, ask the server to + * transmit them (schedule the respective tasks, etc.) + * + * @param session for which session should we do this + */ +static void process_pending_messages (struct Session *session); + + +/** + * Function called to notify a client about the socket + * begin ready to queue more data. "buf" will be + * NULL and "size" zero if the socket was closed for + * writing in the meantime. + * + * @param cls closure + * @param size number of bytes available in buf + * @param buf where the callee should write the message + * @return number of bytes written to buf + */ +static size_t +do_transmit (void *cls, size_t size, void *buf) +{ + struct Session *session = cls; + struct PendingMessage *pm; + char *cbuf; + uint16_t msize; + size_t ret; + struct DataMessage *dm; + + session->transmit_handle = NULL; + if (buf == NULL) + { +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", "Timeout trying to transmit\n"); +#endif + /* timeout */ + while (NULL != (pm = session->pending_messages)) + { + session->pending_messages = pm->next; + if (pm->transmit_cont != NULL) + pm->transmit_cont (pm->transmit_cont_cls, + session->service_context, + &session->target, GNUNET_SYSERR); + GNUNET_free (pm); + } + return 0; + } + ret = 0; + cbuf = buf; + while (NULL != (pm = session->pending_messages)) + { + if (pm->is_welcome) + { + if (size < (msize = htons (pm->msg->size))) + break; + memcpy (cbuf, pm->msg, msize); + cbuf += msize; + ret += msize; + size -= msize; + } + else + { + if (size < + sizeof (struct DataMessage) + (msize = htons (pm->msg->size))) + break; + dm = (struct DataMessage *) cbuf; + dm->header.size = htons (sizeof (struct DataMessage) + msize); + dm->header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_TCP_DATA); + dm->ack_out = GNUNET_htonll (++session->out_msg_counter); + dm->ack_in = GNUNET_htonll (session->max_in_msg_counter); + cbuf += sizeof (struct DataMessage); + ret += sizeof (struct DataMessage); + size -= sizeof (struct DataMessage); + memcpy (cbuf, pm->msg, msize); + cbuf += msize; + ret += msize; + size -= msize; + } + session->pending_messages = pm->next; + if (pm->transmit_cont != NULL) + pm->transmit_cont (pm->transmit_cont_cls, + session->service_context, + &session->target, GNUNET_OK); + GNUNET_free (pm); + session->gen_time[session->out_msg_counter % ACK_LOG_SIZE] + = GNUNET_TIME_absolute_get (); + } + process_pending_messages (session); +#if DEBUG_TCP || 1 + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", "Transmitting %u bytes\n", ret); +#endif + return ret; +} + + +/** + * If we have pending messages, ask the server to + * transmit them (schedule the respective tasks, etc.) + * + * @param session for which session should we do this + */ +static void +process_pending_messages (struct Session *session) +{ + GNUNET_assert (session->client != NULL); + if (session->pending_messages == NULL) + return; + if (session->transmit_handle != NULL) + return; + session->transmit_handle + = GNUNET_SERVER_notify_transmit_ready (session->client, + htons (session->pending_messages-> + msg->size) + + (session->pending_messages-> + is_welcome ? 0 : sizeof (struct + DataMessage)), + GNUNET_TIME_absolute_get_remaining + (session->pending_messages[0]. + timeout), &do_transmit, session); +} + + +/** + * Function that can be used by the transport service to transmit + * a message using the plugin using a fresh connection (even if + * we already have a connection to this peer, this function is + * required to establish a new one). + * + * @param cls closure + * @param target who should receive this message + * @param msg1 first message to transmit + * @param msg2 second message to transmit (can be NULL) + * @param timeout how long should we try to transmit these? + * @param addrlen length of the address + * @param addr the address + * @return session if the transmission has been scheduled + * NULL if the address format is invalid + */ +static void * +tcp_plugin_send_to (void *cls, + const struct GNUNET_PeerIdentity *target, + const struct GNUNET_MessageHeader *msg1, + const struct GNUNET_MessageHeader *msg2, + struct GNUNET_TIME_Relative timeout, + const void *addr, size_t addrlen) +{ + struct Plugin *plugin = cls; + struct Session *session; + struct PendingMessage *pl; + struct PendingMessage *pm; + + session = connect_and_create_session (plugin, target, addr, addrlen); + if (session == NULL) + { +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", "Failed to create fresh session.\n"); +#endif + return NULL; + } + pl = NULL; + if (msg2 != NULL) + { + pm = GNUNET_malloc (sizeof (struct PendingMessage) + + ntohs (msg2->size)); + pm->msg = (struct GNUNET_MessageHeader *) &pm[1]; + memcpy (pm->msg, msg2, ntohs (msg2->size)); + pm->timeout = GNUNET_TIME_relative_to_absolute (timeout); + pm->is_welcome = GNUNET_NO; + pl = pm; + } + if (msg1 != NULL) + { + pm = GNUNET_malloc (sizeof (struct PendingMessage) + + ntohs (msg1->size)); + pm->msg = (struct GNUNET_MessageHeader *) &pm[1]; + memcpy (pm->msg, msg1, ntohs (msg1->size)); + pm->timeout = GNUNET_TIME_relative_to_absolute (timeout); + pm->is_welcome = GNUNET_NO; + pm->next = pl; + pl = pm; + } + /* append */ + if (session->pending_messages != NULL) + { + pm = session->pending_messages; + while (pm->next != NULL) + pm = pm->next; + pm->next = pl; + } + else + { + session->pending_messages = pl; + } + process_pending_messages (session); + return session; +} + + +/** + * Functions with this signature are called whenever we need + * to close a session due to a disconnect or failure to + * establish a connection. + * + * @param session session to close down + */ +static void +disconnect_session (struct Session *session) +{ + struct Session *prev; + struct Session *pos; + struct PendingMessage *pm; + +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", + "Disconnecting from other peer (session %p).\n", session); +#endif + /* remove from session list */ + prev = NULL; + pos = session->plugin->sessions; + while (pos != session) + { + prev = pos; + pos = pos->next; + } + if (prev == NULL) + session->plugin->sessions = session->next; + else + prev->next = session->next; + /* clean up state */ + if (session->client != NULL) + { +#if DEBUG_TCP + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Disconnecting from client address %p\n", session->client); +#endif + GNUNET_SERVER_client_drop (session->client); + session->client = NULL; + } + if (session->transmit_handle != NULL) + { + GNUNET_NETWORK_notify_transmit_ready_cancel (session->transmit_handle); + session->transmit_handle = NULL; + } + while (NULL != (pm = session->pending_messages)) + { + session->pending_messages = pm->next; + if (NULL != pm->transmit_cont) + pm->transmit_cont (pm->transmit_cont_cls, + session->service_context, + &session->target, GNUNET_SYSERR); + GNUNET_free (pm); + } + /* notify transport service about disconnect */ + session->plugin->env->receive (session->plugin->env->cls, + session, + session->service_context, + GNUNET_TIME_UNIT_ZERO, + &session->target, NULL); + GNUNET_free_non_null (session->connect_addr); + GNUNET_free (session); +} + + +/** + * Iterator callback to go over all addresses. If we get + * a TCP address, increment the counter + * + * @param cls closure, points to the counter + * @param tname name of the transport + * @param expiration expiration time + * @param addr the address + * @param addrlen length of the address + * @return GNUNET_OK to keep the address, + * GNUNET_NO to delete it from the HELLO + * GNUNET_SYSERR to stop iterating (but keep current address) + */ +static int +count_tcp_addresses (void *cls, + const char *tname, + struct GNUNET_TIME_Absolute expiration, + const void *addr, size_t addrlen) +{ + unsigned int *counter = cls; + + if (0 != strcmp (tname, "tcp")) + return GNUNET_OK; /* not one of ours */ + (*counter)++; + return GNUNET_OK; /* failed to connect */ +} + + +struct ConnectContext +{ + struct Plugin *plugin; + + struct GNUNET_NETWORK_SocketHandle *sa; + + struct PendingMessage *welcome; + + unsigned int pos; +}; + + +/** + * Iterator callback to go over all addresses. If we get + * the "pos" TCP address, try to connect to it. + * + * @param cls closure + * @param tname name of the transport + * @param expiration expiration time + * @param addrlen length of the address + * @param addr the address + * @return GNUNET_OK to keep the address, + * GNUNET_NO to delete it from the HELLO + * GNUNET_SYSERR to stop iterating (but keep current address) + */ +static int +try_connect_to_address (void *cls, + const char *tname, + struct GNUNET_TIME_Absolute expiration, + const void *addr, size_t addrlen) +{ + struct ConnectContext *cc = cls; + int af; + + if (0 != strcmp (tname, "tcp")) + return GNUNET_OK; /* not one of ours */ + if (sizeof (struct sockaddr_in) == addrlen) + af = AF_INET; + else if (sizeof (struct sockaddr_in6) == addrlen) + af = AF_INET6; + else + { + /* not a valid address */ + GNUNET_break (0); + return GNUNET_NO; + } + if (0 == cc->pos--) + { + cc->welcome = create_welcome (addrlen, addr, cc->plugin); + cc->sa = + GNUNET_NETWORK_socket_create_from_sockaddr (cc->plugin->env->sched, + af, addr, addrlen, + GNUNET_SERVER_MAX_MESSAGE_SIZE); +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", "Connected to other peer.\n"); +#endif + return GNUNET_SYSERR; + } + return GNUNET_OK; /* failed to connect */ +} + + +/** + * Type of an iterator over the hosts. Note that each + * host will be called with each available protocol. + * + * @param cls closure + * @param peer id of the peer, NULL for last call + * @param hello hello message for the peer (can be NULL) + * @param trust amount of trust we have in the peer + */ +static void +session_try_connect (void *cls, + const struct GNUNET_PeerIdentity *peer, + const struct GNUNET_HELLO_Message *hello, uint32_t trust) +{ + struct Session *session = cls; + unsigned int count; + struct ConnectContext cctx; + struct PendingMessage *pm; + + if (peer == NULL) + { + /* last call, destroy session if we are still not + connected */ + if (session->still_connecting == GNUNET_NO) + { +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", + "Connected to other peer, now processing messages.\n"); +#endif + process_pending_messages (session); + } + else + { +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", + "Failed to connect to other peer, now closing session.\n"); +#endif + disconnect_session (session); + } + return; + } + if ((hello == NULL) || (session->client != NULL)) + { + GNUNET_break (0); /* should this ever happen!? */ + return; + } + count = 0; + GNUNET_HELLO_iterate_addresses (hello, + GNUNET_NO, &count_tcp_addresses, &count); + if (count == 0) + { +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", + "Asked to connect, but have no addresses to try.\n"); +#endif + return; + } + cctx.plugin = session->plugin; + cctx.sa = NULL; + cctx.welcome = NULL; + cctx.pos = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, count); + GNUNET_HELLO_iterate_addresses (hello, + GNUNET_NO, &try_connect_to_address, &cctx); + if (cctx.sa == NULL) + { +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", + "Asked to connect, but all addresses failed.\n"); +#endif + GNUNET_free_non_null (cctx.welcome); + return; + } + session->client = GNUNET_SERVER_connect_socket (session->plugin->server, + cctx.sa); +#if DEBUG_TCP + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Connected getting client address %p\n", session->client); +#endif + if (session->client == NULL) + { + GNUNET_break (0); /* how could this happen? */ + GNUNET_free_non_null (cctx.welcome); + return; + } + pm = cctx.welcome; + /* prepend (!) */ + pm->next = session->pending_messages; + session->pending_messages = pm; + session->still_connecting = GNUNET_NO; +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", + "Connected to other peer, now sending `%s' message.\n", + "WELCOME"); +#endif +} + + +/** + * Function that can be used by the transport service to transmit + * a message using the plugin. + * + * @param cls closure + * @param plugin_context value we were asked to pass to this plugin + * to respond to the given peer (use is optional, + * but may speed up processing), can be NULL + * @param service_context value passed to the transport-service + * to identify the neighbour + * @param target who should receive this message + * @param msg the message to transmit + * @param cont continuation to call once the message has + * been transmitted (or if the transport is ready + * for the next transmission call; or if the + * peer disconnected...) + * @param cont_cls closure for cont + * @return plugin_context that should be used next time for + * sending messages to the specified peer + */ +static void * +tcp_plugin_send (void *cls, + void *plugin_context, + struct ReadyList *service_context, + const struct GNUNET_PeerIdentity *target, + const struct GNUNET_MessageHeader *msg, + struct GNUNET_TIME_Relative timeout, + GNUNET_TRANSPORT_TransmitContinuation cont, void *cont_cls) +{ + struct Plugin *plugin = cls; + struct Session *session = plugin_context; + struct PendingMessage *pm; + struct PendingMessage *pme; + + if (session == NULL) + session = find_session_by_target (plugin, target); + pm = GNUNET_malloc (sizeof (struct PendingMessage) + ntohs (msg->size)); + pm->msg = (struct GNUNET_MessageHeader *) &pm[1]; + memcpy (pm->msg, msg, ntohs (msg->size)); + pm->timeout = GNUNET_TIME_relative_to_absolute (timeout); + pm->transmit_cont = cont; + pm->transmit_cont_cls = cont_cls; + if (session == NULL) + { + session = GNUNET_malloc (sizeof (struct Session)); +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", + "Asked to transmit, creating fresh session %p.\n", + session); +#endif + session->next = plugin->sessions; + plugin->sessions = session; + session->plugin = plugin; + session->target = *target; + session->last_quota_update = GNUNET_TIME_absolute_get (); + session->quota_in = plugin->env->default_quota_in; + session->expecting_welcome = GNUNET_YES; + session->still_connecting = GNUNET_YES; + session->pending_messages = pm; + GNUNET_PEERINFO_for_all (plugin->env->cfg, + plugin->env->sched, + target, + 0, timeout, &session_try_connect, session); + return session; + } + GNUNET_assert (session != NULL); + GNUNET_assert (session->still_connecting == GNUNET_NO); + /* append pm to pending_messages list */ + pme = session->pending_messages; + if (pme == NULL) + { + session->pending_messages = pm; + } + else + { + while (NULL != pme->next) + pme = pme->next; + pme->next = pm; + } +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", "Asked to transmit, added message to list.\n"); +#endif + process_pending_messages (session); + return session; +} + + + +/** + * Function that can be called to force a disconnect from the + * specified neighbour. This should also cancel all previously + * scheduled transmissions. Obviously the transmission may have been + * partially completed already, which is OK. The plugin is supposed + * to close the connection (if applicable) and no longer call the + * transmit continuation(s). + * + * Finally, plugin MUST NOT call the services's receive function to + * notify the service that the connection to the specified target was + * closed after a getting this call. + * + * @param cls closure + * @param plugin_context value we were asked to pass to this plugin + * to respond to the given peer (use is optional, + * but may speed up processing), can be NULL (if + * NULL was returned from the transmit function) + * @param service_context must correspond to the service context + * of the corresponding Transmit call; the plugin should + * not cancel a send call made with a different service + * context pointer! Never NULL. + * @param target peer for which the last transmission is + * to be cancelled + */ +static void +tcp_plugin_cancel (void *cls, + void *plugin_context, + struct ReadyList *service_context, + const struct GNUNET_PeerIdentity *target) +{ + struct Plugin *plugin = cls; + struct PendingMessage *pm; + struct Session *session; + struct Session *next; + + session = plugin->sessions; + while (session != NULL) + { + next = session->next; + if (0 == memcmp (target, + &session->target, sizeof (struct GNUNET_PeerIdentity))) + { + pm = session->pending_messages; + while (pm != NULL) + { + pm->transmit_cont = NULL; + pm->transmit_cont_cls = NULL; + pm = pm->next; + } + session->service_context = NULL; + GNUNET_SERVER_client_disconnect (session->client); + /* rest of the clean-up of the session will be done as part of + disconnect_notify which should be triggered any time now */ + } + session = next; + } +} + + +struct PrettyPrinterContext +{ + GNUNET_TRANSPORT_AddressStringCallback asc; + void *asc_cls; + uint16_t port; +}; + + +/** + * Append our port and forward the result. + */ +static void +append_port (void *cls, const char *hostname) +{ + struct PrettyPrinterContext *ppc = cls; + char *ret; + + if (hostname == NULL) + { + ppc->asc (ppc->asc_cls, NULL); + GNUNET_free (ppc); + return; + } + GNUNET_asprintf (&ret, "%s:%d", hostname, ppc->port); + ppc->asc (ppc->asc_cls, ret); + GNUNET_free (ret); +} + + +/** + * Convert the transports address to a nice, human-readable + * format. + * + * @param cls closure + * @param name name of the transport that generated the address + * @param addr one of the addresses of the host, NULL for the last address + * the specific address format depends on the transport + * @param addrlen length of the address + * @param numeric should (IP) addresses be displayed in numeric form? + * @param timeout after how long should we give up? + * @param asc function to call on each string + * @param asc_cls closure for asc + */ +static void +tcp_plugin_address_pretty_printer (void *cls, + const char *type, + const void *addr, + size_t addrlen, + int numeric, + struct GNUNET_TIME_Relative timeout, + GNUNET_TRANSPORT_AddressStringCallback asc, + void *asc_cls) +{ + struct Plugin *plugin = cls; + const struct sockaddr_in *v4; + const struct sockaddr_in6 *v6; + struct PrettyPrinterContext *ppc; + + if ((addrlen != sizeof (struct sockaddr_in)) && + (addrlen != sizeof (struct sockaddr_in6))) + { + /* invalid address */ + GNUNET_break_op (0); + asc (asc_cls, NULL); + return; + } + ppc = GNUNET_malloc (sizeof (struct PrettyPrinterContext)); + ppc->asc = asc; + ppc->asc_cls = asc_cls; + if (addrlen == sizeof (struct sockaddr_in)) + { + v4 = (const struct sockaddr_in *) addr; + ppc->port = ntohs (v4->sin_port); + } + else + { + v6 = (const struct sockaddr_in6 *) addr; + ppc->port = ntohs (v6->sin6_port); + + } + GNUNET_RESOLVER_hostname_get (plugin->env->sched, + plugin->env->cfg, + addr, + addrlen, + !numeric, timeout, &append_port, ppc); +} + + +/** + * Update the last-received and bandwidth quota values + * for this session. + * + * @param session session to update + * @param force set to GNUNET_YES if we should update even + * though the minimum refresh time has not yet expired + */ +static void +update_quota (struct Session *session, int force) +{ + struct GNUNET_TIME_Absolute now; + unsigned long long delta; + unsigned long long total_allowed; + unsigned long long total_remaining; + + now = GNUNET_TIME_absolute_get (); + delta = now.value - session->last_quota_update.value; + if ((delta < MIN_QUOTA_REFRESH_TIME) && (!force)) + return; /* too early, not enough data */ + + total_allowed = session->quota_in * delta; + if (total_allowed > session->last_received) + { + /* got less than acceptable */ + total_remaining = total_allowed - session->last_received; + session->last_received = 0; + delta = total_remaining / session->quota_in; /* bonus seconds */ + if (delta > MAX_BANDWIDTH_CARRY) + delta = MAX_BANDWIDTH_CARRY; /* limit amount of carry-over */ + } + else + { + /* got more than acceptable */ + total_remaining = 0; + session->last_received -= total_allowed; + delta = 0; + } + session->last_quota_update.value = now.value - delta; +} + + +/** + * Set a quota for receiving data from the given peer; this is a + * per-transport limit. The transport should limit its read/select + * calls to stay below the quota (in terms of incoming data). + * + * @param cls closure + * @param peer the peer for whom the quota is given + * @param quota_in quota for receiving/sending data in bytes per ms + */ +static void +tcp_plugin_set_receive_quota (void *cls, + const struct GNUNET_PeerIdentity *target, + uint32_t quota_in) +{ + struct Plugin *plugin = cls; + struct Session *session; + + session = find_session_by_target (plugin, target); + if (session->quota_in != quota_in) + { + update_quota (session, GNUNET_YES); + if (session->quota_in > quota_in) + session->last_quota_update = GNUNET_TIME_absolute_get (); + session->quota_in = quota_in; + } +} + + +/** + * Check if the given port is plausible (must be either + * our listen port or our advertised port). If it is + * neither, we return one of these two ports at random. + * + * @return either in_port or a more plausible port + */ +static uint16_t +check_port (struct Plugin *plugin, uint16_t in_port) +{ + if ((in_port == plugin->adv_port) || (in_port == plugin->open_port)) + return in_port; + return (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, + 2) == 0) + ? plugin->open_port : plugin->adv_port; +} + + +/** + * Another peer has suggested an address for this + * peer and transport plugin. Check that this could be a valid + * address. If so, consider adding it to the list + * of addresses. + * + * @param cls closure + * @param addr pointer to the address + * @param addrlen length of addr + * @return GNUNET_OK if this is a plausible address for this peer + * and transport + */ +static int +tcp_plugin_address_suggested (void *cls, const void *addr, size_t addrlen) +{ + struct Plugin *plugin = cls; + char buf[sizeof (struct sockaddr_in6)]; + struct sockaddr_in *v4; + struct sockaddr_in6 *v6; + char dst[INET6_ADDRSTRLEN]; + uint16_t port; + + if ((addrlen != sizeof (struct sockaddr_in)) && + (addrlen != sizeof (struct sockaddr_in6))) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + memcpy (buf, addr, sizeof (struct sockaddr_in6)); + if (addrlen == sizeof (struct sockaddr_in)) + { + v4 = (struct sockaddr_in *) buf; + v4->sin_port = htons (check_port (plugin, ntohs (v4->sin_port))); + inet_ntop (AF_INET, &v4->sin_addr, dst, sizeof (dst)); + port = ntohs (v4->sin_port); + } + else + { + v6 = (struct sockaddr_in6 *) buf; + v6->sin6_port = htons (check_port (plugin, ntohs (v6->sin6_port))); + inet_ntop (AF_INET6, &v6->sin6_addr, dst, sizeof (dst)); + port = ntohs (v6->sin6_port); + } +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", + "Informing transport service about my address `%s:%u'.\n", + dst, port); +#endif + plugin->env->notify_address (plugin->env->cls, + "tcp", + buf, addrlen, LEARNED_ADDRESS_EXPIRATION); + return GNUNET_OK; +} + + +/** + * We've received a welcome from this peer via TCP. + * Possibly create a fresh client record and send back + * our welcome. + * + * @param cls closure + * @param server the server handling the message + * @param client identification of the client + * @param message the actual message + */ +static void +handle_tcp_welcome (void *cls, + struct GNUNET_SERVER_Handle *server, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message) +{ + struct Plugin *plugin = cls; + struct Session *session_c; + const struct WelcomeMessage *wm; + uint16_t msize; + uint32_t addrlen; + size_t alen; + void *vaddr; + const struct sockaddr *addr; + +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", + "Received `%s' message from %p.\n", "WELCOME", client); +#endif + msize = ntohs (message->size); + if (msize < sizeof (struct WelcomeMessage)) + { + GNUNET_break_op (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return; + } + wm = (const struct WelcomeMessage *) message; + session_c = find_session_by_client (plugin, client); + if (session_c == NULL) + { + vaddr = NULL; + GNUNET_SERVER_client_get_address (client, &vaddr, &alen); + GNUNET_SERVER_client_keep (client); + session_c = create_session (plugin, + &wm->clientIdentity, client, vaddr, alen); +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", + "Creating new session %p for incoming `%s' message.\n", + session_c, "WELCOME"); +#endif + GNUNET_free_non_null (vaddr); + process_pending_messages (session_c); + } + session_c->expecting_welcome = GNUNET_NO; + if (0 < (addrlen = msize - sizeof (struct WelcomeMessage))) + { + addr = (const struct sockaddr *) &wm[1]; + tcp_plugin_address_suggested (plugin, addr, addrlen); + } + GNUNET_SERVER_receive_done (client, GNUNET_OK); +} + + +/** + * Calculate how long we should delay reading from the TCP socket to + * ensure that we stay within our bandwidth limits (push back). + * + * @param session for which client should this be calculated + */ +static struct GNUNET_TIME_Relative +calculate_throttle_delay (struct Session *session) +{ + struct GNUNET_TIME_Relative ret; + struct GNUNET_TIME_Absolute now; + uint64_t del; + uint64_t avail; + uint64_t excess; + + now = GNUNET_TIME_absolute_get (); + del = now.value - session->last_quota_update.value; + if (del > MAX_BANDWIDTH_CARRY) + { + update_quota (session, GNUNET_YES); + del = now.value - session->last_quota_update.value; + GNUNET_assert (del <= MAX_BANDWIDTH_CARRY); + } + if (session->quota_in == 0) + session->quota_in = 1; /* avoid divison by zero */ + avail = del * session->quota_in; + if (avail > session->last_received) + return GNUNET_TIME_UNIT_ZERO; /* can receive right now */ + excess = session->last_received - avail; + ret.value = excess / session->quota_in; + return ret; +} + + +/** + * Task to signal the server that we can continue + * receiving from the TCP client now. + */ +static void +delayed_done (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct Session *session = cls; + GNUNET_SERVER_receive_done (session->client, GNUNET_OK); +} + + +/** + * We've received data for this peer via TCP. Unbox, + * compute latency and forward. + * + * @param cls closure + * @param server the server handling the message + * @param client identification of the client + * @param message the actual message + */ +static void +handle_tcp_data (void *cls, + struct GNUNET_SERVER_Handle *server, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message) +{ + struct Plugin *plugin = cls; + struct Session *session; + const struct DataMessage *dm; + uint16_t msize; + const struct GNUNET_MessageHeader *msg; + struct GNUNET_TIME_Relative latency; + struct GNUNET_TIME_Absolute ttime; + struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Relative delay; + uint64_t ack_in; + +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", "Receiving data from other peer.\n"); +#endif + msize = ntohs (message->size); + if ((msize < + sizeof (struct DataMessage) + sizeof (struct GNUNET_MessageHeader))) + { + GNUNET_break_op (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return; + } + session = find_session_by_client (plugin, client); + if ((NULL == session) || (GNUNET_YES == session->expecting_welcome)) + { + GNUNET_break_op (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return; + } + dm = (const struct DataMessage *) message; + session->max_in_msg_counter = GNUNET_MAX (session->max_in_msg_counter, + GNUNET_ntohll (dm->ack_out)); + msg = (const struct GNUNET_MessageHeader *) &dm[1]; + if (msize != sizeof (struct DataMessage) + ntohs (msg->size)) + { + GNUNET_break_op (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return; + } + /* estimate latency */ + ack_in = GNUNET_ntohll (dm->ack_in); + if ((ack_in <= session->out_msg_counter) && + (session->out_msg_counter - ack_in < ACK_LOG_SIZE)) + { + delay = GNUNET_TIME_relative_ntoh (dm->delay); + ttime = session->gen_time[ack_in % ACK_LOG_SIZE]; + now = GNUNET_TIME_absolute_get (); + if (delay.value > now.value - ttime.value) + delay.value = 0; /* not plausible */ + /* update (round-trip) latency using ageing; we + use 7:1 so that we can reasonably quickly react + to changes, but not so fast that latency is largely + jitter... */ + session->latency_estimate + = ((7 * session->latency_estimate) + + (now.value - ttime.value - delay.value)) / 8; + } + latency.value = (uint64_t) session->latency_estimate; + /* deliver on */ +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", + "Forwarding data of type %u to transport service.\n", + ntohs (msg->type)); +#endif + session->service_context + = plugin->env->receive (plugin->env->cls, + session, + session->service_context, + latency, &session->target, msg); + /* update bandwidth used */ + session->last_received += msize; + update_quota (session, GNUNET_NO); + + delay = calculate_throttle_delay (session); + if (delay.value == 0) + GNUNET_SERVER_receive_done (client, GNUNET_OK); + else + GNUNET_SCHEDULER_add_delayed (session->plugin->env->sched, + GNUNET_NO, + GNUNET_SCHEDULER_PRIORITY_HIGH, + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, + delay, &delayed_done, session); +} + + +/** + * Handlers for the various TCP messages. + */ +static struct GNUNET_SERVER_MessageHandler my_handlers[] = { + {&handle_tcp_welcome, NULL, GNUNET_MESSAGE_TYPE_TRANSPORT_TCP_WELCOME, 0}, + {&handle_tcp_data, NULL, GNUNET_MESSAGE_TYPE_TRANSPORT_TCP_DATA, 0}, + {NULL, NULL, 0, 0} +}; + + +static void +create_tcp_handlers (struct Plugin *plugin) +{ + unsigned int i; + plugin->handlers = GNUNET_malloc (sizeof (my_handlers)); + memcpy (plugin->handlers, my_handlers, sizeof (my_handlers)); + for (i = 0; + i < + sizeof (my_handlers) / sizeof (struct GNUNET_SERVER_MessageHandler); + i++) + plugin->handlers[i].callback_cls = plugin; + GNUNET_SERVER_add_handlers (plugin->server, plugin->handlers); +} + + +/** + * Functions with this signature are called whenever a peer + * is disconnected on the network level. + * + * @param cls closure + * @param client identification of the client + */ +static void +disconnect_notify (void *cls, struct GNUNET_SERVER_Client *client) +{ + struct Plugin *plugin = cls; + struct Session *session; + +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", + "Notified about network-level disconnect of client %p.\n", + client); +#endif + session = find_session_by_client (plugin, client); + if (session == NULL) + return; /* unknown, nothing to do */ +#if DEBUG_TCP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + "tcp", "Will now destroy session %p.\n", session); +#endif + disconnect_session (session); +} + + +/** + * Add the IP of our network interface to the list of + * our external IP addresses. + */ +static int +process_interfaces (void *cls, + const char *name, + int isDefault, + const struct sockaddr *addr, socklen_t addrlen) +{ + struct Plugin *plugin = cls; + char dst[INET6_ADDRSTRLEN]; + int af; + struct sockaddr_in *v4; + struct sockaddr_in6 *v6; + + af = addr->sa_family; + if (af == AF_INET) + { + v4 = (struct sockaddr_in *) addr; + inet_ntop (AF_INET, &v4->sin_addr, dst, sizeof (dst)); + v4->sin_port = htons (plugin->adv_port); + } + else + { + GNUNET_assert (af == AF_INET6); + v6 = (struct sockaddr_in6 *) addr; + inet_ntop (AF_INET6, &v6->sin6_addr, dst, sizeof (dst)); + v6->sin6_port = htons (plugin->adv_port); + } + GNUNET_log_from (GNUNET_ERROR_TYPE_INFO | + GNUNET_ERROR_TYPE_BULK, + "tcp", _("Found address `%s' (%s)\n"), dst, name); + plugin->env->notify_address (plugin->env->cls, + "tcp", + addr, addrlen, GNUNET_TIME_UNIT_FOREVER_REL); + return GNUNET_OK; +} + + +/** + * Function called by the resolver for each address obtained from DNS + * for our own hostname. Add the addresses to the list of our + * external IP addresses. + * + * @param cls closure + * @param addr one of the addresses of the host, NULL for the last address + * @param addrlen length of the address + */ +static void +process_hostname_ips (void *cls, + const struct sockaddr *addr, socklen_t addrlen) +{ + struct Plugin *plugin = cls; + + if (addr == NULL) + return; + plugin->env->notify_address (plugin->env->cls, + "tcp", + addr, addrlen, GNUNET_TIME_UNIT_FOREVER_REL); +} + + +/** + * Entry point for the plugin. + */ +void * +libgnunet_plugin_transport_tcp_init (void *cls) +{ + struct GNUNET_TRANSPORT_PluginEnvironment *env = cls; + struct GNUNET_TRANSPORT_PluginFunctions *api; + struct Plugin *plugin; + struct GNUNET_SERVICE_Context *service; + unsigned long long aport; + unsigned long long bport; + + service = GNUNET_SERVICE_start ("tcp", env->sched, env->cfg); + if (service == NULL) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, + "tcp", + _ + ("Failed to start service for `%s' transport plugin.\n"), + "tcp"); + return NULL; + } + aport = 0; + if ((GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (env->cfg, + "tcp", + "PORT", + &bport)) || + (bport > 65535) || + ((GNUNET_OK == + GNUNET_CONFIGURATION_get_value_number (env->cfg, + "tcp", + "ADVERTISED-PORT", + &aport)) && (aport > 65535))) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, + "tcp", + _ + ("Require valid port number for service `%s' in configuration!\n"), + "tcp"); + GNUNET_SERVICE_stop (service); + return NULL; + } + if (aport == 0) + aport = bport; + plugin = GNUNET_malloc (sizeof (struct Plugin)); + plugin->open_port = bport; + plugin->adv_port = aport; + plugin->env = env; + plugin->lsock = NULL; + plugin->statistics = NULL; + api = GNUNET_malloc (sizeof (struct GNUNET_TRANSPORT_PluginFunctions)); + api->cls = plugin; + api->send_to = &tcp_plugin_send_to; + api->send = &tcp_plugin_send; + api->cancel = &tcp_plugin_cancel; + api->address_pretty_printer = &tcp_plugin_address_pretty_printer; + api->set_receive_quota = &tcp_plugin_set_receive_quota; + api->address_suggested = &tcp_plugin_address_suggested; + api->cost_estimate = 42; /* TODO: ATS */ + plugin->service = service; + plugin->server = GNUNET_SERVICE_get_server (service); + create_tcp_handlers (plugin); + GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, + "tcp", _("TCP transport listening on port %u\n"), bport); + if (aport != bport) + GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, + "tcp", + _ + ("TCP transport advertises itself as being on port %u\n"), + aport); + GNUNET_SERVER_disconnect_notify (plugin->server, &disconnect_notify, + plugin); + GNUNET_OS_network_interfaces_list (&process_interfaces, plugin); + GNUNET_RESOLVER_hostname_resolve (env->sched, + env->cfg, + AF_UNSPEC, + HOSTNAME_RESOLVE_TIMEOUT, + &process_hostname_ips, plugin); + return api; +} + + +/** + * Exit point from the plugin. + */ +void * +libgnunet_plugin_transport_tcp_done (void *cls) +{ + struct GNUNET_TRANSPORT_PluginFunctions *api = cls; + struct Plugin *plugin = api->cls; + + GNUNET_SERVICE_stop (plugin->service); + GNUNET_free (plugin->handlers); + GNUNET_free (plugin); + GNUNET_free (api); + return NULL; +} + +/* end of plugin_transport_tcp.c */ diff --git a/src/transport/plugin_transport_template.c b/src/transport/plugin_transport_template.c new file mode 100644 index 000000000..1c8b06c61 --- /dev/null +++ b/src/transport/plugin_transport_template.c @@ -0,0 +1,335 @@ +/* + This file is part of GNUnet + (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file transport/plugin_transport_template.c + * @brief template for a new transport service + * @author Christian Grothoff + */ + +#include "platform.h" +#include "gnunet_protocols.h" +#include "gnunet_network_lib.h" +#include "gnunet_server_lib.h" +#include "gnunet_service_lib.h" +#include "gnunet_statistics_service.h" +#include "gnunet_transport_service.h" +#include "plugin_transport.h" + +#define DEBUG_TEMPLATE GNUNET_NO + +/** + * After how long do we expire an address that we + * learned from another peer if it is not reconfirmed + * by anyone? + */ +#define LEARNED_ADDRESS_EXPIRATION GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS, 6) + + +/** + * Encapsulation of all of the state of the plugin. + */ +struct Plugin; + + +/** + * Session handle for connections. + */ +struct Session +{ + + /** + * Stored in a linked list. + */ + struct Session *next; + + /** + * Pointer to the global plugin struct. + */ + struct Plugin *plugin; + + /** + * The client (used to identify this connection) + */ + /* void *client; */ + + /** + * Continuation function to call once the transmission buffer + * has again space available. NULL if there is no + * continuation to call. + */ + GNUNET_TRANSPORT_TransmitContinuation transmit_cont; + + /** + * Closure for transmit_cont. + */ + void *transmit_cont_cls; + + /** + * To whom are we talking to (set to our identity + * if we are still waiting for the welcome message) + */ + struct GNUNET_PeerIdentity sender; + + /** + * At what time did we reset last_received last? + */ + struct GNUNET_TIME_Absolute last_quota_update; + + /** + * How many bytes have we received since the "last_quota_update" + * timestamp? + */ + uint64_t last_received; + + /** + * Number of bytes per ms that this peer is allowed + * to send to us. + */ + uint32_t quota; + +}; + +/** + * Encapsulation of all of the state of the plugin. + */ +struct Plugin +{ + /** + * Our environment. + */ + struct GNUNET_TRANSPORT_PluginEnvironment *env; + + /** + * List of open sessions. + */ + struct Session *sessions; + + /** + * Handle for the statistics service. + */ + struct GNUNET_STATISTICS_Handle *statistics; + +}; + + + +/** + * Function that can be used by the transport service to transmit + * a message using the plugin using a fresh connection (even if + * we already have a connection to this peer, this function is + * required to establish a new one). + * + * @param cls closure + * @param target who should receive this message + * @param msg1 first message to transmit + * @param msg2 second message to transmit (can be NULL) + * @param timeout how long until we give up? + * @param addr the address + * @param addrlen length of the address + * @return non-null session if the transmission has been scheduled + * NULL if the address format is invalid + */ +static void * +template_plugin_send_to (void *cls, + const struct GNUNET_PeerIdentity *target, + const struct GNUNET_MessageHeader *msg1, + const struct GNUNET_MessageHeader *msg2, + struct GNUNET_TIME_Relative timeout, + const void *addr, size_t addrlen) +{ + // FIXME + return NULL; +} + + +/** + * Function that can be used by the transport service to transmit + * a message using the plugin. + * + * @param cls closure + * @param plugin_context value we were asked to pass to this plugin + * to respond to the given peer (use is optional, + * but may speed up processing), can be NULL + * @param service_context value passed to the transport-service + * to identify the neighbour + * @param target who should receive this message + * @param msg the message to transmit + * @param cont continuation to call once the message has + * been transmitted (or if the transport is ready + * for the next transmission call; or if the + * peer disconnected...) + * @param cont_cls closure for cont + * @return plugin_context that should be used next time for + * sending messages to the specified peer + */ +static void * +template_plugin_send (void *cls, + void *plugin_context, + struct ReadyList *service_context, + const struct GNUNET_PeerIdentity *target, + const struct GNUNET_MessageHeader *msg, + struct GNUNET_TIME_Relative timeout, + GNUNET_TRANSPORT_TransmitContinuation cont, + void *cont_cls) +{ + // struct Plugin *plugin = cls; + return NULL; +} + + + +/** + * + * @param cls closure + * @param plugin_context value we were asked to pass to this plugin + * to respond to the given peer (use is optional, + * but may speed up processing), can be NULL (if + * NULL was returned from the transmit function) + * @param service_context must correspond to the service context + * of the corresponding Transmit call; the plugin should + * not cancel a send call made with a different service + * context pointer! Never NULL. + * @param target peer for which the last transmission is + * to be cancelled + */ +static void +template_plugin_cancel (void *cls, + void *plugin_context, + struct ReadyList *service_context, + const struct GNUNET_PeerIdentity *target) +{ + // struct Plugin *plugin = cls; + // FIXME +} + + +/** + * Convert the transports address to a nice, human-readable + * format. + * + * @param cls closure + * @param name name of the transport that generated the address + * @param addr one of the addresses of the host, NULL for the last address + * the specific address format depends on the transport + * @param addrlen length of the address + * @param numeric should (IP) addresses be displayed in numeric form? + * @param timeout after how long should we give up? + * @param asc function to call on each string + * @param asc_cls closure for asc + */ +static void +template_plugin_address_pretty_printer (void *cls, + const char *type, + const void *addr, + size_t addrlen, + int numeric, + struct GNUNET_TIME_Relative timeout, + GNUNET_TRANSPORT_AddressStringCallback + asc, void *asc_cls) +{ + asc (asc_cls, NULL); +} + +/** + * Set a quota for receiving data from the given peer; this is a + * per-transport limit. The transport should limit its read/select + * calls to stay below the quota (in terms of incoming data). + * + * @param cls closure + * @param peer the peer for whom the quota is given + * @param quota_in quota for receiving/sending data in bytes per ms + */ +static void +template_plugin_set_receive_quota (void *cls, + const struct GNUNET_PeerIdentity *target, + uint32_t quota_in) +{ + // struct Plugin *plugin = cls; + // FIXME! +} + + +/** + * Another peer has suggested an address for this + * peer and transport plugin. Check that this could be a valid + * address. If so, consider adding it to the list + * of addresses. + * + * @param cls closure + * @param addr pointer to the address + * @param addrlen length of addr + * @return GNUNET_OK if this is a plausible address for this peer + * and transport + */ +static int +template_plugin_address_suggested (void *cls, + const void *addr, size_t addrlen) +{ + // struct Plugin *plugin = cls; + + /* check if the address is plausible; if so, + add it to our list! */ + // FIXME! + return GNUNET_OK; +} + + +/** + * Entry point for the plugin. + */ +void * +gnunet_plugin_transport_template_init (void *cls) +{ + struct GNUNET_TRANSPORT_PluginEnvironment *env = cls; + struct GNUNET_TRANSPORT_PluginFunctions *api; + struct Plugin *plugin; + + plugin = GNUNET_malloc (sizeof (struct Plugin)); + plugin->env = env; + plugin->statistics = NULL; + api = GNUNET_malloc (sizeof (struct GNUNET_TRANSPORT_PluginFunctions)); + api->cls = plugin; + api->send_to = &template_plugin_send_to; + api->send = &template_plugin_send; + api->cancel = &template_plugin_cancel; + api->address_pretty_printer = &template_plugin_address_pretty_printer; + api->set_receive_quota = &template_plugin_set_receive_quota; + api->address_suggested = &template_plugin_address_suggested; + api->cost_estimate = 42; // FIXME + return api; +} + + +/** + * Exit point from the plugin. + */ +void * +gnunet_plugin_transport_template_done (void *cls) +{ + struct GNUNET_TRANSPORT_PluginFunctions *api = cls; + struct Plugin *plugin = api->cls; + + GNUNET_free (plugin); + GNUNET_free (api); + return NULL; +} + +/* end of plugin_transport_template.c */ diff --git a/src/transport/plugin_transport_udp.c b/src/transport/plugin_transport_udp.c new file mode 100644 index 000000000..ccaf9fbd1 --- /dev/null +++ b/src/transport/plugin_transport_udp.c @@ -0,0 +1,592 @@ +/* + This file is part of GNUnet + (C) 2001, 2002, 2003, 2004, 2005, 2008 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file transports/udp.c + * @brief Implementation of the UDP transport service + * @author Christian Grothoff + */ + +#include "platform.h" +#include "gnunet_util.h" +#include "gnunet_protocols.h" +#include "gnunet_transport.h" +#include "gnunet_stats_service.h" +#include "gnunet_upnp_service.h" +#include "ip.h" + +#define DEBUG_UDP GNUNET_YES + +/** + * The default maximum size of each outbound UDP message, + * optimal value for Ethernet (10 or 100 MBit). + */ +#define MESSAGE_SIZE 1472 + +/** + * Message-Packet header. + */ +typedef struct +{ + /** + * size of the message, in bytes, including this header. + */ + GNUNET_MessageHeader header; + + /** + * What is the identity of the sender (GNUNET_hash of public key) + */ + GNUNET_PeerIdentity sender; + +} UDPMessage; + +#define MY_TRANSPORT_NAME "UDP" +#include "common.c" + +/* *********** globals ************* */ + +static int stat_bytesReceived; + +static int stat_bytesSent; + +static int stat_bytesDropped; + +static int stat_udpConnected; + +/** + * thread that listens for inbound messages + */ +static struct GNUNET_SelectHandle *selector; + +/** + * the socket that we transmit all data with + */ +static struct GNUNET_SocketHandle *udp_sock; + +static struct GNUNET_LoadMonitor *load_monitor; + + +/** + * The socket of session has data waiting, process! + * + * This function may only be called if the tcplock is + * already held by the caller. + */ +static int +select_message_handler (void *mh_cls, + struct GNUNET_SelectHandle *sh, + struct GNUNET_SocketHandle *sock, + void *sock_ctx, const GNUNET_MessageHeader * msg) +{ + unsigned int len; + GNUNET_TransportPacket *mp; + const UDPMessage *um; + + len = ntohs (msg->size); + if (len <= sizeof (UDPMessage)) + { + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_WARNING | GNUNET_GE_USER | GNUNET_GE_BULK, + _("Received malformed message via %s. Ignored.\n"), + "UDP"); + return GNUNET_SYSERR; + } + um = (const UDPMessage *) msg; + mp = GNUNET_malloc (sizeof (GNUNET_TransportPacket)); + mp->msg = GNUNET_malloc (len - sizeof (UDPMessage)); + memcpy (mp->msg, &um[1], len - sizeof (UDPMessage)); + mp->sender = um->sender; + mp->size = len - sizeof (UDPMessage); + mp->tsession = NULL; + coreAPI->receive (mp); + if (stats != NULL) + stats->change (stat_bytesReceived, len); + return GNUNET_OK; +} + +static void * +select_accept_handler (void *ah_cls, + struct GNUNET_SelectHandle *sh, + struct GNUNET_SocketHandle *sock, + const void *addr, unsigned int addr_len) +{ + static int nonnullpointer; + + if (GNUNET_NO != is_rejected_tester (addr, addr_len)) + return NULL; + return &nonnullpointer; +} + +/** + * Select has been forced to close a connection. + * Free the associated context. + */ +static void +select_close_handler (void *ch_cls, + struct GNUNET_SelectHandle *sh, + struct GNUNET_SocketHandle *sock, void *sock_ctx) +{ + /* do nothing */ +} + +/** + * Establish a connection to a remote node. + * + * @param hello the hello-Message for the target node + * @param tsessionPtr the session handle that is to be set + * @return GNUNET_OK on success, GNUNET_SYSERR if the operation failed + */ +static int +udp_connect (const GNUNET_MessageHello * hello, + GNUNET_TSession ** tsessionPtr, int may_reuse) +{ + GNUNET_TSession *tsession; + + tsession = GNUNET_malloc (sizeof (GNUNET_TSession)); + memset (tsession, 0, sizeof (GNUNET_TSession)); + tsession->internal = GNUNET_malloc (GNUNET_sizeof_hello (hello)); + memcpy (tsession->internal, hello, GNUNET_sizeof_hello (hello)); + tsession->ttype = myAPI.protocol_number; + tsession->peer = hello->senderIdentity; + *tsessionPtr = tsession; + if (stats != NULL) + stats->change (stat_udpConnected, 1); + return GNUNET_OK; +} + +/** + * A (core) Session is to be associated with a transport session. The + * transport service may want to know in order to call back on the + * core if the connection is being closed. + * + * @param tsession the session handle passed along + * from the call to receive that was made by the transport + * layer + * @return GNUNET_OK if the session could be associated, + * GNUNET_SYSERR if not. + */ +int +udp_associate (GNUNET_TSession * tsession) +{ + return GNUNET_SYSERR; /* UDP connections can never be associated */ +} + +/** + * Disconnect from a remote node. + * + * @param tsession the session that is closed + * @return GNUNET_OK on success, GNUNET_SYSERR if the operation failed + */ +static int +udp_disconnect (GNUNET_TSession * tsession) +{ + if (tsession != NULL) + { + if (tsession->internal != NULL) + GNUNET_free (tsession->internal); + GNUNET_free (tsession); + if (stats != NULL) + stats->change (stat_udpConnected, -1); + } + return GNUNET_OK; +} + +/** + * Shutdown the server process (stop receiving inbound traffic). Maybe + * restarted later! + */ +static int +udp_transport_server_stop () +{ + GNUNET_GE_ASSERT (coreAPI->ectx, udp_sock != NULL); + if (selector != NULL) + { + GNUNET_select_destroy (selector); + selector = NULL; + } + GNUNET_socket_destroy (udp_sock); + udp_sock = NULL; + return GNUNET_OK; +} + +/** + * Test if the transport would even try to send + * a message of the given size and importance + * for the given session.
+ * This function is used to check if the core should + * even bother to construct (and encrypt) this kind + * of message. + * + * @return GNUNET_YES if the transport would try (i.e. queue + * the message or call the OS to send), + * GNUNET_NO if the transport would just drop the message, + * GNUNET_SYSERR if the size/session is invalid + */ +static int +udp_test_would_try (GNUNET_TSession * tsession, unsigned int size, + int important) +{ + const GNUNET_MessageHello *hello; + + if (udp_sock == NULL) + return GNUNET_SYSERR; + if (size == 0) + { + GNUNET_GE_BREAK (coreAPI->ectx, 0); + return GNUNET_SYSERR; + } + if (size > myAPI.mtu) + { + GNUNET_GE_BREAK (coreAPI->ectx, 0); + return GNUNET_SYSERR; + } + hello = (const GNUNET_MessageHello *) tsession->internal; + if (hello == NULL) + return GNUNET_SYSERR; + return GNUNET_YES; +} + +/** + * Create a UDP socket. If possible, use IPv6, otherwise + * try IPv4. Update available_protocols accordingly. + */ +static int +udp_create_socket () +{ + int s; + + available_protocols = VERSION_AVAILABLE_NONE; + s = -1; + if (GNUNET_YES != + GNUNET_GC_get_configuration_value_yesno (cfg, "GNUNETD", "DISABLE-IPV6", + GNUNET_YES)) + { +#ifndef MINGW + s = SOCKET (PF_INET6, SOCK_DGRAM, 17); +#else + s = win_ols_socket (PF_INET6, SOCK_DGRAM, 17); +#endif + } + if (s < 0) + { +#ifndef MINGW + s = SOCKET (PF_INET, SOCK_DGRAM, 17); +#else + s = win_ols_socket (PF_INET, SOCK_DGRAM, 17); +#endif + if (s < 0) + { + GNUNET_GE_LOG_STRERROR (coreAPI->ectx, + GNUNET_GE_ERROR | GNUNET_GE_ADMIN | + GNUNET_GE_BULK, "socket"); + return GNUNET_SYSERR; + } + available_protocols = VERSION_AVAILABLE_IPV4; + } + else + { + available_protocols = VERSION_AVAILABLE_IPV6 | VERSION_AVAILABLE_IPV4; + } + return s; +} + +/** + * Send a message to the specified remote node. + * + * @param tsession the GNUNET_MessageHello identifying the remote node + * @param message what to send + * @param size the size of the message + * @return GNUNET_SYSERR on error, GNUNET_OK on success + */ +static int +udp_send (GNUNET_TSession * tsession, + const void *message, const unsigned int size, int important) +{ + const GNUNET_MessageHello *hello; + const HostAddress *haddr; + UDPMessage *mp; + struct sockaddr_in serverAddrv4; + struct sockaddr_in6 serverAddrv6; + struct sockaddr *serverAddr; + socklen_t addrlen; + unsigned short available; + int ok; + int ssize; + size_t sent; + + GNUNET_GE_ASSERT (NULL, tsession != NULL); + if (udp_sock == NULL) + return GNUNET_SYSERR; + if (size == 0) + { + GNUNET_GE_BREAK (coreAPI->ectx, 0); + return GNUNET_SYSERR; + } + if (size > myAPI.mtu) + { + GNUNET_GE_BREAK (coreAPI->ectx, 0); + return GNUNET_SYSERR; + } + hello = (const GNUNET_MessageHello *) tsession->internal; + if (hello == NULL) + return GNUNET_SYSERR; + + haddr = (const HostAddress *) &hello[1]; + available = ntohs (haddr->availability) & available_protocols; + if (available == VERSION_AVAILABLE_NONE) + return GNUNET_SYSERR; + if (available == (VERSION_AVAILABLE_IPV4 | VERSION_AVAILABLE_IPV6)) + { + if (GNUNET_random_u32 (GNUNET_RANDOM_QUALITY_WEAK, 2) == 0) + available = VERSION_AVAILABLE_IPV4; + else + available = VERSION_AVAILABLE_IPV6; + } + ssize = size + sizeof (UDPMessage); + mp = GNUNET_malloc (ssize); + mp->header.size = htons (ssize); + mp->header.type = 0; + mp->sender = *(coreAPI->my_identity); + memcpy (&mp[1], message, size); + ok = GNUNET_SYSERR; + + if ((available & VERSION_AVAILABLE_IPV4) > 0) + { + memset (&serverAddrv4, 0, sizeof (serverAddrv4)); + serverAddrv4.sin_family = AF_INET; + serverAddrv4.sin_port = haddr->port; + memcpy (&serverAddrv4.sin_addr, &haddr->ipv4, sizeof (struct in_addr)); + addrlen = sizeof (serverAddrv4); + serverAddr = (struct sockaddr *) &serverAddrv4; + } + else + { + memset (&serverAddrv6, 0, sizeof (serverAddrv6)); + serverAddrv6.sin6_family = AF_INET; + serverAddrv6.sin6_port = haddr->port; + memcpy (&serverAddrv6.sin6_addr, &haddr->ipv6, + sizeof (struct in6_addr)); + addrlen = sizeof (serverAddrv6); + serverAddr = (struct sockaddr *) &serverAddrv6; + } +#ifndef MINGW + if (GNUNET_YES == GNUNET_socket_send_to (udp_sock, + GNUNET_NC_NONBLOCKING, + mp, + ssize, &sent, + (const char *) serverAddr, + addrlen)) +#else + sent = + win_ols_sendto (udp_sock, mp, ssize, (const char *) serverAddr, addrlen); + if (sent != SOCKET_ERROR) +#endif + { + ok = GNUNET_OK; + if (stats != NULL) + stats->change (stat_bytesSent, sent); + } + else + { + if (stats != NULL) + stats->change (stat_bytesDropped, ssize); + } + GNUNET_free (mp); + return ok; +} + +/** + * Start the server process to receive inbound traffic. + * + * @return GNUNET_OK on success, GNUNET_SYSERR if the operation failed + */ +static int +udp_transport_server_start () +{ + struct sockaddr_in serverAddrv4; + struct sockaddr_in6 serverAddrv6; + struct sockaddr *serverAddr; + socklen_t addrlen; + int sock; + const int on = 1; + unsigned short port; + + GNUNET_GE_ASSERT (coreAPI->ectx, selector == NULL); + /* initialize UDP network */ + port = get_port (); + if (port != 0) + { + sock = udp_create_socket (); + if (sock < 0) + return GNUNET_SYSERR; + if (SETSOCKOPT (sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) < 0) + { + GNUNET_GE_DIE_STRERROR (coreAPI->ectx, + GNUNET_GE_FATAL | GNUNET_GE_ADMIN | + GNUNET_GE_IMMEDIATE, "setsockopt"); + return GNUNET_SYSERR; + } + if (available_protocols == VERSION_AVAILABLE_IPV4) + { + memset (&serverAddrv4, 0, sizeof (serverAddrv4)); + serverAddrv4.sin_family = AF_INET; + serverAddrv4.sin_addr.s_addr = INADDR_ANY; + serverAddrv4.sin_port = htons (port); + addrlen = sizeof (serverAddrv4); + serverAddr = (struct sockaddr *) &serverAddrv4; + } + else + { + memset (&serverAddrv6, 0, sizeof (serverAddrv6)); + serverAddrv6.sin6_family = AF_INET6; + serverAddrv6.sin6_addr = in6addr_any; + serverAddrv6.sin6_port = htons (port); + addrlen = sizeof (serverAddrv6); + serverAddr = (struct sockaddr *) &serverAddrv6; + } + if (BIND (sock, serverAddr, addrlen) < 0) + { + GNUNET_GE_LOG_STRERROR (coreAPI->ectx, + GNUNET_GE_FATAL | GNUNET_GE_ADMIN | + GNUNET_GE_IMMEDIATE, "bind"); + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_FATAL | GNUNET_GE_ADMIN | + GNUNET_GE_IMMEDIATE, + _("Failed to bind to %s port %d.\n"), + MY_TRANSPORT_NAME, port); + if (0 != CLOSE (sock)) + GNUNET_GE_LOG_STRERROR (coreAPI->ectx, + GNUNET_GE_ERROR | GNUNET_GE_USER | + GNUNET_GE_ADMIN | GNUNET_GE_BULK, + "close"); + return GNUNET_SYSERR; + } + selector = GNUNET_select_create ("udp", GNUNET_YES, coreAPI->ectx, load_monitor, sock, addrlen, 0, /* timeout */ + &select_message_handler, + NULL, + &select_accept_handler, + NULL, + &select_close_handler, + NULL, 64 * 1024, + 16 /* max sockets */ ); + if (selector == NULL) + return GNUNET_SYSERR; + } + sock = udp_create_socket (); + if (sock == -1) + { + GNUNET_GE_LOG_STRERROR (coreAPI->ectx, + GNUNET_GE_ERROR | GNUNET_GE_ADMIN | + GNUNET_GE_BULK, "socket"); + GNUNET_select_destroy (selector); + selector = NULL; + return GNUNET_SYSERR; + } + udp_sock = GNUNET_socket_create (coreAPI->ectx, load_monitor, sock); + GNUNET_GE_ASSERT (coreAPI->ectx, udp_sock != NULL); + return GNUNET_OK; +} + +/** + * The exported method. Makes the core api available via a global and + * returns the udp transport API. + */ +GNUNET_TransportAPI * +inittransport_udp (GNUNET_CoreAPIForTransport * core) +{ + unsigned long long mtu; + + cfg = core->cfg; + load_monitor = core->load_monitor; + GNUNET_GE_ASSERT (coreAPI->ectx, sizeof (UDPMessage) == 68); + GNUNET_GE_ASSERT (coreAPI->ectx, sizeof (HostAddress) == 24); + coreAPI = core; + if (-1 == GNUNET_GC_get_configuration_value_number (cfg, + "UDP", + "MTU", + sizeof (UDPMessage) + + + GNUNET_P2P_MESSAGE_OVERHEAD + + + sizeof + (GNUNET_MessageHeader) + + 32, 65500, + MESSAGE_SIZE, &mtu)) + { + return NULL; + } + if (mtu < 1200) + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_ERROR | GNUNET_GE_USER | GNUNET_GE_IMMEDIATE, + _("MTU %llu for `%s' is probably too low!\n"), mtu, "UDP"); + lock = GNUNET_mutex_create (GNUNET_NO); + if (0 != + GNUNET_GC_attach_change_listener (cfg, &reload_configuration, NULL)) + { + GNUNET_mutex_destroy (lock); + lock = NULL; + return NULL; + } + if (GNUNET_GC_get_configuration_value_yesno (cfg, "UDP", "UPNP", GNUNET_YES) + == GNUNET_YES) + { + upnp = coreAPI->service_request ("upnp"); + if (upnp == NULL) + GNUNET_GE_LOG (coreAPI->ectx, + GNUNET_GE_ERROR | GNUNET_GE_USER | GNUNET_GE_IMMEDIATE, + "The UPnP service could not be loaded. To disable UPnP, set the " + "configuration option \"UPNP\" in section \"%s\" to \"NO\"\n", + "UDP"); + } + stats = coreAPI->service_request ("stats"); + if (stats != NULL) + { + stat_bytesReceived + = stats->create (gettext_noop ("# bytes received via UDP")); + stat_bytesSent = stats->create (gettext_noop ("# bytes sent via UDP")); + stat_bytesDropped + = stats->create (gettext_noop ("# bytes dropped by UDP (outgoing)")); + stat_udpConnected + = stats->create (gettext_noop ("# UDP connections (right now)")); + } + myAPI.protocol_number = GNUNET_TRANSPORT_PROTOCOL_NUMBER_UDP; + myAPI.mtu = mtu - sizeof (UDPMessage); + myAPI.cost = 20000; + myAPI.hello_verify = &verify_hello; + myAPI.hello_create = &create_hello; + myAPI.connect = &udp_connect; + myAPI.send = &udp_send; + myAPI.associate = &udp_associate; + myAPI.disconnect = &udp_disconnect; + myAPI.server_start = &udp_transport_server_start; + myAPI.server_stop = &udp_transport_server_stop; + myAPI.hello_to_address = &hello_to_address; + myAPI.send_now_test = &udp_test_would_try; + + return &myAPI; +} + +void +donetransport_udp () +{ + do_shutdown (); +} + +/* end of udp.c */ diff --git a/src/transport/test_transport_api.c b/src/transport/test_transport_api.c new file mode 100644 index 000000000..02c28b09c --- /dev/null +++ b/src/transport/test_transport_api.c @@ -0,0 +1,305 @@ +/* + This file is part of GNUnet. + (C) 2009 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ +/** + * @file transport/test_transport_api.c + * @brief testcase for transport_api.c + */ +#include "platform.h" +#include "gnunet_common.h" +#include "gnunet_hello_lib.h" +#include "gnunet_getopt_lib.h" +#include "gnunet_os_lib.h" +#include "gnunet_program_lib.h" +#include "gnunet_scheduler_lib.h" +#include "gnunet_transport_service.h" +#include "transport.h" + +#define VERBOSE GNUNET_NO + +#define START_ARM GNUNET_YES + +/** + * How long until we give up on transmitting the message? + */ +#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30) + +#define MTYPE 12345 + +struct PeerContext +{ + struct GNUNET_CONFIGURATION_Handle *cfg; + struct GNUNET_TRANSPORT_Handle *th; + struct GNUNET_PeerIdentity id; +#if START_ARM + pid_t arm_pid; +#endif +}; + +static struct PeerContext p1; + +static struct PeerContext p2; + +static struct GNUNET_SCHEDULER_Handle *sched; + +static int ok; + +#if VERBOSE +#define OKPP do { ok++; fprintf (stderr, "Now at stage %u at %s:%u\n", ok, __FILE__, __LINE__); } while (0) +#else +#define OKPP do { ok++; } while (0) +#endif + + +static void +end () +{ + /* do work here */ + GNUNET_assert (ok == 8); + GNUNET_TRANSPORT_disconnect (p1.th); + GNUNET_TRANSPORT_disconnect (p2.th); + ok = 0; +} + + +/** + * Function called by the transport for each received message. + * + * @param cls closure + * @param latency estimated latency for communicating with the + * given peer + * @param peer (claimed) identity of the other peer + * @param message the message + */ +static void +notify_receive (void *cls, + struct GNUNET_TIME_Relative latency, + const struct GNUNET_PeerIdentity *peer, + const struct GNUNET_MessageHeader *message) +{ + GNUNET_assert (ok == 7); + OKPP; + GNUNET_assert (MTYPE == ntohs (message->type)); + GNUNET_assert (sizeof (struct GNUNET_MessageHeader) == + ntohs (message->size)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received message from peer (%p)!\n", + cls); + end (); +} + + +/** + * Function called to notify transport users that another + * peer connected to us. + * + * @param cls closure + * @param transport the transport service handle + * @param peer the peer that disconnected + * @param latency current latency of the connection + */ +static void +notify_connect (void *cls, + const struct GNUNET_PeerIdentity *peer, + struct GNUNET_TIME_Relative latency) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Peer connected to us (%p)!\n", cls); + GNUNET_assert ((ok >= 1) && (ok <= 6)); + OKPP; +} + + +/** + * Function called to notify transport users that another + * peer disconnected from us. + * + * @param cls closure + * @param transport the transport service handle + * @param peer the peer that disconnected + */ +static void +notify_disconnect (void *cls, const struct GNUNET_PeerIdentity *peer) +{ + GNUNET_assert (0); +} + + +static void +setup_peer (struct PeerContext *p, const char *cfgname) +{ + p->cfg = GNUNET_CONFIGURATION_create (); +#if START_ARM + p->arm_pid = GNUNET_OS_start_process ("gnunet-service-arm", + "gnunet-service-arm", +#if VERBOSE + "-L", "DEBUG", +#endif + "-c", cfgname, NULL); + sleep (1); /* allow ARM to start */ +#endif + GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_load (p->cfg, cfgname)); + p->th = GNUNET_TRANSPORT_connect (sched, p->cfg, + p, + ¬ify_receive, + ¬ify_connect, ¬ify_disconnect); + GNUNET_assert (p->th != NULL); +} + + +static size_t +notify_ready (void *cls, size_t size, void *buf) +{ + struct GNUNET_MessageHeader *hdr; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting message to peer (%p) - %u!\n", cls, size); + GNUNET_assert (size >= 256); + GNUNET_assert ((ok >= 5) && (ok <= 6)); + OKPP; + hdr = buf; + hdr->size = htons (sizeof (struct GNUNET_MessageHeader)); + hdr->type = htons (MTYPE); + return sizeof (struct GNUNET_MessageHeader); +} + + +static void +exchange_hello_last (void *cls, + struct GNUNET_TIME_Relative latency, + const struct GNUNET_PeerIdentity *peer, + const struct GNUNET_MessageHeader *message) +{ + struct PeerContext *me = cls; + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pk; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Exchanging HELLO with peer (%p)!\n", cls); + GNUNET_assert (ok >= 3); + OKPP; + GNUNET_assert (message != NULL); + GNUNET_assert (GNUNET_OK == + GNUNET_HELLO_get_key ((const struct GNUNET_HELLO_Message *) + message, &pk)); + GNUNET_CRYPTO_hash (&pk, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &me->id.hashPubKey); + GNUNET_TRANSPORT_offer_hello (p1.th, message); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Finished exchanging HELLOs, now waiting for transmission!\n"); + /* both HELLOs exchanged, get ready to test transmission! */ + GNUNET_TRANSPORT_notify_transmit_ready (p1.th, + &p2.id, + 256, TIMEOUT, ¬ify_ready, &p1); +} + + +static void +exchange_hello (void *cls, + struct GNUNET_TIME_Relative latency, + const struct GNUNET_PeerIdentity *peer, + const struct GNUNET_MessageHeader *message) +{ + struct PeerContext *me = cls; + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pk; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Exchanging HELLO with peer (%p)!\n", cls); + GNUNET_assert (ok >= 2); + OKPP; + GNUNET_assert (message != NULL); + GNUNET_assert (GNUNET_OK == + GNUNET_HELLO_get_key ((const struct GNUNET_HELLO_Message *) + message, &pk)); + GNUNET_CRYPTO_hash (&pk, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &me->id.hashPubKey); + GNUNET_TRANSPORT_get_hello (p2.th, GNUNET_TIME_UNIT_MINUTES, + &exchange_hello_last, &p2); +} + + +static void +run (void *cls, + struct GNUNET_SCHEDULER_Handle *s, + char *const *args, + const char *cfgfile, struct GNUNET_CONFIGURATION_Handle *cfg) +{ + GNUNET_assert (ok == 1); + OKPP; + sched = s; + setup_peer (&p1, "test_transport_api_peer1.conf"); + setup_peer (&p2, "test_transport_api_peer2.conf"); + GNUNET_TRANSPORT_get_hello (p1.th, + GNUNET_TIME_UNIT_MINUTES, &exchange_hello, &p1); +} + + +static void +stop_arm (struct PeerContext *p) +{ +#if START_ARM + if (0 != PLIBC_KILL (p->arm_pid, SIGTERM)) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill"); + waitpid (p->arm_pid, NULL, 0); +#endif + GNUNET_CONFIGURATION_destroy (p->cfg); +} + + +static int +check () +{ + char *const argv[] = { "test-transport-api", + "-c", + "test_transport_api_data.conf", +#if VERBOSE + "-L", "DEBUG", +#endif + NULL + }; + struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_END + }; + + ok = 1; + GNUNET_PROGRAM_run ((sizeof (argv) / sizeof (char *)) - 1, + argv, "test-transport-api", "nohelp", + options, &run, &ok); + stop_arm (&p1); + stop_arm (&p2); + return ok; +} + +int +main (int argc, char *argv[]) +{ + int ret; + + GNUNET_log_setup ("test-transport-api", +#if VERBOSE + "DEBUG", +#else + "WARNING", +#endif + NULL); + ret = check (); + + return ret; +} + +/* end of test_transport_api.c */ diff --git a/src/transport/test_transport_api_data.conf b/src/transport/test_transport_api_data.conf new file mode 100644 index 000000000..0fa611350 --- /dev/null +++ b/src/transport/test_transport_api_data.conf @@ -0,0 +1,24 @@ +[PATHS] +SERVICEHOME = /tmp/test-gnunetd-transport-master/ + +[resolver] +PORT = 2364 + +[transport] +PORT = 2365 +PLUGINS = tcp + +[arm] +PORT = 2366 + +[statistics] +PORT = 2367 + +[tcp] +PORT = 2368 + +[peerinfo] +PORT = 2369 + +[testing] +WEAKRANDOM = YES diff --git a/src/transport/test_transport_api_peer1.conf b/src/transport/test_transport_api_peer1.conf new file mode 100644 index 000000000..dcc0ab4cf --- /dev/null +++ b/src/transport/test_transport_api_peer1.conf @@ -0,0 +1,25 @@ +[PATHS] +SERVICEHOME = /tmp/test-gnunetd-transport-peer-1/ +DEFAULTCONFIG = test_transport_api_peer1.conf + +[resolver] +PORT = 12364 + +[transport] +PORT = 12365 +PLUGINS = tcp + +[arm] +PORT = 12366 + +[statistics] +PORT = 12367 + +[tcp] +PORT = 12368 + +[peerinfo] +PORT = 12369 + +[testing] +WEAKRANDOM = YES diff --git a/src/transport/test_transport_api_peer2.conf b/src/transport/test_transport_api_peer2.conf new file mode 100644 index 000000000..8567c6ac8 --- /dev/null +++ b/src/transport/test_transport_api_peer2.conf @@ -0,0 +1,25 @@ +[PATHS] +SERVICEHOME = /tmp/test-gnunetd-transport-peer-2/ +DEFAULTCONFIG = test_transport_api_peer2.conf + +[resolver] +PORT = 22364 + +[transport] +PORT = 22365 +PLUGINS = tcp + +[arm] +PORT = 22366 + +[statistics] +PORT = 22367 + +[tcp] +PORT = 22368 + +[peerinfo] +PORT = 22369 + +[testing] +WEAKRANDOM = YES diff --git a/src/transport/transport.h b/src/transport/transport.h new file mode 100644 index 000000000..8e1291005 --- /dev/null +++ b/src/transport/transport.h @@ -0,0 +1,238 @@ +/* + This file is part of GNUnet. + (C) 2009 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file transport/transport.h + * @brief common internal definitions for transport service + * @author Christian Grothoff + */ +#include "gnunet_crypto_lib.h" +#include "gnunet_time_lib.h" +#include "gnunet_transport_service.h" + +#define DEBUG_TRANSPORT GNUNET_NO + +/** + * For how long do we allow unused bandwidth + * from the past to carry over into the future? (in ms) + */ +#define MAX_BANDWIDTH_CARRY 5000 + +/** + * How often do we (at most) do a full quota + * recalculation? (in ms) + */ +#define MIN_QUOTA_REFRESH_TIME 2000 + +/** + * Message from the transport service to the library + * informing about neighbors. + */ +struct ConnectInfoMessage +{ + + /** + * Type will be GNUNET_MESSAGE_TYPE_TRANSPORT_CONNECT + */ + struct GNUNET_MessageHeader header; + + /** + * Current quota for outbound traffic in bytes/ms. + * (should be equal to system default) + */ + uint32_t quota_out GNUNET_PACKED; + + /** + * Latency estimate. + */ + struct GNUNET_TIME_RelativeNBO latency; + + /** + * Identity of the new neighbour. + */ + struct GNUNET_PeerIdentity id; + +}; + + +/** + * Message from the transport service to the library + * informing about disconnects. + */ +struct DisconnectInfoMessage +{ + + /** + * Type will be GNUNET_MESSAGE_TYPE_TRANSPORT_DISCONNECT + */ + struct GNUNET_MessageHeader header; + + /** + * Reserved, always zero. + */ + uint32_t reserved GNUNET_PACKED; + + /** + * Who got disconnected? + */ + struct GNUNET_PeerIdentity peer; + +}; + + +/** + * Message used to set a particular bandwidth quota. Send + * TO the service to set an incoming quota, send FROM the + * service to update an outgoing quota. + */ +struct QuotaSetMessage +{ + + /** + * Type will be GNUNET_MESSAGE_TYPE_TRANSPORT_NEIGHBOUR_INFO + */ + struct GNUNET_MessageHeader header; + + /** + * Quota in bytes per ms, 0 to drop everything; + * in network byte order. + */ + uint32_t quota_in GNUNET_PACKED; + + /** + * About which peer are we talking here? + */ + struct GNUNET_PeerIdentity peer; + +}; + + +/** + * Message used to ask the transport service to connect + * to a particular peer. + */ +struct TryConnectMessage +{ + + /** + * Type will be GNUNET_MESSAGE_TYPE_TRANSPORT_TRY_CONNECT. + */ + struct GNUNET_MessageHeader header; + + /** + * Always zero. + */ + uint32_t reserved GNUNET_PACKED; + + /** + * About which peer are we talking here? + */ + struct GNUNET_PeerIdentity peer; + +}; + +/** + * Message used to notify the transport API about a message + * received from the network. The actual message follows. + */ +struct InboundMessage +{ + + /** + * Type will be GNUNET_MESSAGE_TYPE_TRANSPORT_RECV + */ + struct GNUNET_MessageHeader header; + + /** + * Always zero. + */ + uint32_t reserved GNUNET_PACKED; + + /** + * Latency estimate. + */ + struct GNUNET_TIME_RelativeNBO latency; + + /** + * Which peer sent the message? + */ + struct GNUNET_PeerIdentity peer; + +}; + + +/** + * Message used to notify the transport API that it can + * send another message to the transport service. + */ +struct SendOkMessage +{ + + /** + * Type will be GNUNET_MESSAGE_TYPE_TRANSPORT_SEND_OK + */ + struct GNUNET_MessageHeader header; + + /** + * GNUNET_OK if the transmission succeeded, + * GNUNET_SYSERR if it failed (i.e. network disconnect); + * in either case, it is now OK for this client to + * send us another message for the given peer. + */ + uint32_t success GNUNET_PACKED; + + /** + * Which peer can send more now? + */ + struct GNUNET_PeerIdentity peer; + +}; + + +/** + * Message used to notify the transport service about a message + * to be transmitted to another peer. The actual message follows. + */ +struct OutboundMessage +{ + + /** + * Type will be GNUNET_MESSAGE_TYPE_TRANSPORT_SEND + */ + struct GNUNET_MessageHeader header; + + /** + * Always zero. + */ + uint32_t reserved GNUNET_PACKED; + + /** + * Which peer should receive the message? + */ + struct GNUNET_PeerIdentity peer; + +}; + + + + + + +/* end of transport.h */ diff --git a/src/transport/transport_api.c b/src/transport/transport_api.c new file mode 100644 index 000000000..d6d4e2a96 --- /dev/null +++ b/src/transport/transport_api.c @@ -0,0 +1,1863 @@ +/* + This file is part of GNUnet. + (C) 2009 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file transport/transport_api.c + * @brief library to access the low-level P2P IO service + * @author Christian Grothoff + * + * TODO: + * - set_quota with low bandwidth should cause peer + * disconnects (currently never does that) (MINOR) + */ +#include "platform.h" +#include "gnunet_client_lib.h" +#include "gnunet_arm_service.h" +#include "gnunet_hello_lib.h" +#include "gnunet_protocols.h" +#include "gnunet_server_lib.h" +#include "gnunet_time_lib.h" +#include "gnunet_transport_service.h" +#include "transport.h" + +/** + * After how long do we give up on transmitting a HELLO + * to the service? + */ +#define OFFER_HELLO_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30) + +/** + * How long should ARM wait when starting up the + * transport service before reporting back? + */ +#define START_SERVICE_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5) + +/** + * How long should ARM wait when stopping the + * transport service before reporting back? + */ +#define STOP_SERVICE_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5) + +/** + * Entry in linked list of all of our current neighbours. + */ +struct NeighbourList +{ + + /** + * This is a linked list. + */ + struct NeighbourList *next; + + /** + * Active transmit handle, can be NULL. Used to move + * from ready to wait list on disconnect and to block + * two transmissions to the same peer from being scheduled + * at the same time. + */ + struct GNUNET_TRANSPORT_TransmitHandle *transmit_handle; + + + /** + * Identity of this neighbour. + */ + struct GNUNET_PeerIdentity id; + + /** + * At what time did we reset last_sent last? + */ + struct GNUNET_TIME_Absolute last_quota_update; + + /** + * How many bytes have we sent since the "last_quota_update" + * timestamp? + */ + uint64_t last_sent; + + /** + * Global quota for outbound traffic to the neighbour in bytes/ms. + */ + uint32_t quota_out; + + /** + * Set to GNUNET_YES if we are currently allowed to + * transmit a message to the transport service for this + * peer, GNUNET_NO otherwise. + */ + int transmit_ok; + + /** + * Set to GNUNET_YES if we have received an ACK for the + * given peer. Peers that receive our HELLO always respond + * with an ACK to let us know that we are successfully + * communicating. Note that a PING can not be used for this + * since PINGs are only send if a HELLO address requires + * confirmation (and also, PINGs are not passed to the + * transport API itself). + */ + int received_ack; + +}; + + +/** + * Linked list of requests from clients for our HELLO + * that were deferred. + */ +struct HelloWaitList +{ + + /** + * This is a linked list. + */ + struct HelloWaitList *next; + + /** + * Reference back to our transport handle. + */ + struct GNUNET_TRANSPORT_Handle *handle; + + /** + * Callback to call once we got our HELLO. + */ + GNUNET_TRANSPORT_ReceiveCallback rec; + + /** + * Closure for rec. + */ + void *rec_cls; + + /** + * When to time out (call rec with NULL). + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * Timeout task (used to trigger timeout, + * cancel if we get the HELLO in time). + */ + GNUNET_SCHEDULER_TaskIdentifier task; + + +}; + + +/** + * Opaque handle for a transmission-ready request. + */ +struct GNUNET_TRANSPORT_TransmitHandle +{ + + /** + * We keep the transmit handles that are waiting for + * a transport-level connection in a doubly linked list. + */ + struct GNUNET_TRANSPORT_TransmitHandle *next; + + /** + * We keep the transmit handles that are waiting for + * a transport-level connection in a doubly linked list. + */ + struct GNUNET_TRANSPORT_TransmitHandle *prev; + + /** + * Handle of the main transport data structure. + */ + struct GNUNET_TRANSPORT_Handle *handle; + + /** + * Neighbour for this handle, can be NULL if the service + * is not yet connected to the target. + */ + struct NeighbourList *neighbour; + + /** + * Which peer is this transmission going to be for? All + * zeros if it is control-traffic to the service. + */ + struct GNUNET_PeerIdentity target; + + /** + * Function to call when notify_size bytes are available + * for transmission. + */ + GNUNET_NETWORK_TransmitReadyNotify notify; + + /** + * Closure for notify. + */ + void *notify_cls; + + /** + * transmit_ready task Id. The task is used to introduce + * the artificial delay that may be required to maintain + * the bandwidth limits. + */ + GNUNET_SCHEDULER_TaskIdentifier notify_delay_task; + + /** + * Timeout for this request. + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * How many bytes is our notify callback waiting for? + */ + size_t notify_size; + +}; + + +/** + * Handle for the transport service (includes all of the + * state for the transport service). + */ +struct GNUNET_TRANSPORT_Handle +{ + + /** + * Closure for the callbacks. + */ + void *cls; + + /** + * Function to call for received data. + */ + GNUNET_TRANSPORT_ReceiveCallback rec; + + /** + * function to call on connect events + */ + GNUNET_TRANSPORT_NotifyConnect nc_cb; + + /** + * function to call on disconnect events + */ + GNUNET_TRANSPORT_NotifyDisconnect nd_cb; + + /** + * The current HELLO message for this peer. Updated + * whenever transports change their addresses. + */ + struct GNUNET_HELLO_Message *my_hello; + + /** + * My client connection to the transport service. + */ + struct GNUNET_CLIENT_Connection *client; + + /** + * Handle to our registration with the client for notification. + */ + struct GNUNET_NETWORK_TransmitHandle *network_handle; + + /** + * Linked list of transmit handles that are waiting for the + * transport to connect to the respective peer. When we + * receive notification that the transport connected to a + * peer, we go over this list and check if someone has already + * requested a transmission to the new peer; if so, we trigger + * the next step. + */ + struct GNUNET_TRANSPORT_TransmitHandle *connect_wait_head; + + /** + * Linked list of transmit handles that are waiting for the + * transport to be ready for transmission to the respective + * peer. When we + * receive notification that the transport disconnected from + * a peer, we go over this list and move the entry back to + * the connect_wait list. + */ + struct GNUNET_TRANSPORT_TransmitHandle *connect_ready_head; + + /** + * Linked list of pending requests for our HELLO. + */ + struct HelloWaitList *hwl_head; + + /** + * My scheduler. + */ + struct GNUNET_SCHEDULER_Handle *sched; + + /** + * My configuration. + */ + struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Linked list of the current neighbours of this peer. + */ + struct NeighbourList *neighbours; + + /** + * ID of the task trying to reconnect to the + * service. + */ + GNUNET_SCHEDULER_TaskIdentifier reconnect_task; + + /** + * Delay until we try to reconnect. + */ + struct GNUNET_TIME_Relative reconnect_delay; + + /** + * Do we currently have a transmission pending? + * (schedule transmission was called but has not + * yet succeeded)? + */ + int transmission_scheduled; +}; + + +static struct NeighbourList * +find_neighbour (struct GNUNET_TRANSPORT_Handle *h, + const struct GNUNET_PeerIdentity *peer) +{ + struct NeighbourList *pos; + + pos = h->neighbours; + while ((pos != NULL) && + (0 != memcmp (peer, &pos->id, sizeof (struct GNUNET_PeerIdentity)))) + pos = pos->next; + return pos; +} + + +/** + * Schedule the task to send one message from the + * connect_ready list to the service. + */ +static void schedule_transmission (struct GNUNET_TRANSPORT_Handle *h); + + +/** + * Transmit message to client... + */ +static size_t +transport_notify_ready (void *cls, size_t size, void *buf) +{ + struct GNUNET_TRANSPORT_Handle *h = cls; + struct GNUNET_TRANSPORT_TransmitHandle *th; + struct NeighbourList *n; + size_t ret; + char *cbuf; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Ready to transmit %u bytes to transport service\n", size); +#endif + h->network_handle = NULL; + h->transmission_scheduled = GNUNET_NO; + if (buf == NULL) + { + th = h->connect_ready_head; + if (th->next != NULL) + th->next->prev = NULL; + h->connect_ready_head = th->next; + if (NULL != (n = th->neighbour)) + { + GNUNET_assert (n->transmit_handle == th); + n->transmit_handle = NULL; + } + GNUNET_assert (0 == th->notify (th->notify_cls, 0, NULL)); + GNUNET_free (th); + return 0; + } + cbuf = buf; + ret = 0; + h->network_handle = NULL; + h->transmission_scheduled = GNUNET_NO; + do + { + th = h->connect_ready_head; + GNUNET_assert (th->notify_size <= size); + if (th->next != NULL) + th->next->prev = NULL; + h->connect_ready_head = th->next; + if (NULL != (n = th->neighbour)) + { + GNUNET_assert (n->transmit_handle == th); + n->transmit_handle = NULL; + } + ret += th->notify (th->notify_cls, size, &cbuf[ret]); + GNUNET_free (th); + if (n != NULL) + n->last_sent += ret; + size -= ret; + } + while ((h->connect_ready_head != NULL) && + (h->connect_ready_head->notify_size <= size)); + if (h->connect_ready_head != NULL) + schedule_transmission (h); +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting %u bytes to transport service\n", ret); +#endif + return ret; +} + + +/** + * Schedule the task to send one message from the + * connect_ready list to the service. + */ +static void +schedule_transmission (struct GNUNET_TRANSPORT_Handle *h) +{ + struct GNUNET_TRANSPORT_TransmitHandle *th; + + GNUNET_assert (NULL == h->network_handle); + if (h->client == NULL) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Not yet connected to transport service, need to wait.\n"); +#endif + return; + } + th = h->connect_ready_head; + if (th == NULL) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Schedule transmission called, but no request is pending.\n"); +#endif + return; + } + h->transmission_scheduled = GNUNET_YES; +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Asking client API for transmission of %u bytes\n", + th->notify_size); +#endif + h->network_handle = GNUNET_CLIENT_notify_transmit_ready (h->client, + th->notify_size, + GNUNET_TIME_absolute_get_remaining + (th->timeout), + &transport_notify_ready, + h); + GNUNET_assert (NULL != h->network_handle); +} + + +/** + * Insert the given transmit handle in the given sorted + * doubly linked list based on timeout. + * + * @param head pointer to the head of the linked list + * @param th element to insert into the list + */ +static void +insert_transmit_handle (struct GNUNET_TRANSPORT_TransmitHandle **head, + struct GNUNET_TRANSPORT_TransmitHandle *th) +{ + struct GNUNET_TRANSPORT_TransmitHandle *pos; + struct GNUNET_TRANSPORT_TransmitHandle *prev; + + pos = *head; + prev = NULL; + while ((pos != NULL) && (pos->timeout.value < th->timeout.value)) + { + prev = pos; + pos = pos->next; + } + if (prev == NULL) + { + th->next = *head; + if (th->next != NULL) + th->next->prev = th; + *head = th; + } + else + { + th->next = pos; + th->prev = prev; + prev->next = th; + if (pos != NULL) + pos->prev = th; + } +} + + +/** + * Queue control request for transmission to the transport + * service. + * + * @param size number of bytes to be transmitted + * @param at_head request must be added to the head of the queue + * (otherwise request will be appended) + * @param timeout how long this transmission can wait (at most) + * @param notify function to call to get the content + * @param notify_cls closure for notify + */ +static void +schedule_control_transmit (struct GNUNET_TRANSPORT_Handle *h, + size_t size, + int at_head, + struct GNUNET_TIME_Relative timeout, + GNUNET_NETWORK_TransmitReadyNotify notify, + void *notify_cls) +{ + struct GNUNET_TRANSPORT_TransmitHandle *th; +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Queueing %u bytes control transmission request.\n", size); +#endif + th = GNUNET_malloc (sizeof (struct GNUNET_TRANSPORT_TransmitHandle)); + th->handle = h; + th->notify = notify; + th->notify_cls = notify_cls; + th->timeout = GNUNET_TIME_relative_to_absolute (timeout); + th->notify_size = size; + if (at_head) + { + th->next = h->connect_ready_head; + h->connect_ready_head = th; + if (th->next != NULL) + th->next->prev = th; + } + else + { + insert_transmit_handle (&h->connect_ready_head, th); + } + if (GNUNET_NO == h->transmission_scheduled) + schedule_transmission (h); +} + + +/** + * Update the quota values for the given neighbour now. + */ +static void +update_quota (struct NeighbourList *n) +{ + struct GNUNET_TIME_Relative delta; + uint64_t allowed; + uint64_t remaining; + + delta = GNUNET_TIME_absolute_get_duration (n->last_quota_update); + allowed = delta.value * n->quota_out; + if (n->last_sent < allowed) + { + remaining = allowed - n->last_sent; + if (n->quota_out > 0) + remaining /= n->quota_out; + else + remaining = 0; + if (remaining > MAX_BANDWIDTH_CARRY) + remaining = MAX_BANDWIDTH_CARRY; + n->last_sent = 0; + n->last_quota_update = GNUNET_TIME_absolute_get (); + n->last_quota_update.value -= remaining; + } + else + { + n->last_sent -= allowed; + n->last_quota_update = GNUNET_TIME_absolute_get (); + } +} + + +struct SetQuotaContext +{ + struct GNUNET_TRANSPORT_Handle *handle; + + struct GNUNET_PeerIdentity target; + + GNUNET_SCHEDULER_Task cont; + + void *cont_cls; + + struct GNUNET_TIME_Absolute timeout; + + uint32_t quota_in; +}; + + +static size_t +send_set_quota (void *cls, size_t size, void *buf) +{ + struct SetQuotaContext *sqc = cls; + struct QuotaSetMessage *msg; + + if (buf == NULL) + { + GNUNET_SCHEDULER_add_continuation (sqc->handle->sched, + GNUNET_NO, + sqc->cont, + sqc->cont_cls, + GNUNET_SCHEDULER_REASON_TIMEOUT); + GNUNET_free (sqc); + return 0; + } +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting `%s' request with respect to `%4s'.\n", + "SET_QUOTA", GNUNET_i2s (&sqc->target)); +#endif + GNUNET_assert (size >= sizeof (struct QuotaSetMessage)); + msg = buf; + msg->header.size = htons (sizeof (struct QuotaSetMessage)); + msg->header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_SET_QUOTA); + msg->quota_in = htonl (sqc->quota_in); + memcpy (&msg->peer, &sqc->target, sizeof (struct GNUNET_PeerIdentity)); + if (sqc->cont != NULL) + GNUNET_SCHEDULER_add_continuation (sqc->handle->sched, + GNUNET_NO, + sqc->cont, + sqc->cont_cls, + GNUNET_SCHEDULER_REASON_PREREQ_DONE); + GNUNET_free (sqc); + return sizeof (struct QuotaSetMessage); +} + + +/** + * Set the share of incoming bandwidth for the given + * peer to the specified amount. + * + * @param handle connection to transport service + * @param target who's bandwidth quota is being changed + * @param quota_in incoming bandwidth quota in bytes per ms; 0 can + * be used to force all traffic to be discarded + * @param quota_out outgoing bandwidth quota in bytes per ms; 0 can + * be used to force all traffic to be discarded + * @param timeout how long to wait until signaling failure if + * we can not communicate the quota change + * @param cont continuation to call when done, will be called + * either with reason "TIMEOUT" or with reason "PREREQ_DONE" + * @param cont_cls closure for continuation + */ +void +GNUNET_TRANSPORT_set_quota (struct GNUNET_TRANSPORT_Handle *handle, + const struct GNUNET_PeerIdentity *target, + uint32_t quota_in, + uint32_t quota_out, + struct GNUNET_TIME_Relative timeout, + GNUNET_SCHEDULER_Task cont, void *cont_cls) +{ + struct NeighbourList *n; + struct SetQuotaContext *sqc; + + n = find_neighbour (handle, target); + if (n != NULL) + { + update_quota (n); + if (n->quota_out < quota_out) + n->last_quota_update = GNUNET_TIME_absolute_get (); + n->quota_out = quota_out; + } + sqc = GNUNET_malloc (sizeof (struct SetQuotaContext)); + sqc->handle = handle; + sqc->target = *target; + sqc->cont = cont; + sqc->cont_cls = cont_cls; + sqc->timeout = GNUNET_TIME_relative_to_absolute (timeout); + sqc->quota_in = quota_in; + schedule_control_transmit (handle, + sizeof (struct QuotaSetMessage), + GNUNET_NO, timeout, &send_set_quota, sqc); +} + + +/** + * A "get_hello" request has timed out. Signal the client + * and clean up. + */ +static void +hello_wait_timeout (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct HelloWaitList *hwl = cls; + struct HelloWaitList *pos; + struct HelloWaitList *prev; + + prev = NULL; + pos = hwl->handle->hwl_head; + while (pos != hwl) + { + GNUNET_assert (pos != NULL); + prev = pos; + pos = pos->next; + } + if (prev == NULL) + hwl->handle->hwl_head = hwl->next; + else + prev->next = hwl->next; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + _("Timeout trying to obtain `%s' from transport service.\n"), + "HELLO"); + /* signal timeout */ + if (hwl->rec != NULL) + hwl->rec (hwl->rec_cls, GNUNET_TIME_UNIT_ZERO, NULL, NULL); + GNUNET_free (hwl); +} + + +/** + * Obtain the HELLO message for this peer. + * + * @param handle connection to transport service + * @param timeout how long to wait for the HELLO + * @param rec function to call with the HELLO, sender will be our peer + * identity; message and sender will be NULL on timeout + * (handshake with transport service pending/failed). + * cost estimate will be 0. + * @param rec_cls closure for rec + */ +void +GNUNET_TRANSPORT_get_hello (struct GNUNET_TRANSPORT_Handle *handle, + struct GNUNET_TIME_Relative timeout, + GNUNET_TRANSPORT_ReceiveCallback rec, + void *rec_cls) +{ + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pk; + struct GNUNET_PeerIdentity me; + struct HelloWaitList *hwl; + + if (handle->my_hello == NULL) + { + hwl = GNUNET_malloc (sizeof (struct HelloWaitList)); + hwl->next = handle->hwl_head; + handle->hwl_head = hwl; + hwl->handle = handle; + hwl->rec = rec; + hwl->rec_cls = rec_cls; + hwl->timeout = GNUNET_TIME_relative_to_absolute (timeout); + hwl->task = GNUNET_SCHEDULER_add_delayed (handle->sched, + GNUNET_YES, + GNUNET_SCHEDULER_PRIORITY_KEEP, + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, + timeout, + &hello_wait_timeout, hwl); + return; + } + GNUNET_assert (GNUNET_OK == GNUNET_HELLO_get_key (handle->my_hello, &pk)); + GNUNET_CRYPTO_hash (&pk, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &me.hashPubKey); + + rec (rec_cls, + GNUNET_TIME_UNIT_ZERO, + &me, (const struct GNUNET_MessageHeader *) handle->my_hello); +} + + +static size_t +send_hello (void *cls, size_t size, void *buf) +{ + struct GNUNET_MessageHeader *hello = cls; + uint16_t msize; + + if (buf == NULL) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Timeout while trying to transmit `%s' request.\n", + "HELLO"); +#endif + GNUNET_free (hello); + return 0; + } +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting `%s' request.\n", "HELLO"); +#endif + msize = ntohs (hello->size); + GNUNET_assert (size >= msize); + memcpy (buf, hello, msize); + GNUNET_free (hello); + return msize; +} + + +/** + * Offer the transport service the HELLO of another peer. Note that + * the transport service may just ignore this message if the HELLO is + * malformed or useless due to our local configuration. + * + * @param handle connection to transport service + * @param hello the hello message + */ +void +GNUNET_TRANSPORT_offer_hello (struct GNUNET_TRANSPORT_Handle *handle, + const struct GNUNET_MessageHeader *hello) +{ + struct GNUNET_MessageHeader *hc; + uint16_t size; + + if (handle->client == NULL) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Not connected to transport service, dropping offered HELLO\n"); +#endif + return; + } + GNUNET_break (ntohs (hello->type) == GNUNET_MESSAGE_TYPE_HELLO); + size = ntohs (hello->size); + GNUNET_break (size >= sizeof (struct GNUNET_MessageHeader)); + hc = GNUNET_malloc (size); + memcpy (hc, hello, size); + schedule_control_transmit (handle, + size, + GNUNET_NO, OFFER_HELLO_TIMEOUT, &send_hello, hc); +} + + +/** + * Function we use for handling incoming messages. + */ +static void demultiplexer (void *cls, const struct GNUNET_MessageHeader *msg); + + +static size_t +send_start (void *cls, size_t size, void *buf) +{ + struct GNUNET_MessageHeader *s = buf; + + if (buf == NULL) + return 0; +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting `%s' request.\n", "START"); +#endif + GNUNET_assert (size >= sizeof (struct GNUNET_MessageHeader)); + s->size = htons (sizeof (struct GNUNET_MessageHeader)); + s->type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_START); + return sizeof (struct GNUNET_MessageHeader); +} + + +/** + * Try again to connect to transport service. + */ +static void +reconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct GNUNET_TRANSPORT_Handle *h = cls; + struct GNUNET_TRANSPORT_TransmitHandle *pos; + struct NeighbourList *n; + + while (NULL != (n = h->neighbours)) + { + h->neighbours = n->next; + pos = n->transmit_handle; + if (pos != NULL) + { + pos->neighbour = NULL; + pos->next = h->connect_wait_head; + h->connect_wait_head = pos; + if (pos->next != NULL) + pos->next->prev = pos; + pos->prev = NULL; + } + GNUNET_free (n); + } + h->connect_ready_head = NULL; +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting to transport service.\n"); +#endif + GNUNET_assert (h->client == NULL); + h->reconnect_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; + h->client = GNUNET_CLIENT_connect (h->sched, "transport", h->cfg); + GNUNET_assert (h->client != NULL); + /* make sure we don't send "START" twice, + remove existing entry from queue (if present) */ + pos = h->connect_ready_head; + while (pos != NULL) + { + if (pos->notify == &send_start) + { + if (pos->prev == NULL) + h->connect_ready_head = pos->next; + else + pos->prev->next = pos->next; + if (pos->next != NULL) + pos->next->prev = pos->prev; + GNUNET_assert (pos->neighbour == NULL); + GNUNET_free (pos); + break; + } + pos = pos->next; + } + schedule_control_transmit (h, + sizeof (struct GNUNET_MessageHeader), + GNUNET_YES, + GNUNET_TIME_UNIT_FOREVER_REL, &send_start, NULL); + GNUNET_CLIENT_receive (h->client, + &demultiplexer, h, GNUNET_TIME_UNIT_FOREVER_REL); +} + + +/** + * Function that will schedule the job that will try + * to connect us again to the client. + */ +static void +schedule_reconnect (struct GNUNET_TRANSPORT_Handle *h) +{ +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Scheduling task to reconnect to transport service in %llu ms.\n", + h->reconnect_delay.value); +#endif + GNUNET_assert (h->client == NULL); + GNUNET_assert (h->reconnect_task == GNUNET_SCHEDULER_NO_PREREQUISITE_TASK); + h->reconnect_task + = GNUNET_SCHEDULER_add_delayed (h->sched, + GNUNET_NO, + GNUNET_SCHEDULER_PRIORITY_DEFAULT, + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, + h->reconnect_delay, &reconnect, h); + h->reconnect_delay = GNUNET_TIME_UNIT_SECONDS; +} + + +/** + * Remove the given transmit handle from the wait list. Does NOT free + * it. + */ +static void +remove_from_wait_list (struct GNUNET_TRANSPORT_TransmitHandle *th) +{ + if (th->prev == NULL) + th->handle->connect_wait_head = th->next; + else + th->prev->next = th->next; + if (th->next != NULL) + th->next->prev = th->prev; +} + + +/** + * We are connected to the respective peer, check the + * bandwidth limits and schedule the transmission. + */ +static void schedule_request (struct GNUNET_TRANSPORT_TransmitHandle *th); + + +/** + * Function called by the scheduler when the timeout + * for bandwidth availablility for the target + * neighbour is reached. + */ +static void +transmit_ready (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct GNUNET_TRANSPORT_TransmitHandle *th = cls; + + th->notify_delay_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; + schedule_request (th); +} + + +/** + * Called when our transmit request timed out before any transport + * reported success connecting to the desired peer or before the + * transport was ready to receive. Signal error and free + * TransmitHandle. + */ +static void +transmit_timeout (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct GNUNET_TRANSPORT_TransmitHandle *th = cls; + + if (th->neighbour != NULL) + th->neighbour->transmit_handle = NULL; +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Transmission request timed out.\n"); +#endif + th->notify_delay_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; + remove_from_wait_list (th); + th->notify (th->notify_cls, 0, NULL); + GNUNET_free (th); +} + + +/** + * We are connected to the respective peer, check the + * bandwidth limits and schedule the transmission. + */ +static void +schedule_request (struct GNUNET_TRANSPORT_TransmitHandle *th) +{ + struct GNUNET_TRANSPORT_Handle *h; + struct GNUNET_TIME_Relative duration; + struct NeighbourList *n; + uint64_t available; + + h = th->handle; + n = th->neighbour; + if (th->notify_delay_task != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) + { + GNUNET_SCHEDULER_cancel (h->sched, th->notify_delay_task); + th->notify_delay_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; + } + /* check outgoing quota */ + duration = GNUNET_TIME_absolute_get_duration (n->last_quota_update); + if (duration.value > MIN_QUOTA_REFRESH_TIME) + { + update_quota (n); + duration = GNUNET_TIME_absolute_get_duration (n->last_quota_update); + } + available = duration.value * n->quota_out; + if (available < n->last_sent + th->notify_size) + { + /* calculate how much bandwidth we'd still need to + accumulate and based on that how long we'll have + to wait... */ + available = n->last_sent + th->notify_size - available; + duration = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + available / n->quota_out); + if (th->timeout.value < + GNUNET_TIME_relative_to_absolute (duration).value) + { + /* signal timeout! */ +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Would need %llu ms before bandwidth is available for delivery, that is too long. Signaling timeout.\n", + duration.value); +#endif + remove_from_wait_list (th); + th->notify (th->notify_cls, 0, NULL); + GNUNET_free (th); + return; + } +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Need more bandwidth, delaying delivery by %llu ms\n", + duration.value); +#endif + th->notify_delay_task + = GNUNET_SCHEDULER_add_delayed (h->sched, + GNUNET_NO, + GNUNET_SCHEDULER_PRIORITY_KEEP, + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, + duration, &transmit_ready, th); + return; + } +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Bandwidth available for transmission to `%4s'\n", + GNUNET_i2s (&n->id)); +#endif + if (GNUNET_NO == n->transmit_ok) + { + /* we may be ready, but transport service is not; + wait for SendOkMessage or timeout */ +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Need to wait for transport service `%s' message\n", + "SEND_OK"); +#endif + th->notify_delay_task + = GNUNET_SCHEDULER_add_delayed (h->sched, + GNUNET_NO, + GNUNET_SCHEDULER_PRIORITY_KEEP, + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, + GNUNET_TIME_absolute_get_remaining + (th->timeout), &transmit_timeout, th); + return; + } + n->transmit_ok = GNUNET_NO; + remove_from_wait_list (th); +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Moving message to ready list\n"); +#endif + insert_transmit_handle (&h->connect_ready_head, th); + if (GNUNET_NO == h->transmission_scheduled) + schedule_transmission (h); +} + + +/** + * Add neighbour to our list + */ +static void +add_neighbour (struct GNUNET_TRANSPORT_Handle *h, + uint32_t quota_out, + struct GNUNET_TIME_Relative latency, + const struct GNUNET_PeerIdentity *pid) +{ + struct NeighbourList *n; + struct GNUNET_TRANSPORT_TransmitHandle *prev; + struct GNUNET_TRANSPORT_TransmitHandle *pos; + struct GNUNET_TRANSPORT_TransmitHandle *next; + + /* check for duplicates */ + if (NULL != find_neighbour (h, pid)) + { + GNUNET_break (0); + return; + } +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Creating entry for new neighbour `%4s'.\n", GNUNET_i2s (pid)); +#endif + n = GNUNET_malloc (sizeof (struct NeighbourList)); + n->id = *pid; + n->last_quota_update = GNUNET_TIME_absolute_get (); + n->quota_out = quota_out; + n->next = h->neighbours; + n->transmit_ok = GNUNET_YES; + h->neighbours = n; + if (h->nc_cb != NULL) + h->nc_cb (h->cls, &n->id, latency); + prev = NULL; + pos = h->connect_wait_head; + while (pos != NULL) + { + next = pos->next; +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Found entry in connect_wait_head for `%4s'.\n", + GNUNET_i2s (&pos->target)); +#endif + if (0 == memcmp (pid, + &pos->target, sizeof (struct GNUNET_PeerIdentity))) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Found pending request for new connection, will trigger now.\n"); +#endif + pos->neighbour = n; + if (pos->notify_delay_task != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) + { + GNUNET_SCHEDULER_cancel (h->sched, pos->notify_delay_task); + pos->notify_delay_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; + } + GNUNET_assert (NULL == n->transmit_handle); + n->transmit_handle = pos; + if (GNUNET_YES == n->received_ack) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "`%s' already received, scheduling request\n", + "ACK"); +#endif + schedule_request (pos); + } + else + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Still need to wait to receive `%s' message\n", + "ACK"); +#endif + pos->notify_delay_task + = GNUNET_SCHEDULER_add_delayed (h->sched, + GNUNET_NO, + GNUNET_SCHEDULER_PRIORITY_KEEP, + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, + GNUNET_TIME_absolute_get_remaining + (pos->timeout), + &transmit_timeout, pos); + } + if (prev == NULL) + h->connect_wait_head = next; + else + prev->next = next; + break; + } + prev = pos; + pos = next; + } +} + + +/** + * Connect to the transport service. Note that the connection may + * complete (or fail) asynchronously. + * + + * @param sched scheduler to use + * @param cfg configuration to use + * @param cls closure for the callbacks + * @param rec receive function to call + * @param nc function to call on connect events + * @param dc function to call on disconnect events + */ +struct GNUNET_TRANSPORT_Handle * +GNUNET_TRANSPORT_connect (struct GNUNET_SCHEDULER_Handle *sched, + struct GNUNET_CONFIGURATION_Handle *cfg, + void *cls, + GNUNET_TRANSPORT_ReceiveCallback rec, + GNUNET_TRANSPORT_NotifyConnect nc, + GNUNET_TRANSPORT_NotifyDisconnect nd) +{ + struct GNUNET_TRANSPORT_Handle *ret; + + GNUNET_ARM_start_service ("peerinfo", + cfg, sched, START_SERVICE_TIMEOUT, NULL, NULL); + GNUNET_ARM_start_service ("transport", + cfg, sched, START_SERVICE_TIMEOUT, NULL, NULL); + ret = GNUNET_malloc (sizeof (struct GNUNET_TRANSPORT_Handle)); + ret->sched = sched; + ret->cfg = cfg; + ret->cls = cls; + ret->rec = rec; + ret->nc_cb = nc; + ret->nd_cb = nd; + ret->reconnect_delay = GNUNET_TIME_UNIT_ZERO; + schedule_reconnect (ret); + return ret; +} + + +/** + * These stop activities must be run in a fresh + * scheduler that is NOT in shutdown mode. + */ +static void +stop_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct GNUNET_TRANSPORT_Handle *handle = cls; + GNUNET_ARM_stop_service ("transport", + handle->cfg, + tc->sched, STOP_SERVICE_TIMEOUT, NULL, NULL); + GNUNET_ARM_stop_service ("peerinfo", + handle->cfg, + tc->sched, STOP_SERVICE_TIMEOUT, NULL, NULL); +} + + +/** + * Disconnect from the transport service. + */ +void +GNUNET_TRANSPORT_disconnect (struct GNUNET_TRANSPORT_Handle *handle) +{ + struct GNUNET_TRANSPORT_TransmitHandle *th; + struct NeighbourList *n; + struct HelloWaitList *hwl; + struct GNUNET_CLIENT_Connection *client; + +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Transport disconnect called!\n"); +#endif + while (NULL != (th = handle->connect_ready_head)) + { + handle->connect_ready_head = th->next; + th->notify (th->notify_cls, 0, NULL); + GNUNET_free (th); + } + + while (NULL != (th = handle->connect_wait_head)) + { + handle->connect_wait_head = th->next; + if (th->notify_delay_task != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) + { + GNUNET_SCHEDULER_cancel (handle->sched, th->notify_delay_task); + th->notify_delay_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; + } + th->notify (th->notify_cls, 0, NULL); + GNUNET_free (th); + } + while (NULL != (n = handle->neighbours)) + { + handle->neighbours = n->next; + GNUNET_free (n); + } + while (NULL != (hwl = handle->hwl_head)) + { + handle->hwl_head = hwl->next; + GNUNET_SCHEDULER_cancel (handle->sched, hwl->task); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + _ + ("Disconnect while trying to obtain HELLO from transport service.\n")); + if (hwl->rec != NULL) + hwl->rec (hwl->rec_cls, GNUNET_TIME_UNIT_ZERO, NULL, NULL); + GNUNET_free (hwl); + } + if (handle->reconnect_task != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) + { + GNUNET_SCHEDULER_cancel (handle->sched, handle->reconnect_task); + handle->reconnect_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; + } + GNUNET_free_non_null (handle->my_hello); + handle->my_hello = NULL; + GNUNET_SCHEDULER_run (&stop_task, handle); + if (NULL != (client = handle->client)) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Disconnecting from transport service for good.\n"); +#endif + handle->client = NULL; + GNUNET_CLIENT_disconnect (client); + } + if (client == NULL) + GNUNET_free (handle); +} + + +/** + * We're ready to transmit the request that the transport service + * should connect to a new peer. In addition to sending the + * request, schedule the next phase for the transmission processing + * that caused the connect request in the first place. + */ +static size_t +request_connect (void *cls, size_t size, void *buf) +{ + struct GNUNET_TRANSPORT_TransmitHandle *th = cls; + struct TryConnectMessage *tcm; + struct GNUNET_TRANSPORT_Handle *h; + + h = th->handle; + if (buf == NULL) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Failed to transmit connect request to service.\n"); +#endif + th->notify (th->notify_cls, 0, NULL); + GNUNET_free (th); + return 0; + } +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting `%s' message for `%4s'.\n", + "TRY_CONNECT", GNUNET_i2s (&th->target)); +#endif + GNUNET_assert (size >= sizeof (struct TryConnectMessage)); + tcm = buf; + tcm->header.size = htons (sizeof (struct TryConnectMessage)); + tcm->header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_TRY_CONNECT); + tcm->reserved = htonl (0); + memcpy (&tcm->peer, &th->target, sizeof (struct GNUNET_PeerIdentity)); + th->notify_delay_task + = GNUNET_SCHEDULER_add_delayed (h->sched, + GNUNET_NO, + GNUNET_SCHEDULER_PRIORITY_KEEP, + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, + GNUNET_TIME_absolute_get_remaining (th-> + timeout), + &transmit_timeout, th); + insert_transmit_handle (&h->connect_wait_head, th); + return sizeof (struct TryConnectMessage); +} + + +/** + * Schedule a request to connect to the given + * neighbour (and if successful, add the specified + * handle to the wait list). + */ +static void +try_connect (struct GNUNET_TRANSPORT_TransmitHandle *th) +{ + schedule_control_transmit (th->handle, + sizeof (struct TryConnectMessage), + GNUNET_NO, + GNUNET_TIME_absolute_get_remaining (th->timeout), + &request_connect, th); +} + + +/** + * Cancel a pending notify transmit task + * and also remove the given transmit handle + * from whatever list is on. + */ +static void +remove_from_any_list (struct GNUNET_TRANSPORT_TransmitHandle *th) +{ + struct GNUNET_TRANSPORT_Handle *h; + + h = th->handle; + if (th->notify_delay_task != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) + { + GNUNET_SCHEDULER_cancel (h->sched, th->notify_delay_task); + th->notify_delay_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; + } + if (th->prev == NULL) + { + if (th == h->connect_wait_head) + h->connect_wait_head = th->next; + else + h->connect_ready_head = th->next; + } + else + th->prev->next = th->next; + if (th->next != NULL) + th->next->prev = th->prev; +} + + +/** + * Remove neighbour from our list + */ +static void +remove_neighbour (struct GNUNET_TRANSPORT_Handle *h, + const struct GNUNET_PeerIdentity *peer) +{ + struct NeighbourList *prev; + struct NeighbourList *pos; + struct GNUNET_TRANSPORT_TransmitHandle *th; + + prev = NULL; + pos = h->neighbours; + while ((pos != NULL) && + (0 != memcmp (peer, &pos->id, sizeof (struct GNUNET_PeerIdentity)))) + { + prev = pos; + pos = pos->next; + } + if (pos == NULL) + { + GNUNET_break (0); + return; + } + if (prev == NULL) + h->neighbours = pos->next; + else + prev->next = pos->next; + if (NULL != (th = pos->transmit_handle)) + { + pos->transmit_handle = NULL; + th->neighbour = NULL; + remove_from_any_list (th); + try_connect (th); + } + if (h->nc_cb != NULL) + h->nd_cb (h->cls, peer); + GNUNET_free (pos); +} + + +/** + * Type of a function to call when we receive a message + * from the service. + * + * @param cls closure + * @param msg message received, NULL on timeout or fatal error + */ +static void +demultiplexer (void *cls, const struct GNUNET_MessageHeader *msg) +{ + struct GNUNET_TRANSPORT_Handle *h = cls; + const struct DisconnectInfoMessage *dim; + const struct ConnectInfoMessage *cim; + const struct InboundMessage *im; + const struct GNUNET_MessageHeader *imm; + const struct SendOkMessage *okm; + struct HelloWaitList *hwl; + struct NeighbourList *n; + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pkey; + struct GNUNET_PeerIdentity me; + uint16_t size; + + if ((msg == NULL) || (h->client == NULL)) + { + if (h->client != NULL) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Error receiving from transport service, disconnecting temporarily.\n"); +#endif + if (h->network_handle != NULL) + { + GNUNET_NETWORK_notify_transmit_ready_cancel (h->network_handle); + h->network_handle = NULL; + h->transmission_scheduled = GNUNET_NO; + } + GNUNET_CLIENT_disconnect (h->client); + h->client = NULL; + schedule_reconnect (h); + } + else + { + /* shutdown initiated from 'GNUNET_TRANSPORT_disconnect', + finish clean up work! */ + GNUNET_free (h); + } + return; + } + GNUNET_CLIENT_receive (h->client, + &demultiplexer, h, GNUNET_TIME_UNIT_FOREVER_REL); + size = ntohs (msg->size); + switch (ntohs (msg->type)) + { + case GNUNET_MESSAGE_TYPE_HELLO: + if (GNUNET_OK != + GNUNET_HELLO_get_key ((const struct GNUNET_HELLO_Message *) msg, + &pkey)) + { + GNUNET_break (0); + break; + } + GNUNET_CRYPTO_hash (&pkey, + sizeof (struct + GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &me.hashPubKey); +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Receiving (my own) `%s' message, I am `%4s'.\n", + "HELLO", GNUNET_i2s (&me)); +#endif + GNUNET_free_non_null (h->my_hello); + h->my_hello = NULL; + if (size < sizeof (struct GNUNET_MessageHeader)) + { + GNUNET_break (0); + break; + } + h->my_hello = GNUNET_malloc (size); + memcpy (h->my_hello, msg, size); + while (NULL != (hwl = h->hwl_head)) + { + h->hwl_head = hwl->next; + GNUNET_SCHEDULER_cancel (h->sched, hwl->task); + GNUNET_TRANSPORT_get_hello (h, + GNUNET_TIME_UNIT_ZERO, + hwl->rec, hwl->rec_cls); + GNUNET_free (hwl); + } + break; + case GNUNET_MESSAGE_TYPE_TRANSPORT_CONNECT: + if (size != sizeof (struct ConnectInfoMessage)) + { + GNUNET_break (0); + break; + } + cim = (const struct ConnectInfoMessage *) msg; +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Receiving `%s' message for `%4s'.\n", + "CONNECT", GNUNET_i2s (&cim->id)); +#endif + add_neighbour (h, + ntohl (cim->quota_out), + GNUNET_TIME_relative_ntoh (cim->latency), &cim->id); + break; + case GNUNET_MESSAGE_TYPE_TRANSPORT_DISCONNECT: + if (size != sizeof (struct DisconnectInfoMessage)) + { + GNUNET_break (0); + break; + } + dim = (const struct DisconnectInfoMessage *) msg; +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Receiving `%s' message for `%4s'.\n", + "DISCONNECT", GNUNET_i2s (&dim->peer)); +#endif + remove_neighbour (h, &dim->peer); + break; + case GNUNET_MESSAGE_TYPE_TRANSPORT_SEND_OK: +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Receiving `%s' message.\n", "SEND_OK"); +#endif + if (size != sizeof (struct SendOkMessage)) + { + GNUNET_break (0); + break; + } + okm = (const struct SendOkMessage *) msg; + n = find_neighbour (h, &okm->peer); + GNUNET_assert (n != NULL); + n->transmit_ok = GNUNET_YES; + if (n->transmit_handle != NULL) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Processing pending message\n"); +#endif + GNUNET_SCHEDULER_cancel (h->sched, + n->transmit_handle->notify_delay_task); + n->transmit_handle->notify_delay_task = + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; + GNUNET_assert (GNUNET_YES == n->received_ack); + schedule_request (n->transmit_handle); + } + break; + case GNUNET_MESSAGE_TYPE_TRANSPORT_RECV: +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Receiving `%s' message.\n", "RECV"); +#endif + if (size < + sizeof (struct InboundMessage) + + sizeof (struct GNUNET_MessageHeader)) + { + GNUNET_break (0); + break; + } + im = (const struct InboundMessage *) msg; + imm = (const struct GNUNET_MessageHeader *) &im[1]; + if (ntohs (imm->size) + sizeof (struct InboundMessage) != size) + { + GNUNET_break (0); + break; + } + switch (ntohs (imm->type)) + { + case GNUNET_MESSAGE_TYPE_TRANSPORT_ACK: +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Receiving `%s' message from `%4s'.\n", + "ACK", GNUNET_i2s (&im->peer)); +#endif + n = find_neighbour (h, &im->peer); + if (n == NULL) + { + GNUNET_break (0); + break; + } + if (n->received_ack == GNUNET_NO) + { + n->received_ack = GNUNET_YES; + if (NULL != n->transmit_handle) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Peer connected, scheduling delayed message for deliverery now.\n"); +#endif + schedule_request (n->transmit_handle); + } + } + break; + default: +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received message of type %u from `%4s'.\n", + ntohs (imm->type), GNUNET_i2s (&im->peer)); +#endif + if (h->rec != NULL) + h->rec (h->cls, + GNUNET_TIME_relative_ntoh (im->latency), &im->peer, imm); + break; + } + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _ + ("Received unexpected message of type %u from `%4s' in %s:%u\n"), + ntohs (msg->type), GNUNET_i2s (&im->peer), __FILE__, + __LINE__); + GNUNET_break (0); + break; + } +} + + +struct ClientTransmitWrapper +{ + GNUNET_NETWORK_TransmitReadyNotify notify; + void *notify_cls; + struct GNUNET_TRANSPORT_TransmitHandle *th; +}; + + +/** + * Transmit message of a client destined for another + * peer to the service. + */ +static size_t +client_notify_wrapper (void *cls, size_t size, void *buf) +{ + struct ClientTransmitWrapper *ctw = cls; + struct OutboundMessage *obm; + struct GNUNET_MessageHeader *hdr; + size_t ret; + + if (size == 0) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmission request could not be satisfied.\n"); +#endif + ret = ctw->notify (ctw->notify_cls, 0, NULL); + GNUNET_assert (ret == 0); + GNUNET_free (ctw); + return 0; + } + GNUNET_assert (size >= sizeof (struct OutboundMessage)); + obm = buf; + ret = ctw->notify (ctw->notify_cls, + size - sizeof (struct OutboundMessage), + (void *) &obm[1]); + if (ret == 0) + { + /* Need to reset flag, no SEND means no SEND_OK! */ + ctw->th->neighbour->transmit_ok = GNUNET_YES; + GNUNET_free (ctw); + return 0; + } + GNUNET_assert (ret >= sizeof (struct GNUNET_MessageHeader)); + hdr = (struct GNUNET_MessageHeader *) &obm[1]; + GNUNET_assert (ntohs (hdr->size) == ret); + GNUNET_assert (ret + sizeof (struct OutboundMessage) < + GNUNET_SERVER_MAX_MESSAGE_SIZE); +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting `%s' message with data for `%4s'\n", + "SEND", GNUNET_i2s (&ctw->th->target)); +#endif + ret += sizeof (struct OutboundMessage); + obm->header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_SEND); + obm->header.size = htons (ret); + obm->reserved = htonl (0); + obm->peer = ctw->th->target; + GNUNET_free (ctw); + return ret; +} + + + +/** + * Check if we could queue a message of the given size for + * transmission. The transport service will take both its + * internal buffers and bandwidth limits imposed by the + * other peer into consideration when answering this query. + * + * @param handle connection to transport service + * @param target who should receive the message + * @param size how big is the message we want to transmit? + * @param timeout after how long should we give up (and call + * notify with buf NULL and size 0)? + * @param notify function to call when we are ready to + * send such a message + * @param notify_cls closure for notify + * @return NULL if someone else is already waiting to be notified + * non-NULL if the notify callback was queued (can be used to cancel + * using GNUNET_TRANSPORT_notify_transmit_ready_cancel) + */ +struct GNUNET_TRANSPORT_TransmitHandle * +GNUNET_TRANSPORT_notify_transmit_ready (struct GNUNET_TRANSPORT_Handle + *handle, + const struct GNUNET_PeerIdentity + *target, size_t size, + struct GNUNET_TIME_Relative timeout, + GNUNET_NETWORK_TransmitReadyNotify + notify, void *notify_cls) +{ + struct GNUNET_TRANSPORT_TransmitHandle *pos; + struct GNUNET_TRANSPORT_TransmitHandle *th; + struct NeighbourList *n; + struct ClientTransmitWrapper *ctw; + + if (size + sizeof (struct OutboundMessage) >= + GNUNET_SERVER_MAX_MESSAGE_SIZE) + return NULL; +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Asking transport service for transmission of %u bytes to peer `%4s'.\n", + size, GNUNET_i2s (target)); +#endif + n = find_neighbour (handle, target); + ctw = GNUNET_malloc (sizeof (struct ClientTransmitWrapper)); + th = GNUNET_malloc (sizeof (struct GNUNET_TRANSPORT_TransmitHandle)); + ctw->notify = notify; + ctw->notify_cls = notify_cls; + ctw->th = th; + th->handle = handle; + th->target = *target; + th->notify = &client_notify_wrapper; + th->notify_cls = ctw; + th->notify_size = size + sizeof (struct OutboundMessage); + th->timeout = GNUNET_TIME_relative_to_absolute (timeout); + th->neighbour = n; + if (NULL == n) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmission request could not be satisfied (not yet connected), adding it to pending request list.\n"); +#endif + pos = handle->connect_wait_head; + while (pos != NULL) + { + GNUNET_assert (0 != memcmp (target, + &pos->target, + sizeof (struct GNUNET_PeerIdentity))); + pos = pos->next; + } +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Will now try to connect to `%4s'.\n", GNUNET_i2s (target)); +#endif + try_connect (th); + } + else + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmission request queued for transmission to transport service.\n"); +#endif + GNUNET_assert (NULL == n->transmit_handle); + n->transmit_handle = th; + if (GNUNET_YES == n->received_ack) + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Peer `%4s' is connected, scheduling for delivery now.\n", + GNUNET_i2s (target)); +#endif + schedule_request (th); + } + else + { +#if DEBUG_TRANSPORT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Connection to `%4s' is not yet confirmed connected, scheduling timeout (%llums) only.\n", + GNUNET_i2s (target), timeout.value); +#endif + th->notify_delay_task + = GNUNET_SCHEDULER_add_delayed (handle->sched, + GNUNET_NO, + GNUNET_SCHEDULER_PRIORITY_KEEP, + GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, + timeout, &transmit_timeout, th); + } + } + return th; +} + + +/** + * Cancel the specified transmission-ready + * notification. + */ +void +GNUNET_TRANSPORT_notify_transmit_ready_cancel (struct + GNUNET_TRANSPORT_TransmitHandle + *th) +{ + struct GNUNET_TRANSPORT_Handle *h; + + GNUNET_assert (th->notify == &client_notify_wrapper); + remove_from_any_list (th); + h = th->handle; + if ((h->connect_ready_head == NULL) && (h->network_handle != NULL)) + { + GNUNET_NETWORK_notify_transmit_ready_cancel (h->network_handle); + h->network_handle = NULL; + h->transmission_scheduled = GNUNET_NO; + } + GNUNET_free (th->notify_cls); + GNUNET_free (th); +} + + +/* end of transport_api.c */ -- cgit v1.2.3