X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Futil%2Fclient.c;h=163ae6eb9f522c6e4fbbf35d5935cc13f7b03643;hb=01523e0c445460302a71077dfaa735d4c52e9a45;hp=55eb68d13ca2f58cdc2a584c1b7a810f56517b85;hpb=647428d5ed0708276983bca2bc91e430bf2b6a6e;p=oweals%2Fgnunet.git diff --git a/src/util/client.c b/src/util/client.c index 55eb68d13..163ae6eb9 100644 --- a/src/util/client.c +++ b/src/util/client.c @@ -1,10 +1,10 @@ /* This file is part of GNUnet. - (C) 2001, 2002, 2006, 2008, 2009 Christian Grothoff (and other contributing authors) + Copyright (C) 2001-2016 GNUnet e.V. 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 + by the Free Software Foundation; either version 3, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but @@ -14,8 +14,8 @@ 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. + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ /** @@ -26,233 +26,138 @@ * Generic TCP code for reliable, record-oriented TCP * connections between clients and service providers. */ - #include "platform.h" -#include "gnunet_common.h" -#include "gnunet_client_lib.h" #include "gnunet_protocols.h" -#include "gnunet_server_lib.h" -#include "gnunet_scheduler_lib.h" +#include "gnunet_util_lib.h" +#include "gnunet_resolver_service.h" +#include "gnunet_socks.h" -#define DEBUG_CLIENT GNUNET_NO -/** - * How often do we re-try tranmsitting requests before giving up? - * Note that if we succeeded transmitting a request but failed to read - * a response, we do NOT re-try. - */ -#define MAX_ATTEMPTS 50 +#define LOG(kind,...) GNUNET_log_from (kind, "util-client",__VA_ARGS__) /** - * Handle for a transmission request. + * Internal state for a client connected to a GNUnet service. */ -struct GNUNET_CLIENT_TransmitHandle -{ - /** - * Connection state. - */ - struct GNUNET_CLIENT_Connection *sock; - - /** - * Function to call to get the data for transmission. - */ - GNUNET_CONNECTION_TransmitReadyNotify notify; - - /** - * Closure for notify. - */ - void *notify_cls; - - /** - * Handle to the transmission with the underlying - * connection. - */ - struct GNUNET_CONNECTION_TransmitHandle *th; - - /** - * If we are re-trying and are delaying to do so, - * handle to the scheduled task managing the delay. - */ - GNUNET_SCHEDULER_TaskIdentifier reconnect_task; - - /** - * Timeout for the operation overall. - */ - struct GNUNET_TIME_Absolute timeout; - - /** - * Number of bytes requested. - */ - size_t size; - - /** - * Are we allowed to re-try to connect without telling - * the user (of this API) about the connection troubles? - */ - int auto_retry; - - /** - * Number of attempts left for transmitting the request. We may - * fail the first time (say because the service is not yet up), in - * which case (if auto_retry is set) we wait a bit and re-try - * (timeout permitting). - */ - unsigned int attempts_left; - -}; +struct ClientState; /** - * Context for processing - * "GNUNET_CLIENT_transmit_and_get_response" requests. + * During connect, we try multiple possible IP addresses + * to find out which one might work. */ -struct TransmitGetResponseContext +struct AddressProbe { - /** - * Client handle. - */ - struct GNUNET_CLIENT_Connection *sock; - - /** - * Message to transmit; do not free, allocated - * right after this struct. - */ - const struct GNUNET_MessageHeader *hdr; - - /** - * Timeout to use. - */ - struct GNUNET_TIME_Absolute timeout; /** - * Function to call when done. + * This is a linked list. */ - GNUNET_CLIENT_MessageHandler rn; + struct AddressProbe *next; /** - * Closure for "rn". + * This is a doubly-linked list. */ - void *rn_cls; -}; + struct AddressProbe *prev; -/** - * Context for handling the shutdown of a service. - */ -struct ShutdownContext -{ - /** - * Scheduler to be used to call continuation - */ - struct GNUNET_SCHEDULER_Handle *sched; /** - * Connection to the service that is being shutdown. + * The address; do not free (allocated at the end of this struct). */ - struct GNUNET_CLIENT_Connection *sock; + const struct sockaddr *addr; /** - * Time allowed for shutdown to happen. + * Underlying OS's socket. */ - struct GNUNET_TIME_Absolute timeout; + struct GNUNET_NETWORK_Handle *sock; /** - * Task set up to cancel the shutdown request on timeout. + * Connection for which we are probing. */ - GNUNET_SCHEDULER_TaskIdentifier cancel_task; + struct ClientState *cstate; /** - * Task to call once shutdown complete + * Lenth of addr. */ - GNUNET_CLIENT_ShutdownTask cont; + socklen_t addrlen; /** - * Closure for shutdown continuation + * Task waiting for the connection to finish connecting. */ - void *cont_cls; - - /** - * We received a confirmation that the service will shut down. - */ - int confirmed; - + struct GNUNET_SCHEDULER_Task *task; }; + /** - * Struct to refer to a GNUnet TCP connection. - * This is more than just a socket because if the server - * drops the connection, the client automatically tries - * to reconnect (and for that needs connection information). + * Internal state for a client connected to a GNUnet service. */ -struct GNUNET_CLIENT_Connection +struct ClientState { /** - * the socket handle, NULL if not live + * The connection handle, NULL if not live */ - struct GNUNET_CONNECTION_Handle *sock; + struct GNUNET_NETWORK_Handle *sock; /** - * Our scheduler. + * Handle to a pending DNS lookup request, NULL if DNS is finished. */ - struct GNUNET_SCHEDULER_Handle *sched; + struct GNUNET_RESOLVER_RequestHandle *dns_active; /** * Our configuration. */ - struct GNUNET_CONFIGURATION_Handle *cfg; + const struct GNUNET_CONFIGURATION_Handle *cfg; /** - * Name of the service we interact with. + * Linked list of sockets we are currently trying out + * (during connect). */ - char *service_name; + struct AddressProbe *ap_head; /** - * Context of a transmit_and_get_response operation, NULL - * if no such operation is pending. + * Linked list of sockets we are currently trying out + * (during connect). */ - struct TransmitGetResponseContext *tag; + struct AddressProbe *ap_tail; /** - * Handler for current receiver task. + * Name of the service we interact with. */ - GNUNET_CLIENT_MessageHandler receiver_handler; + char *service_name; /** - * Closure for receiver_handler. + * Hostname, if any. */ - void *receiver_handler_cls; + char *hostname; /** - * Handle for a pending transmission request, NULL if there is - * none pending. + * Next message to transmit to the service. NULL for none. */ - struct GNUNET_CLIENT_TransmitHandle *th; + const struct GNUNET_MessageHeader *msg; /** - * Handler for service test completion (NULL unless in service_test) + * Task for trying to connect to the service. */ - GNUNET_SCHEDULER_Task test_cb; + struct GNUNET_SCHEDULER_Task *retry_task; /** - * Deadline for calling 'test_cb'. + * Task for sending messages to the service. */ - struct GNUNET_TIME_Absolute test_deadline; + struct GNUNET_SCHEDULER_Task *send_task; /** - * If we are re-trying and are delaying to do so, - * handle to the scheduled task managing the delay. + * Task for sending messages to the service. */ - GNUNET_SCHEDULER_TaskIdentifier receive_task; + struct GNUNET_SCHEDULER_Task *recv_task; /** - * Closure for test_cb (NULL unless in service_test) + * Tokenizer for inbound messages. */ - void *test_cb_cls; + struct GNUNET_MessageStreamTokenizer *mst; /** - * Buffer for received message. + * Message queue under our control. */ - char *received_buf; + struct GNUNET_MQ_Handle *mq; /** * Timeout for receiving a response (absolute time). @@ -266,914 +171,717 @@ struct GNUNET_CLIENT_Connection struct GNUNET_TIME_Relative back_off; /** - * Number of bytes in received_buf that are valid. - */ - size_t received_pos; - - /** - * Size of received_buf. + * TCP port (0 for disabled). */ - unsigned int received_size; + unsigned long long port; /** - * Do we have a complete response in received_buf? + * Offset in the message where we are for transmission. */ - int msg_complete; + size_t msg_off; /** - * Are we currently busy doing receive-processing? - * GNUNET_YES if so, GNUNET_NO if not. + * How often have we tried to connect? */ - int in_receive; + unsigned int attempts; /** - * Are we ignoring shutdown signals? + * Are we supposed to die? #GNUNET_SYSERR if destruction must be + * deferred, #GNUNET_NO by default, #GNUNET_YES if destruction was + * deferred. */ - int ignore_shutdown; + int in_destroy; }; -static struct GNUNET_CONNECTION_Handle * -do_connect (struct GNUNET_SCHEDULER_Handle *sched, - const char *service_name, - const struct GNUNET_CONFIGURATION_Handle *cfg) -{ - struct GNUNET_CONNECTION_Handle *sock; - char *hostname; - unsigned long long port; - - if ((GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (cfg, - service_name, - "PORT", - &port)) || - (port > 65535) || - (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - service_name, - "HOSTNAME", &hostname))) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - _ - ("Could not determine valid hostname and port for service `%s' from configuration.\n"), - service_name); - return NULL; - } - if (0 == strlen (hostname)) - { - GNUNET_free (hostname); - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - _("Need a non-empty hostname for service `%s'.\n"), - service_name); - return NULL; - } - sock = GNUNET_CONNECTION_create_from_connect (sched, - cfg, - hostname, - port, - GNUNET_SERVER_MAX_MESSAGE_SIZE); - GNUNET_free (hostname); - return sock; -} - - /** - * Get a connection with a service. + * Try to connect to the service. * - * @param sched scheduler to use - * @param service_name name of the service - * @param cfg configuration to use - * @return NULL on error (service unknown to configuration) + * @param cls the `struct ClientState` to try to connect to the service */ -struct GNUNET_CLIENT_Connection * -GNUNET_CLIENT_connect (struct GNUNET_SCHEDULER_Handle *sched, - const char *service_name, - const struct GNUNET_CONFIGURATION_Handle *cfg) -{ - struct GNUNET_CLIENT_Connection *ret; - struct GNUNET_CONNECTION_Handle *sock; - - sock = do_connect (sched, service_name, cfg); - if (sock == NULL) - return NULL; - ret = GNUNET_malloc (sizeof (struct GNUNET_CLIENT_Connection)); - ret->sock = sock; - ret->sched = sched; - ret->service_name = GNUNET_strdup (service_name); - ret->cfg = GNUNET_CONFIGURATION_dup (cfg); - ret->back_off = GNUNET_TIME_UNIT_MILLISECONDS; - return ret; -} +static void +start_connect (void *cls); /** - * Configure this connection to ignore shutdown signals. + * We've failed for good to establish a connection (timeout or + * no more addresses to try). * - * @param h client handle - * @param do_ignore GNUNET_YES to ignore, GNUNET_NO to restore default + * @param cstate the connection we tried to establish */ -void -GNUNET_CLIENT_ignore_shutdown (struct GNUNET_CLIENT_Connection *h, - int do_ignore) +static void +connect_fail_continuation (struct ClientState *cstate) { - h->ignore_shutdown = do_ignore; - if (h->sock != NULL) - GNUNET_CONNECTION_ignore_shutdown (h->sock, - do_ignore); + GNUNET_break (NULL == cstate->ap_head); + GNUNET_break (NULL == cstate->ap_tail); + GNUNET_break (NULL == cstate->dns_active); + GNUNET_break (NULL == cstate->sock); + GNUNET_assert (NULL == cstate->send_task); + GNUNET_assert (NULL == cstate->recv_task); + // GNUNET_assert (NULL == cstate->proxy_handshake); + + cstate->back_off = GNUNET_TIME_STD_BACKOFF (cstate->back_off); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Failed to establish connection to `%s', no further addresses to try, will try again in %s.\n", + cstate->service_name, + GNUNET_STRINGS_relative_time_to_string (cstate->back_off, + GNUNET_YES)); + cstate->retry_task + = GNUNET_SCHEDULER_add_delayed (cstate->back_off, + &start_connect, + cstate); } /** - * Destroy connection with the service. This will automatically - * cancel any pending "receive" request (however, the handler will - * *NOT* be called, not even with a NULL message). Any pending - * transmission request will also be cancelled UNLESS the callback for - * the transmission request has already been called, in which case the - * transmission 'finish_pending_write' argument determines whether or - * not the write is guaranteed to complete before the socket is fully - * destroyed (unless, of course, there is an error with the server in - * which case the message may still be lost). + * We are ready to send a message to the service. * - * @param finish_pending_write should a transmission already passed to the - * handle be completed? - * @param sock handle to the service connection - */ -void -GNUNET_CLIENT_disconnect (struct GNUNET_CLIENT_Connection *sock, - int finish_pending_write) -{ - GNUNET_assert (sock->sock != NULL); - if (sock->in_receive == GNUNET_YES) - { - GNUNET_CONNECTION_receive_cancel (sock->sock); - sock->in_receive = GNUNET_NO; - } - GNUNET_CONNECTION_destroy (sock->sock, finish_pending_write); - sock->sock = NULL; - if (sock->tag != NULL) - { - GNUNET_free (sock->tag); - sock->tag = NULL; - } - sock->receiver_handler = NULL; - if (sock->th != NULL) - GNUNET_CLIENT_notify_transmit_ready_cancel (sock->th); - if (sock->receive_task != GNUNET_SCHEDULER_NO_TASK) - { - GNUNET_SCHEDULER_cancel (sock->sched, sock->receive_task); - sock->receive_task = GNUNET_SCHEDULER_NO_TASK; - } - GNUNET_array_grow (sock->received_buf, sock->received_size, 0); - GNUNET_free (sock->service_name); - GNUNET_CONFIGURATION_destroy (sock->cfg); - GNUNET_free (sock); -} - - -/** - * Check if message is complete + * @param cls the `struct ClientState` with the `msg` to transmit */ static void -check_complete (struct GNUNET_CLIENT_Connection *conn) +transmit_ready (void *cls) { - if ((conn->received_pos >= sizeof (struct GNUNET_MessageHeader)) && - (conn->received_pos >= - ntohs (((const struct GNUNET_MessageHeader *) conn->received_buf)-> - size))) - conn->msg_complete = GNUNET_YES; + struct ClientState *cstate = cls; + ssize_t ret; + size_t len; + const char *pos; + int notify_in_flight; + + cstate->send_task = NULL; + pos = (const char *) cstate->msg; + len = ntohs (cstate->msg->size); + GNUNET_assert (cstate->msg_off < len); + RETRY: + ret = GNUNET_NETWORK_socket_send (cstate->sock, + &pos[cstate->msg_off], + len - cstate->msg_off); + if (-1 == ret) + { + if (EINTR == errno) + goto RETRY; + GNUNET_MQ_inject_error (cstate->mq, + GNUNET_MQ_ERROR_WRITE); + return; + } + notify_in_flight = (0 == cstate->msg_off); + cstate->msg_off += ret; + if (cstate->msg_off < len) + { + cstate->send_task + = GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_FOREVER_REL, + cstate->sock, + &transmit_ready, + cstate); + if (notify_in_flight) + GNUNET_MQ_impl_send_in_flight (cstate->mq); + return; + } + cstate->msg = NULL; + GNUNET_MQ_impl_send_continue (cstate->mq); } /** - * Callback function for data received from the network. Note that - * both "available" and "errCode" would be 0 if the read simply timed out. + * We have received a full message, pass to the MQ dispatcher. + * Called by the tokenizer via #receive_ready(). * - * @param cls closure - * @param buf pointer to received data - * @param available number of bytes availabe in "buf", - * possibly 0 (on errors) - * @param addr address of the sender - * @param addrlen size of addr - * @param errCode value of errno (on errors receiving) + * @param cls the `struct ClientState` + * @param msg message we received. + * @return #GNUNET_OK on success, #GNUNET_SYSERR to stop further processing */ -static void -receive_helper (void *cls, - const void *buf, - size_t available, - const struct sockaddr *addr, socklen_t addrlen, int errCode) +static int +recv_message (void *cls, + const struct GNUNET_MessageHeader *msg) { - struct GNUNET_CLIENT_Connection *conn = cls; - struct GNUNET_TIME_Relative remaining; - GNUNET_CLIENT_MessageHandler receive_handler; - void *receive_handler_cls; - - GNUNET_assert (conn->msg_complete == GNUNET_NO); - conn->in_receive = GNUNET_NO; - if ((available == 0) || (conn->sock == NULL) || (errCode != 0)) - { - /* signal timeout! */ - if (NULL != (receive_handler = conn->receiver_handler)) - { - receive_handler_cls = conn->receiver_handler_cls; - conn->receiver_handler = NULL; - receive_handler (receive_handler_cls, NULL); - } - return; - } - - /* FIXME: optimize for common fast case where buf contains the - entire message and we need no copying... */ - + struct ClientState *cstate = cls; - /* slow path: append to array */ - if (conn->received_size < conn->received_pos + available) - GNUNET_array_grow (conn->received_buf, - conn->received_size, conn->received_pos + available); - memcpy (&conn->received_buf[conn->received_pos], buf, available); - conn->received_pos += available; - check_complete (conn); - /* check for timeout */ - remaining = GNUNET_TIME_absolute_get_remaining (conn->receive_timeout); - if (remaining.value == 0) - { - /* signal timeout! */ - conn->receiver_handler (conn->receiver_handler_cls, NULL); - return; - } - /* back to receive -- either for more data or to call callback! */ - GNUNET_CLIENT_receive (conn, - conn->receiver_handler, - conn->receiver_handler_cls, remaining); + if (GNUNET_YES == cstate->in_destroy) + return GNUNET_SYSERR; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received message of type %u and size %u from %s\n", + ntohs (msg->type), + ntohs (msg->size), + cstate->service_name); + GNUNET_MQ_inject_message (cstate->mq, + msg); + if (GNUNET_YES == cstate->in_destroy) + return GNUNET_SYSERR; + return GNUNET_OK; } /** - * Continuation to call the receive callback. + * Cancel all remaining connect attempts * - * @param cls our handle to the client connection - * @param tc scheduler context + * @param cstate handle of the client state to process */ static void -receive_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +cancel_aps (struct ClientState *cstate) { - struct GNUNET_CLIENT_Connection *sock = cls; - GNUNET_CLIENT_MessageHandler handler = sock->receiver_handler; - const struct GNUNET_MessageHeader *cmsg = - (const struct GNUNET_MessageHeader *) sock->received_buf; - void *handler_cls = sock->receiver_handler_cls; - uint16_t msize = ntohs (cmsg->size); - char mbuf[msize]; - struct GNUNET_MessageHeader *msg = (struct GNUNET_MessageHeader *) mbuf; - - sock->receive_task = GNUNET_SCHEDULER_NO_TASK; - GNUNET_assert (GNUNET_YES == sock->msg_complete); - GNUNET_assert (sock->received_pos >= msize); - memcpy (msg, cmsg, msize); - memmove (sock->received_buf, - &sock->received_buf[msize], sock->received_pos - msize); - sock->received_pos -= msize; - sock->msg_complete = GNUNET_NO; - sock->receiver_handler = NULL; - check_complete (sock); - if (handler != NULL) - handler (handler_cls, msg); + struct AddressProbe *pos; + + while (NULL != (pos = cstate->ap_head)) + { + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (pos->sock)); + GNUNET_SCHEDULER_cancel (pos->task); + GNUNET_CONTAINER_DLL_remove (cstate->ap_head, + cstate->ap_tail, + pos); + GNUNET_free (pos); + } } /** - * Read from the service. + * Implement the destruction of a message queue. Implementations must + * not free @a mq, but should take care of @a impl_state. * - * @param sock the service - * @param handler function to call with the message - * @param handler_cls closure for handler - * @param timeout how long to wait until timing out - */ -void -GNUNET_CLIENT_receive (struct GNUNET_CLIENT_Connection *sock, - GNUNET_CLIENT_MessageHandler handler, - void *handler_cls, struct GNUNET_TIME_Relative timeout) -{ - if (sock->sock == NULL) - { - /* already disconnected, fail instantly! */ - GNUNET_break (0); /* this should not happen in well-written code! */ - handler (handler_cls, NULL); - return; - } - sock->receiver_handler = handler; - sock->receiver_handler_cls = handler_cls; - sock->receive_timeout = GNUNET_TIME_relative_to_absolute (timeout); - if (GNUNET_YES == sock->msg_complete) - { - sock->receive_task = GNUNET_SCHEDULER_add_after (sock->sched, - GNUNET_SCHEDULER_NO_TASK, - &receive_task, sock); - } - else - { - sock->in_receive = GNUNET_YES; - GNUNET_CONNECTION_receive (sock->sock, - GNUNET_SERVER_MAX_MESSAGE_SIZE, - timeout, &receive_helper, sock); - } -} - - -/** - * Handler receiving response to service shutdown requests. - * First call with NULL: service misbehaving, or something. - * First call with GNUNET_MESSAGE_TYPE_SHUTDOWN_ACK: - * - service will shutdown - * First call with GNUNET_MESSAGE_TYPE_SHUTDOWN_REFUSE: - * - service will not be stopped! - * - * Second call with NULL: - * - service has now really shut down. - * - * @param cls closure - * @param msg NULL, indicating socket closure. + * @param mq the message queue to destroy + * @param impl_state our `struct ClientState` */ static void -service_shutdown_handler (void *cls, const struct GNUNET_MessageHeader *msg) +connection_client_destroy_impl (struct GNUNET_MQ_Handle *mq, + void *impl_state) { - struct ShutdownContext *shutdown_ctx = cls; - - if ((msg == NULL) && (shutdown_ctx->confirmed != GNUNET_YES)) - { - /* Means the other side closed the connection and never confirmed a shutdown */ - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Service handle shutdown before ACK!\n"); - if (shutdown_ctx->cont != NULL) - shutdown_ctx->cont(shutdown_ctx->cont_cls, GNUNET_SYSERR); - GNUNET_SCHEDULER_cancel(shutdown_ctx->sched, shutdown_ctx->cancel_task); - GNUNET_CLIENT_disconnect (shutdown_ctx->sock, GNUNET_NO); - GNUNET_free(shutdown_ctx); - } - else if ((msg == NULL) && (shutdown_ctx->confirmed == GNUNET_YES)) - { - GNUNET_log(GNUNET_ERROR_TYPE_WARNING, - "Service shutdown complete.\n"); - if (shutdown_ctx->cont != NULL) - shutdown_ctx->cont(shutdown_ctx->cont_cls, GNUNET_NO); - - GNUNET_SCHEDULER_cancel(shutdown_ctx->sched, shutdown_ctx->cancel_task); - GNUNET_CLIENT_disconnect (shutdown_ctx->sock, GNUNET_NO); - GNUNET_free(shutdown_ctx); - } - else - { - GNUNET_assert(ntohs(msg->size) == sizeof(struct GNUNET_MessageHeader)); - - switch (ntohs(msg->type)) - { - case GNUNET_MESSAGE_TYPE_SHUTDOWN_ACK: - GNUNET_log(GNUNET_ERROR_TYPE_WARNING, - "Received confirmation for service shutdown.\n"); - shutdown_ctx->confirmed = GNUNET_YES; - GNUNET_CLIENT_receive (shutdown_ctx->sock, - &service_shutdown_handler, - shutdown_ctx, - GNUNET_TIME_UNIT_FOREVER_REL); - break; - case GNUNET_MESSAGE_TYPE_SHUTDOWN_REFUSE: - default: /* Fall through */ - GNUNET_log(GNUNET_ERROR_TYPE_WARNING, - "Service shutdown refused!\n"); - if (shutdown_ctx->cont != NULL) - shutdown_ctx->cont(shutdown_ctx->cont_cls, GNUNET_YES); - - GNUNET_SCHEDULER_cancel(shutdown_ctx->sched, shutdown_ctx->cancel_task); - GNUNET_CLIENT_disconnect (shutdown_ctx->sock, GNUNET_NO); - GNUNET_free(shutdown_ctx); - break; - } - } -} - -/** - * Shutting down took too long, cancel receive and return error. - * - * @param cls closure - * @param tc context information (why was this task triggered now) - */ -void service_shutdown_cancel (void *cls, - const struct GNUNET_SCHEDULER_TaskContext * tc) -{ - struct ShutdownContext *shutdown_ctx = cls; - GNUNET_log(GNUNET_ERROR_TYPE_WARNING, "service_shutdown_cancel called!\n"); - shutdown_ctx->cont(shutdown_ctx->cont_cls, GNUNET_SYSERR); - GNUNET_CLIENT_disconnect (shutdown_ctx->sock, GNUNET_NO); - GNUNET_free(shutdown_ctx); + struct ClientState *cstate = impl_state; + + if (GNUNET_SYSERR == cstate->in_destroy) + { + /* defer destruction */ + cstate->in_destroy = GNUNET_YES; + cstate->mq = NULL; + return; + } + if (NULL != cstate->dns_active) + GNUNET_RESOLVER_request_cancel (cstate->dns_active); + if (NULL != cstate->send_task) + GNUNET_SCHEDULER_cancel (cstate->send_task); + if (NULL != cstate->recv_task) + GNUNET_SCHEDULER_cancel (cstate->recv_task); + if (NULL != cstate->retry_task) + GNUNET_SCHEDULER_cancel (cstate->retry_task); + if (NULL != cstate->sock) + GNUNET_NETWORK_socket_close (cstate->sock); + cancel_aps (cstate); + GNUNET_free (cstate->service_name); + GNUNET_free_non_null (cstate->hostname); + GNUNET_MST_destroy (cstate->mst); + GNUNET_free (cstate); } /** - * If possible, write a shutdown message to the target - * buffer and destroy the client connection. + * This function is called once we have data ready to read. * - * @param cls the "struct GNUNET_CLIENT_Connection" to destroy - * @param size number of bytes available in buf - * @param buf NULL on error, otherwise target buffer - * @return number of bytes written to buf + * @param cls `struct ClientState` with connection to read from */ -static size_t -write_shutdown (void *cls, size_t size, void *buf) +static void +receive_ready (void *cls) { - struct GNUNET_MessageHeader *msg; - struct ShutdownContext *shutdown_ctx = cls; - - if (size < sizeof (struct GNUNET_MessageHeader)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - _("Failed to transmit shutdown request to client.\n")); - shutdown_ctx->cont(shutdown_ctx->cont_cls, GNUNET_SYSERR); - GNUNET_CLIENT_disconnect (shutdown_ctx->sock, GNUNET_NO); - GNUNET_free(shutdown_ctx); - return 0; /* client disconnected */ - } - - GNUNET_CLIENT_receive (shutdown_ctx->sock, - &service_shutdown_handler, shutdown_ctx, - GNUNET_TIME_UNIT_FOREVER_REL); - shutdown_ctx->cancel_task = GNUNET_SCHEDULER_add_delayed (shutdown_ctx->sched, - GNUNET_TIME_absolute_get_remaining(shutdown_ctx->timeout), - &service_shutdown_cancel, - shutdown_ctx); - msg = (struct GNUNET_MessageHeader *) buf; - msg->type = htons (GNUNET_MESSAGE_TYPE_SHUTDOWN); - msg->size = htons (sizeof (struct GNUNET_MessageHeader)); - return sizeof (struct GNUNET_MessageHeader); + struct ClientState *cstate = cls; + int ret; + + cstate->recv_task = NULL; + cstate->in_destroy = GNUNET_SYSERR; + ret = GNUNET_MST_read (cstate->mst, + cstate->sock, + GNUNET_NO, + GNUNET_NO); + if (GNUNET_SYSERR == ret) + { + if (NULL != cstate->mq) + GNUNET_MQ_inject_error (cstate->mq, + GNUNET_MQ_ERROR_READ); + if (GNUNET_YES == cstate->in_destroy) + connection_client_destroy_impl (cstate->mq, + cstate); + return; + } + if (GNUNET_YES == cstate->in_destroy) + { + connection_client_destroy_impl (cstate->mq, + cstate); + return; + } + cstate->in_destroy = GNUNET_NO; + cstate->recv_task + = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + cstate->sock, + &receive_ready, + cstate); } /** - * Request that the service should shutdown. - * Afterwards, the connection will automatically be - * disconnected. Hence the "sock" should not - * be used by the caller after this call - * (calling this function frees "sock" after a while). - * - * @param sched the scheduler to use for calling shutdown continuation - * @param sock the socket connected to the service - * @param timeout how long to wait before giving up on transmission - * @param cont continuation to call once the service is really down - * @param cont_cls closure for continuation + * We've succeeded in establishing a connection. * - */ -void -GNUNET_CLIENT_service_shutdown (struct GNUNET_SCHEDULER_Handle *sched, - struct GNUNET_CLIENT_Connection *sock, - struct GNUNET_TIME_Relative timeout, - GNUNET_CLIENT_ShutdownTask cont, - void *cont_cls) -{ - struct ShutdownContext *shutdown_ctx; - shutdown_ctx = GNUNET_malloc(sizeof(struct ShutdownContext)); - shutdown_ctx->sched = sched; - shutdown_ctx->cont = cont; - shutdown_ctx->cont_cls = cont_cls; - shutdown_ctx->sock = sock; - shutdown_ctx->timeout = GNUNET_TIME_relative_to_absolute(timeout); - GNUNET_CONNECTION_notify_transmit_ready (sock->sock, - sizeof (struct - GNUNET_MessageHeader), - timeout, - &write_shutdown, shutdown_ctx); -} - - -/** - * Report service unavailable. + * @param cstate the connection we tried to establish */ static void -service_test_error (struct GNUNET_SCHEDULER_Handle *s, - GNUNET_SCHEDULER_Task task, void *task_cls) +connect_success_continuation (struct ClientState *cstate) { - GNUNET_SCHEDULER_add_continuation (s, - task, - task_cls, - GNUNET_SCHEDULER_REASON_TIMEOUT); + GNUNET_assert (NULL == cstate->recv_task); + cstate->recv_task + = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + cstate->sock, + &receive_ready, + cstate); + if (NULL != cstate->msg) + { + GNUNET_assert (NULL == cstate->send_task); + cstate->send_task + = GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_FOREVER_REL, + cstate->sock, + &transmit_ready, + cstate); + } } /** - * Receive confirmation from test, service is up. + * Try connecting to the server using UNIX domain sockets. * - * @param cls closure - * @param msg message received, NULL on timeout or fatal error + * @param service_name name of service to connect to + * @param cfg configuration to use + * @return NULL on error, socket connected to UNIX otherwise */ -static void -confirm_handler (void *cls, const struct GNUNET_MessageHeader *msg) +static struct GNUNET_NETWORK_Handle * +try_unixpath (const char *service_name, + const struct GNUNET_CONFIGURATION_Handle *cfg) { - struct GNUNET_CLIENT_Connection *conn = cls; - /* We may want to consider looking at the reply in more - detail in the future, for example, is this the - correct service? FIXME! */ - if (msg != NULL) +#if AF_UNIX + struct GNUNET_NETWORK_Handle *sock; + char *unixpath; + struct sockaddr_un s_un; + + unixpath = NULL; + if ((GNUNET_OK == + GNUNET_CONFIGURATION_get_value_filename (cfg, + service_name, + "UNIXPATH", + &unixpath)) && + (0 < strlen (unixpath))) + { + /* We have a non-NULL unixpath, need to validate it */ + if (strlen (unixpath) >= sizeof (s_un.sun_path)) { -#if DEBUG_CLIENT - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Received confirmation that service is running.\n"); -#endif - GNUNET_SCHEDULER_add_continuation (conn->sched, - conn->test_cb, - conn->test_cb_cls, - GNUNET_SCHEDULER_REASON_PREREQ_DONE); + LOG (GNUNET_ERROR_TYPE_WARNING, + _("UNIXPATH `%s' too long, maximum length is %llu\n"), + unixpath, + (unsigned long long) sizeof (s_un.sun_path)); + unixpath = GNUNET_NETWORK_shorten_unixpath (unixpath); + LOG (GNUNET_ERROR_TYPE_INFO, + _("Using `%s' instead\n"), + unixpath); + if (NULL == unixpath) + return NULL; } - else + memset (&s_un, + 0, + sizeof (s_un)); + s_un.sun_family = AF_UNIX; + strncpy (s_un.sun_path, + unixpath, + sizeof (s_un.sun_path) - 1); +#ifdef LINUX { - service_test_error (conn->sched, conn->test_cb, conn->test_cb_cls); - } - GNUNET_CLIENT_disconnect (conn, GNUNET_NO); -} + int abstract; - -static size_t -write_test (void *cls, size_t size, void *buf) -{ - struct GNUNET_CLIENT_Connection *conn = cls; - struct GNUNET_MessageHeader *msg; - - if (size < sizeof (struct GNUNET_MessageHeader)) - { -#if DEBUG_CLIENT - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - _("Failure to transmit TEST request.\n")); -#endif - service_test_error (conn->sched, conn->test_cb, conn->test_cb_cls); - GNUNET_CLIENT_disconnect (conn, GNUNET_NO); - return 0; /* client disconnected */ + abstract = GNUNET_CONFIGURATION_get_value_yesno (cfg, + "TESTING", + "USE_ABSTRACT_SOCKETS"); + if (GNUNET_YES == abstract) + s_un.sun_path[0] = '\0'; } -#if DEBUG_CLIENT - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Transmitting `%s' request.\n", "TEST"); #endif - msg = (struct GNUNET_MessageHeader *) buf; - msg->type = htons (GNUNET_MESSAGE_TYPE_TEST); - msg->size = htons (sizeof (struct GNUNET_MessageHeader)); - GNUNET_CLIENT_receive (conn, - &confirm_handler, - conn, - GNUNET_TIME_absolute_get_remaining (conn->test_deadline)); - return sizeof (struct GNUNET_MessageHeader); -} - - -/** - * Wait until the service is running. - * - * @param sched scheduler to use - * @param service name of the service to wait for - * @param cfg configuration to use - * @param timeout how long to wait at most in ms - * @param task task to run if service is running - * (reason will be "PREREQ_DONE" (service running) - * or "TIMEOUT" (service not known to be running)) - * @param task_cls closure for task - */ -void -GNUNET_CLIENT_service_test (struct GNUNET_SCHEDULER_Handle *sched, - const char *service, - const struct GNUNET_CONFIGURATION_Handle *cfg, - struct GNUNET_TIME_Relative timeout, - GNUNET_SCHEDULER_Task task, void *task_cls) -{ - struct GNUNET_CLIENT_Connection *conn; - -#if DEBUG_CLIENT - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Testing if service `%s' is running.\n", service); +#if HAVE_SOCKADDR_UN_SUN_LEN + s_un.sun_len = (u_char) sizeof (struct sockaddr_un); #endif - conn = GNUNET_CLIENT_connect (sched, service, cfg); - if (conn == NULL) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _ - ("Could not connect to service `%s', must not be running.\n"), - service); - service_test_error (sched, task, task_cls); - return; - } - conn->test_cb = task; - conn->test_cb_cls = task_cls; - conn->test_deadline = GNUNET_TIME_relative_to_absolute (timeout); - - if (NULL == GNUNET_CLIENT_notify_transmit_ready (conn, - sizeof (struct GNUNET_MessageHeader), - timeout, - GNUNET_YES, - &write_test, conn)) + sock = GNUNET_NETWORK_socket_create (AF_UNIX, + SOCK_STREAM, + 0); + if ( (NULL != sock) && + ( (GNUNET_OK == + GNUNET_NETWORK_socket_connect (sock, + (struct sockaddr *) &s_un, + sizeof (s_un))) || + (EINPROGRESS == errno) ) ) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - _("Failure to transmit request to service `%s'\n"), - service); - service_test_error (sched, task, task_cls); - GNUNET_CLIENT_disconnect (conn, GNUNET_NO); - return; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Successfully connected to unixpath `%s'!\n", + unixpath); + GNUNET_free (unixpath); + return sock; } + if (NULL != sock) + GNUNET_NETWORK_socket_close (sock); + } + GNUNET_free_non_null (unixpath); +#endif + return NULL; } /** - * Connection notifies us about failure or success of - * a transmission request. Either pass it on to our - * user or, if possible, retry. + * Scheduler let us know that we're either ready to write on the + * socket OR connect timed out. Do the right thing. * - * @param cls our "struct GNUNET_CLIENT_TransmissionHandle" - * @param size number of bytes available for transmission - * @param buf where to write them - * @return number of bytes written to buf + * @param cls the `struct AddressProbe *` with the address that we are probing */ -static size_t client_notify (void *cls, size_t size, void *buf); +static void +connect_probe_continuation (void *cls) +{ + struct AddressProbe *ap = cls; + struct ClientState *cstate = ap->cstate; + const struct GNUNET_SCHEDULER_TaskContext *tc; + int error; + socklen_t len; + + ap->task = NULL; + GNUNET_assert (NULL != ap->sock); + GNUNET_CONTAINER_DLL_remove (cstate->ap_head, + cstate->ap_tail, + ap); + len = sizeof (error); + error = 0; + tc = GNUNET_SCHEDULER_get_task_context (); + if ( (0 == (tc->reason & GNUNET_SCHEDULER_REASON_WRITE_READY)) || + (GNUNET_OK != + GNUNET_NETWORK_socket_getsockopt (ap->sock, + SOL_SOCKET, + SO_ERROR, + &error, + &len)) || + (0 != error) ) + { + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (ap->sock)); + GNUNET_free (ap); + if ( (NULL == cstate->ap_head) && + // (NULL == cstate->proxy_handshake) && + (NULL == cstate->dns_active) ) + connect_fail_continuation (cstate); + return; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Connection to `%s' succeeded!\n", + cstate->service_name); + /* trigger jobs that waited for the connection */ + GNUNET_assert (NULL == cstate->sock); + cstate->sock = ap->sock; + GNUNET_free (ap); + cancel_aps (cstate); + connect_success_continuation (cstate); +} /** - * This task is run if we should re-try connection to the - * service after a while. + * Try to establish a connection given the specified address. + * This function is called by the resolver once we have a DNS reply. * - * @param cls our "struct GNUNET_CLIENT_TransmitHandle" of the request - * @param tc unused + * @param cls our `struct ClientState *` + * @param addr address to try, NULL for "last call" + * @param addrlen length of @a addr */ static void -client_delayed_retry (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *tc) +try_connect_using_address (void *cls, + const struct sockaddr *addr, + socklen_t addrlen) { - struct GNUNET_CLIENT_TransmitHandle *th = cls; - - th->reconnect_task = GNUNET_SCHEDULER_NO_TASK; - if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) - { -#if DEBUG_CLIENT - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Transmission failed due to shutdown.\n"); -#endif - th->sock->th = NULL; - th->notify (th->notify_cls, 0, NULL); - GNUNET_free (th); - return; - } - th->th = GNUNET_CONNECTION_notify_transmit_ready (th->sock->sock, - th->size, - GNUNET_TIME_absolute_get_remaining - (th->timeout), - &client_notify, th); - if (th->th == NULL) - { - GNUNET_break (0); - th->notify (th->notify_cls, 0, NULL); - GNUNET_free (th); - return; - } + struct ClientState *cstate = cls; + struct AddressProbe *ap; + + if (NULL == addr) + { + cstate->dns_active = NULL; + if ( (NULL == cstate->ap_head) && + // (NULL == cstate->proxy_handshake) && + (NULL == cstate->sock) ) + connect_fail_continuation (cstate); + return; + } + if (NULL != cstate->sock) + return; /* already connected */ + /* try to connect */ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Trying to connect using address `%s:%u'\n", + GNUNET_a2s (addr, + addrlen), + cstate->port); + ap = GNUNET_malloc (sizeof (struct AddressProbe) + addrlen); + ap->addr = (const struct sockaddr *) &ap[1]; + GNUNET_memcpy (&ap[1], + addr, + addrlen); + ap->addrlen = addrlen; + ap->cstate = cstate; + + switch (ap->addr->sa_family) + { + case AF_INET: + ((struct sockaddr_in *) ap->addr)->sin_port = htons (cstate->port); + break; + case AF_INET6: + ((struct sockaddr_in6 *) ap->addr)->sin6_port = htons (cstate->port); + break; + default: + GNUNET_break (0); + GNUNET_free (ap); + return; /* not supported by us */ + } + ap->sock = GNUNET_NETWORK_socket_create (ap->addr->sa_family, + SOCK_STREAM, + 0); + if (NULL == ap->sock) + { + GNUNET_free (ap); + return; /* not supported by OS */ + } + if ( (GNUNET_OK != + GNUNET_NETWORK_socket_connect (ap->sock, + ap->addr, + ap->addrlen)) && + (EINPROGRESS != errno) ) + { + /* maybe refused / unsupported address, try next */ + GNUNET_log_strerror (GNUNET_ERROR_TYPE_INFO, + "connect"); + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (ap->sock)); + GNUNET_free (ap); + return; + } + GNUNET_CONTAINER_DLL_insert (cstate->ap_head, + cstate->ap_tail, + ap); + ap->task = GNUNET_SCHEDULER_add_write_net (GNUNET_CONNECTION_CONNECT_RETRY_TIMEOUT, + ap->sock, + &connect_probe_continuation, + ap); } /** - * Connection notifies us about failure or success of a transmission - * request. Either pass it on to our user or, if possible, retry. + * Test whether the configuration has proper values for connection + * (UNIXPATH || (PORT && HOSTNAME)). * - * @param cls our "struct GNUNET_CLIENT_TransmissionHandle" - * @param size number of bytes available for transmission - * @param buf where to write them - * @return number of bytes written to buf + * @param service_name name of service to connect to + * @param cfg configuration to use + * @return #GNUNET_OK if the configuration is valid, #GNUNET_SYSERR if not */ -static size_t -client_notify (void *cls, size_t size, void *buf) +static int +test_service_configuration (const char *service_name, + const struct GNUNET_CONFIGURATION_Handle *cfg) { - struct GNUNET_CLIENT_TransmitHandle *th = cls; - size_t ret; - struct GNUNET_TIME_Relative delay; - - th->th = NULL; - th->sock->th = NULL; - if (buf == NULL) - { - delay = GNUNET_TIME_absolute_get_remaining (th->timeout); - delay.value /= 2; - if ( (0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & GNUNET_SCHEDULER_get_reason (th->sock->sched))) || - (GNUNET_YES != th->auto_retry) || - (0 == --th->attempts_left) || - (delay.value < 1) ) - { -#if DEBUG_CLIENT - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Transmission failed %u times, giving up.\n", - MAX_ATTEMPTS - th->attempts_left); -#endif - GNUNET_break (0 == th->notify (th->notify_cls, 0, NULL)); - GNUNET_free (th); - return 0; - } - /* auto-retry */ - GNUNET_CONNECTION_destroy (th->sock->sock, GNUNET_NO); - th->sock->sock = do_connect (th->sock->sched, - th->sock->service_name, th->sock->cfg); - GNUNET_assert (NULL != th->sock->sock); - GNUNET_CONNECTION_ignore_shutdown (th->sock->sock, - th->sock->ignore_shutdown); - delay = GNUNET_TIME_relative_min (delay, th->sock->back_off); - th->sock->back_off - = GNUNET_TIME_relative_min (GNUNET_TIME_relative_multiply (th->sock->back_off, 2), - GNUNET_TIME_UNIT_SECONDS); -#if DEBUG_CLIENT - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Transmission failed %u times, trying again in %llums.\n", - MAX_ATTEMPTS - th->attempts_left, - (unsigned long long) delay.value); + int ret = GNUNET_SYSERR; + char *hostname = NULL; + unsigned long long port; +#if AF_UNIX + char *unixpath = NULL; + + if ((GNUNET_OK == + GNUNET_CONFIGURATION_get_value_filename (cfg, + service_name, + "UNIXPATH", + &unixpath)) && + (0 < strlen (unixpath))) + ret = GNUNET_OK; + GNUNET_free_non_null (unixpath); #endif - th->reconnect_task = GNUNET_SCHEDULER_add_delayed (th->sock->sched, - delay, - &client_delayed_retry, - th); - th->sock->th = th; - return 0; - } - GNUNET_assert (size >= th->size); - ret = th->notify (th->notify_cls, size, buf); - GNUNET_free (th); + + if ( (GNUNET_YES == + GNUNET_CONFIGURATION_have_value (cfg, + service_name, + "PORT")) && + (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_number (cfg, + service_name, + "PORT", + &port)) && + (port <= 65535) && + (0 != port) && + (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (cfg, + service_name, + "HOSTNAME", + &hostname)) && + (0 != strlen (hostname)) ) + ret = GNUNET_OK; + GNUNET_free_non_null (hostname); return ret; } /** - * Ask the client to call us once the specified number of bytes - * are free in the transmission buffer. May call the notify - * method immediately if enough space is available. + * Try to connect to the service. * - * @param sock connection to the service - * @param size number of bytes to send - * @param timeout after how long should we give up (and call - * notify with buf NULL and size 0)? - * @param auto_retry if the connection to the service dies, should we - * automatically re-connect and retry (within the timeout period) - * or should we immediately fail in this case? Pass GNUNET_YES - * if the caller does not care about temporary connection errors, - * for example because the protocol is stateless - * @param notify function to call - * @param notify_cls closure for notify - * @return NULL if our buffer will never hold size bytes, - * a handle if the notify callback was queued (can be used to cancel) + * @param cls the `struct ClientState` to try to connect to the service */ -struct GNUNET_CLIENT_TransmitHandle * -GNUNET_CLIENT_notify_transmit_ready (struct GNUNET_CLIENT_Connection *sock, - size_t size, - struct GNUNET_TIME_Relative timeout, - int auto_retry, - GNUNET_CONNECTION_TransmitReadyNotify - notify, void *notify_cls) +static void +start_connect (void *cls) { - struct GNUNET_CLIENT_TransmitHandle *th; + struct ClientState *cstate = cls; + + cstate->retry_task = NULL; +#if 0 + /* Never use a local source if a proxy is configured */ + if (GNUNET_YES == + GNUNET_SOCKS_check_service (cstate->service_name, + cstate->cfg)) + { + socks_connect (cstate); + return; + } +#endif - if (NULL != sock->th) - return NULL; - th = GNUNET_malloc (sizeof (struct GNUNET_CLIENT_TransmitHandle)); - th->sock = sock; - th->size = size; - th->timeout = GNUNET_TIME_relative_to_absolute (timeout); - th->auto_retry = auto_retry; - th->notify = notify; - th->notify_cls = notify_cls; - th->attempts_left = MAX_ATTEMPTS; - th->th = GNUNET_CONNECTION_notify_transmit_ready (sock->sock, - size, - timeout, - &client_notify, th); - if (NULL == th->th) + if ( (0 == (cstate->attempts++ % 2)) || + (0 == cstate->port) || + (NULL == cstate->hostname) ) + { + /* on even rounds, try UNIX first, or always + if we do not have a DNS name and TCP port. */ + cstate->sock = try_unixpath (cstate->service_name, + cstate->cfg); + if (NULL != cstate->sock) { - GNUNET_break (0); - GNUNET_free (th); - return NULL; + connect_success_continuation (cstate); + return; } - sock->th = th; - return th; + } + if ( (NULL == cstate->hostname) || + (0 == cstate->port) ) + { + /* All options failed. Boo! */ + connect_fail_continuation (cstate); + return; + } + cstate->dns_active + = GNUNET_RESOLVER_ip_get (cstate->hostname, + AF_UNSPEC, + GNUNET_CONNECTION_CONNECT_RETRY_TIMEOUT, + &try_connect_using_address, + cstate); } /** - * Cancel a request for notification. - * - * @param th handle from the original request. + * Implements the transmission functionality of a message queue. + * + * @param mq the message queue + * @param msg the message to send + * @param impl_state our `struct ClientState` */ -void -GNUNET_CLIENT_notify_transmit_ready_cancel (struct - GNUNET_CLIENT_TransmitHandle *th) +static void +connection_client_send_impl (struct GNUNET_MQ_Handle *mq, + const struct GNUNET_MessageHeader *msg, + void *impl_state) { - if (th->reconnect_task != GNUNET_SCHEDULER_NO_TASK) - { - GNUNET_break (NULL == th->th); - GNUNET_SCHEDULER_cancel (th->sock->sched, th->reconnect_task); - th->reconnect_task = GNUNET_SCHEDULER_NO_TASK; - } - else - { - GNUNET_break (NULL != th->th); - GNUNET_CONNECTION_notify_transmit_ready_cancel (th->th); - } - th->sock->th = NULL; - GNUNET_free (th); + struct ClientState *cstate = impl_state; + + /* only one message at a time allowed */ + GNUNET_assert (NULL == cstate->msg); + GNUNET_assert (NULL == cstate->send_task); + cstate->msg = msg; + cstate->msg_off = 0; + if (NULL == cstate->sock) + return; /* still waiting for connection */ + cstate->send_task + = GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_FOREVER_REL, + cstate->sock, + &transmit_ready, + cstate); } /** - * Function called to notify a client about the socket - * begin ready to queue the message. "buf" will be - * NULL and "size" zero if the socket was closed for - * writing in the meantime. + * Cancel the currently sent message. * - * @param cls closure of type "struct TransmitGetResponseContext*" - * @param size number of bytes available in buf - * @param buf where the callee should write the message - * @return number of bytes written to buf + * @param mq message queue + * @param impl_state our `struct ClientState` */ -static size_t -transmit_for_response (void *cls, size_t size, void *buf) +static void +connection_client_cancel_impl (struct GNUNET_MQ_Handle *mq, + void *impl_state) { - struct TransmitGetResponseContext *tc = cls; - uint16_t msize; - - tc->sock->tag = NULL; - msize = ntohs (tc->hdr->size); - if (NULL == buf) - { -#if DEBUG_CLIENT - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - _("Could not submit request, not expecting to receive a response.\n")); -#endif - tc->rn (tc->rn_cls, NULL); - GNUNET_free (tc); - return 0; - } - GNUNET_assert (size >= msize); - memcpy (buf, tc->hdr, msize); - GNUNET_CLIENT_receive (tc->sock, - tc->rn, - tc->rn_cls, - GNUNET_TIME_absolute_get_remaining (tc->timeout)); - GNUNET_free (tc); - return msize; + struct ClientState *cstate = impl_state; + + GNUNET_assert (NULL != cstate->msg); + GNUNET_assert (0 == cstate->msg_off); + cstate->msg = NULL; + if (NULL != cstate->send_task) + { + GNUNET_SCHEDULER_cancel (cstate->send_task); + cstate->send_task = NULL; + } } /** - * Convenience API that combines sending a request - * to the service and waiting for a response. - * If either operation times out, the callback - * will be called with a "NULL" response (in which - * case the connection should probably be destroyed). + * Create a message queue to connect to a GNUnet service. + * If handlers are specfied, receive messages from the connection. * - * @param sock connection to use - * @param hdr message to transmit - * @param timeout when to give up (for both transmission - * and for waiting for a response) - * @param auto_retry if the connection to the service dies, should we - * automatically re-connect and retry (within the timeout period) - * or should we immediately fail in this case? Pass GNUNET_YES - * if the caller does not care about temporary connection errors, - * for example because the protocol is stateless - * @param rn function to call with the response - * @param rn_cls closure for rn - * @return GNUNET_OK on success, GNUNET_SYSERR if a request - * is already pending + * @param cfg our configuration + * @param service_name name of the service to connect to + * @param handlers handlers for receiving messages, can be NULL + * @param error_handler error handler + * @param error_handler_cls closure for the @a error_handler + * @return the message queue, NULL on error */ -int -GNUNET_CLIENT_transmit_and_get_response (struct GNUNET_CLIENT_Connection - *sock, - const struct GNUNET_MessageHeader - *hdr, - struct GNUNET_TIME_Relative timeout, - int auto_retry, - GNUNET_CLIENT_MessageHandler rn, - void *rn_cls) +struct GNUNET_MQ_Handle * +GNUNET_CLIENT_connect (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *service_name, + const struct GNUNET_MQ_MessageHandler *handlers, + GNUNET_MQ_ErrorHandler error_handler, + void *error_handler_cls) { - struct TransmitGetResponseContext *tc; - uint16_t msize; + struct ClientState *cstate; - if (NULL != sock->th) - return GNUNET_SYSERR; - GNUNET_assert (sock->tag == NULL); - msize = ntohs (hdr->size); - tc = GNUNET_malloc (sizeof (struct TransmitGetResponseContext) + msize); - tc->sock = sock; - tc->hdr = (const struct GNUNET_MessageHeader *) &tc[1]; - memcpy (&tc[1], hdr, msize); - tc->timeout = GNUNET_TIME_relative_to_absolute (timeout); - tc->rn = rn; - tc->rn_cls = rn_cls; - if (NULL == GNUNET_CLIENT_notify_transmit_ready (sock, - msize, - timeout, - auto_retry, - &transmit_for_response, - tc)) + if (GNUNET_OK != + test_service_configuration (service_name, + cfg)) + return NULL; + cstate = GNUNET_new (struct ClientState); + cstate->service_name = GNUNET_strdup (service_name); + cstate->cfg = cfg; + cstate->retry_task = GNUNET_SCHEDULER_add_now (&start_connect, + cstate); + cstate->mst = GNUNET_MST_create (&recv_message, + cstate); + if (GNUNET_YES == + GNUNET_CONFIGURATION_have_value (cfg, + service_name, + "PORT")) + { + if (! ( (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + service_name, + "PORT", + &cstate->port)) || + (cstate->port > 65535) || + (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + service_name, + "HOSTNAME", + &cstate->hostname)) ) && + (0 == strlen (cstate->hostname)) ) { - GNUNET_break (0); - GNUNET_free (tc); - return GNUNET_SYSERR; + GNUNET_free (cstate->hostname); + cstate->hostname = NULL; + LOG (GNUNET_ERROR_TYPE_WARNING, + _("Need a non-empty hostname for service `%s'.\n"), + service_name); } - sock->tag = tc; - return GNUNET_OK; + } + cstate->mq = GNUNET_MQ_queue_for_callbacks (&connection_client_send_impl, + &connection_client_destroy_impl, + &connection_client_cancel_impl, + cstate, + handlers, + error_handler, + error_handler_cls); + return cstate->mq; } - - -/* end of client.c */ +/* end of client.c */