From d23a815951413af100c74b38cdd09a01ca1c280a Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 16 Mar 2017 05:33:01 +0100 Subject: removing dead/legacy server/connection logic, except for in tcp/wlan/bt plugins (which will be updated 'later') --- src/transport/Makefile.am | 4 +- src/transport/gnunet-helper-transport-wlan-dummy.c | 30 +- src/transport/plugin_transport_http_client.c | 21 +- src/transport/plugin_transport_http_server.c | 23 +- src/transport/plugin_transport_tcp.c | 528 +++++- src/transport/plugin_transport_udp.c | 39 +- src/transport/plugin_transport_udp.h | 12 +- src/transport/plugin_transport_udp_broadcasting.c | 30 +- src/transport/plugin_transport_wlan.c | 29 +- src/transport/tcp_connection_legacy.c | 1647 ++++++++++++++++++ src/transport/tcp_server_legacy.c | 1748 ++++++++++++++++++++ src/transport/tcp_server_mst_legacy.c | 311 ++++ src/transport/tcp_service_legacy.c | 1687 +++++++++++++++++++ src/transport/test_plugin_transport.c | 2 +- 14 files changed, 5979 insertions(+), 132 deletions(-) create mode 100644 src/transport/tcp_connection_legacy.c create mode 100644 src/transport/tcp_server_legacy.c create mode 100644 src/transport/tcp_server_mst_legacy.c create mode 100644 src/transport/tcp_service_legacy.c (limited to 'src/transport') diff --git a/src/transport/Makefile.am b/src/transport/Makefile.am index acc2557c6..7687f2348 100644 --- a/src/transport/Makefile.am +++ b/src/transport/Makefile.am @@ -191,6 +191,8 @@ libexec_PROGRAMS = \ $(BT_BIN) \ gnunet-service-transport + + bin_PROGRAMS = \ gnunet-transport \ gnunet-transport-certificate-creation @@ -561,7 +563,7 @@ TESTS = \ $(HTTP_API_TIMEOUT_TEST) \ $(HTTPS_API_TIMEOUT_TEST) \ $(WLAN_TIMEOUT_TEST) \ - $(BT_TIMEOUT_TEST) + $(BT_TIMEOUT_TEST) if HAVE_GETOPT_BINARY TESTS += \ test_transport_api_slow_ats diff --git a/src/transport/gnunet-helper-transport-wlan-dummy.c b/src/transport/gnunet-helper-transport-wlan-dummy.c index 684546314..63ed9c4b7 100644 --- a/src/transport/gnunet-helper-transport-wlan-dummy.c +++ b/src/transport/gnunet-helper-transport-wlan-dummy.c @@ -120,11 +120,11 @@ send_mac_to_plugin (char *buffer, struct GNUNET_TRANSPORT_WLAN_MacAddress *mac) * type to the output forward and copy it to the buffer for stdout. * * @param cls the 'struct SendBuffer' to copy the converted message to - * @param client unused * @param hdr inbound message from the FIFO */ static int -stdin_send (void *cls, void *client, const struct GNUNET_MessageHeader *hdr) +stdin_send (void *cls, + const struct GNUNET_MessageHeader *hdr) { struct SendBuffer *write_pout = cls; const struct GNUNET_TRANSPORT_WLAN_RadiotapSendMessage *in; @@ -166,11 +166,11 @@ stdin_send (void *cls, void *client, const struct GNUNET_MessageHeader *hdr) * We read a full message from stdin. Copy it to our send buffer. * * @param cls the 'struct SendBuffer' to copy to - * @param client unused * @param hdr the message we received to copy to the buffer */ static int -file_in_send (void *cls, void *client, const struct GNUNET_MessageHeader *hdr) +file_in_send (void *cls, + const struct GNUNET_MessageHeader *hdr) { struct SendBuffer *write_std = cls; uint16_t sendsize; @@ -213,8 +213,8 @@ main (int argc, char *argv[]) fd_set wfds; struct timeval tv; int retval; - struct GNUNET_SERVER_MessageStreamTokenizer *stdin_mst = NULL; - struct GNUNET_SERVER_MessageStreamTokenizer *file_in_mst = NULL; + struct GNUNET_MessageStreamTokenizer *stdin_mst = NULL; + struct GNUNET_MessageStreamTokenizer *file_in_mst = NULL; struct GNUNET_TRANSPORT_WLAN_MacAddress macaddr; int first; @@ -340,8 +340,8 @@ main (int argc, char *argv[]) write_std.pos = 0; write_pout.size = 0; write_pout.pos = 0; - stdin_mst = GNUNET_SERVER_mst_create (&stdin_send, &write_pout); - file_in_mst = GNUNET_SERVER_mst_create (&file_in_send, &write_std); + stdin_mst = GNUNET_MST_create (&stdin_send, &write_pout); + file_in_mst = GNUNET_MST_create (&file_in_send, &write_std); /* Send 'random' mac address */ macaddr.mac[0] = 0x13; @@ -453,8 +453,9 @@ main (int argc, char *argv[]) } else if (0 < readsize) { - GNUNET_SERVER_mst_receive (stdin_mst, NULL, readbuf, readsize, - GNUNET_NO, GNUNET_NO); + GNUNET_MST_from_buffer (stdin_mst, + readbuf, readsize, + GNUNET_NO, GNUNET_NO); } else @@ -475,8 +476,9 @@ main (int argc, char *argv[]) } else if (0 < readsize) { - GNUNET_SERVER_mst_receive (file_in_mst, NULL, readbuf, readsize, - GNUNET_NO, GNUNET_NO); + GNUNET_MST_from_buffer (file_in_mst, + readbuf, readsize, + GNUNET_NO, GNUNET_NO); } else { @@ -489,9 +491,9 @@ main (int argc, char *argv[]) end: /* clean up */ if (NULL != stdin_mst) - GNUNET_SERVER_mst_destroy (stdin_mst); + GNUNET_MST_destroy (stdin_mst); if (NULL != file_in_mst) - GNUNET_SERVER_mst_destroy (file_in_mst); + GNUNET_MST_destroy (file_in_mst); if (NULL != fpout) fclose (fpout); diff --git a/src/transport/plugin_transport_http_client.c b/src/transport/plugin_transport_http_client.c index ceed94af8..e91149289 100644 --- a/src/transport/plugin_transport_http_client.c +++ b/src/transport/plugin_transport_http_client.c @@ -221,7 +221,7 @@ struct GNUNET_ATS_Session /** * Message stream tokenizer for incoming data */ - struct GNUNET_SERVER_MessageStreamTokenizer *msg_tk; + struct GNUNET_MessageStreamTokenizer *msg_tk; /** * Session timeout task @@ -528,7 +528,7 @@ client_delete_session (struct GNUNET_ATS_Session *s) GNUNET_TRANSPORT_SS_DONE); if (NULL != s->msg_tk) { - GNUNET_SERVER_mst_destroy (s->msg_tk); + GNUNET_MST_destroy (s->msg_tk); s->msg_tk = NULL; } GNUNET_HELLO_address_free (s->address); @@ -1158,13 +1158,11 @@ client_wake_up (void *cls) * Callback for message stream tokenizer * * @param cls the session - * @param client not used * @param message the message received * @return always #GNUNET_OK */ static int client_receive_mst_cb (void *cls, - void *client, const struct GNUNET_MessageHeader *message) { struct GNUNET_ATS_Session *s = cls; @@ -1274,14 +1272,13 @@ client_receive (void *stream, return CURL_WRITEFUNC_PAUSE; } if (NULL == s->msg_tk) - s->msg_tk = GNUNET_SERVER_mst_create (&client_receive_mst_cb, - s); - GNUNET_SERVER_mst_receive (s->msg_tk, - s, - stream, - len, - GNUNET_NO, - GNUNET_NO); + s->msg_tk = GNUNET_MST_create (&client_receive_mst_cb, + s); + GNUNET_MST_from_buffer (s->msg_tk, + stream, + len, + GNUNET_NO, + GNUNET_NO); return len; } diff --git a/src/transport/plugin_transport_http_server.c b/src/transport/plugin_transport_http_server.c index 63c67b81c..2d6f40d58 100644 --- a/src/transport/plugin_transport_http_server.c +++ b/src/transport/plugin_transport_http_server.c @@ -201,7 +201,7 @@ struct GNUNET_ATS_Session /** * Message stream tokenizer for incoming data */ - struct GNUNET_SERVER_MessageStreamTokenizer *msg_tk; + struct GNUNET_MessageStreamTokenizer *msg_tk; /** * Client recv handle @@ -608,7 +608,7 @@ server_delete_session (struct GNUNET_ATS_Session *s) } if (NULL != s->msg_tk) { - GNUNET_SERVER_mst_destroy (s->msg_tk); + GNUNET_MST_destroy (s->msg_tk); s->msg_tk = NULL; } GNUNET_HELLO_address_free (s->address); @@ -1621,13 +1621,11 @@ server_send_callback (void *cls, * Callback called by MessageStreamTokenizer when a message has arrived * * @param cls current session as closure - * @param client client * @param message the message to be forwarded to transport service * @return #GNUNET_OK */ static int server_receive_mst_cb (void *cls, - void *client, const struct GNUNET_MessageHeader *message) { struct GNUNET_ATS_Session *s = cls; @@ -1847,13 +1845,16 @@ server_access_cb (void *cls, *upload_data_size); if (s->msg_tk == NULL) { - s->msg_tk = GNUNET_SERVER_mst_create (&server_receive_mst_cb, s); + s->msg_tk = GNUNET_MST_create (&server_receive_mst_cb, + s); } - GNUNET_SERVER_mst_receive (s->msg_tk, s, upload_data, *upload_data_size, - GNUNET_NO, GNUNET_NO); + GNUNET_MST_from_buffer (s->msg_tk, + upload_data, + *upload_data_size, + GNUNET_NO, GNUNET_NO); server_mhd_connection_timeout (plugin, s, - GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT.rel_value_us / 1000LL - / 1000LL); + GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT.rel_value_us / 1000LL + / 1000LL); (*upload_data_size) = 0; } else @@ -1935,7 +1936,7 @@ server_disconnect_cb (void *cls, sc->session->server_recv = NULL; if (NULL != sc->session->msg_tk) { - GNUNET_SERVER_mst_destroy (sc->session->msg_tk); + GNUNET_MST_destroy (sc->session->msg_tk); sc->session->msg_tk = NULL; } } @@ -2757,7 +2758,7 @@ server_start_report_addresses (struct HTTP_Server_Plugin *plugin) return; } - plugin->nat + plugin->nat = GNUNET_NAT_register (plugin->env->cfg, "transport-http_server", IPPROTO_TCP, diff --git a/src/transport/plugin_transport_tcp.c b/src/transport/plugin_transport_tcp.c index 34bbd00e0..10ea01cec 100644 --- a/src/transport/plugin_transport_tcp.c +++ b/src/transport/plugin_transport_tcp.c @@ -45,9 +45,494 @@ */ #define NAT_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10) -GNUNET_NETWORK_STRUCT_BEGIN +/** + * Opaque handle that can be used to cancel + * a transmit-ready notification. + */ +struct GNUNET_CONNECTION_TransmitHandle; + +/** + * @brief handle for a server + */ +struct GNUNET_SERVER_Handle; + +/** + * @brief opaque handle for a client of the server + */ +struct GNUNET_SERVER_Client; + +/** + * @brief opaque handle server returns for aborting transmission to a client. + */ +struct GNUNET_SERVER_TransmitHandle; + +/** + * @brief handle for a network connection + */ +struct GNUNET_CONNECTION_Handle; + + +/** + * Function called to notify a client about the connection begin ready + * to queue more data. @a buf will be NULL and @a size zero if the + * connection was closed for writing in the meantime. + * + * @param cls closure + * @param size number of bytes available in @a buf + * @param buf where the callee should write the message + * @return number of bytes written to @a buf + */ +typedef size_t +(*GNUNET_CONNECTION_TransmitReadyNotify) (void *cls, + size_t size, + void *buf); + +/** + * Credentials for UNIX domain sockets. + */ +struct GNUNET_CONNECTION_Credentials +{ + /** + * UID of the other end of the connection. + */ + uid_t uid; + + /** + * GID of the other end of the connection. + */ + gid_t gid; +}; + + +/** + * Functions with this signature are called whenever a client + * is disconnected on the network level. + * + * @param cls closure + * @param client identification of the client; NULL + * for the last call when the server is destroyed + */ +typedef void +(*GNUNET_SERVER_DisconnectCallback) (void *cls, + struct GNUNET_SERVER_Client *client); + + +/** + * Functions with this signature are called whenever a client + * is connected on the network level. + * + * @param cls closure + * @param client identification of the client + */ +typedef void +(*GNUNET_SERVER_ConnectCallback) (void *cls, + struct GNUNET_SERVER_Client *client); + + +/** + * Function to call for access control checks. + * + * @param cls closure + * @param ucred credentials, if available, otherwise NULL + * @param addr address + * @param addrlen length of address + * @return GNUNET_YES to allow, GNUNET_NO to deny, GNUNET_SYSERR + * for unknown address family (will be denied). + */ +typedef int +(*GNUNET_CONNECTION_AccessCheck) (void *cls, + const struct + GNUNET_CONNECTION_Credentials * + ucred, + const struct sockaddr * addr, + socklen_t addrlen); + +/** + * Callback function for data received from the network. Note that + * both "available" and "err" would be 0 if the read simply timed out. + * + * @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) + */ +typedef void +(*GNUNET_CONNECTION_Receiver) (void *cls, const void *buf, + size_t available, + const struct sockaddr * addr, + socklen_t addrlen, int errCode); + + + +/** + * Close the connection and free associated resources. There must + * not be any pending requests for reading or writing to the + * connection at this time. + * + * @param connection connection to destroy + */ +void +GNUNET_CONNECTION_destroy (struct GNUNET_CONNECTION_Handle *connection); + + +/** + * Signature of a function to create a custom tokenizer. + * + * @param cls closure from #GNUNET_SERVER_set_callbacks + * @param client handle to client the tokenzier will be used for + * @return handle to custom tokenizer ('mst') + */ +typedef void* +(*GNUNET_SERVER_MstCreateCallback) (void *cls, + struct GNUNET_SERVER_Client *client); + + +/** + * Signature of a function to destroy a custom tokenizer. + * + * @param cls closure from #GNUNET_SERVER_set_callbacks + * @param mst custom tokenizer handle + */ +typedef void +(*GNUNET_SERVER_MstDestroyCallback) (void *cls, + void *mst); + +/** + * Signature of a function to receive data for a custom tokenizer. + * + * @param cls closure from #GNUNET_SERVER_set_callbacks + * @param mst custom tokenizer handle + * @param client_identity ID of client for which this is a buffer, + * can be NULL (will be passed back to 'cb') + * @param buf input data to add + * @param size number of bytes in @a buf + * @param purge should any excess bytes in the buffer be discarded + * (i.e. for packet-based services like UDP) + * @param one_shot only call callback once, keep rest of message in buffer + * @return #GNUNET_OK if we are done processing (need more data) + * #GNUNET_NO if one_shot was set and we have another message ready + * #GNUNET_SYSERR if the data stream is corrupt + */ +typedef int +(*GNUNET_SERVER_MstReceiveCallback) (void *cls, void *mst, + struct GNUNET_SERVER_Client *client, + const char *buf, + size_t size, + int purge, + int one_shot); +/** + * Functions with this signature are called whenever a message is + * received. + * + * @param cls closure + * @param client identification of the client + * @param message the actual message + */ +typedef void +(*GNUNET_SERVER_MessageCallback) (void *cls, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message); + +/** + * Message handler. Each struct specifies how to handle on particular + * type of message received. + */ +struct GNUNET_SERVER_MessageHandler +{ + /** + * Function to call for messages of "type". + */ + GNUNET_SERVER_MessageCallback callback; + + /** + * Closure argument for @e callback. + */ + void *callback_cls; + + /** + * Type of the message this handler covers. + */ + uint16_t type; + + /** + * Expected size of messages of this type. Use 0 for + * variable-size. If non-zero, messages of the given + * type will be discarded (and the connection closed) + * if they do not have the right size. + */ + uint16_t expected_size; + +}; + +/** + * Ask the server to disconnect from the given client. This is the + * same as passing #GNUNET_SYSERR to #GNUNET_SERVER_receive_done, + * except that it allows dropping of a client even when not handling a + * message from that client. + * + * @param client the client to disconnect from + */ +void +GNUNET_SERVER_client_disconnect (struct GNUNET_SERVER_Client *client); + +/** + * Return user context associated with the given client. + * Note: you should probably use the macro (call without the underscore). + * + * @param client client to query + * @param size number of bytes in user context struct (for verification only) + * @return pointer to user context + */ +void * +GNUNET_SERVER_client_get_user_context_ (struct GNUNET_SERVER_Client *client, + size_t size); + + +/** + * Functions with this signature are called whenever a + * complete message is received by the tokenizer. + * + * Do not call #GNUNET_SERVER_mst_destroy from within + * the scope of this callback. + * + * @param cls closure + * @param client identification of the client + * @param message the actual message + * @return #GNUNET_OK on success, #GNUNET_SYSERR to stop further processing + */ +typedef int +(*GNUNET_SERVER_MessageTokenizerCallback) (void *cls, + void *client, + const struct GNUNET_MessageHeader *message); + + +/** + * Create a message stream tokenizer. + * + * @param cb function to call on completed messages + * @param cb_cls closure for @a cb + * @return handle to tokenizer + */ +struct GNUNET_SERVER_MessageStreamTokenizer * +GNUNET_SERVER_mst_create (GNUNET_SERVER_MessageTokenizerCallback cb, + void *cb_cls); + +/** + * Add incoming data to the receive buffer and call the + * callback for all complete messages. + * + * @param mst tokenizer to use + * @param client_identity ID of client for which this is a buffer, + * can be NULL (will be passed back to 'cb') + * @param buf input data to add + * @param size number of bytes in @a buf + * @param purge should any excess bytes in the buffer be discarded + * (i.e. for packet-based services like UDP) + * @param one_shot only call callback once, keep rest of message in buffer + * @return #GNUNET_OK if we are done processing (need more data) + * #GNUNET_NO if one_shot was set and we have another message ready + * #GNUNET_SYSERR if the data stream is corrupt + */ +int +GNUNET_SERVER_mst_receive (struct GNUNET_SERVER_MessageStreamTokenizer *mst, + void *client_identity, + const char *buf, size_t size, + int purge, int one_shot); + + + +/** + * Destroys a tokenizer. + * + * @param mst tokenizer to destroy + */ +void +GNUNET_SERVER_mst_destroy (struct GNUNET_SERVER_MessageStreamTokenizer *mst); + + +/** + * Set user context to be associated with the given client. + * Note: you should probably use the macro (call without the underscore). + * + * @param client client to query + * @param ptr pointer to user context + * @param size number of bytes in user context struct (for verification only) + */ +void +GNUNET_SERVER_client_set_user_context_ (struct GNUNET_SERVER_Client *client, + void *ptr, + size_t size); +/** + * Return user context associated with the given client. + * + * @param client client to query + * @param type expected return type (i.e. 'struct Foo') + * @return pointer to user context of type 'type *'. + */ +#define GNUNET_SERVER_client_get_user_context(client,type) \ + (type *) GNUNET_SERVER_client_get_user_context_ (client, sizeof (type)) + +/** + * Set user context to be associated with the given client. + * + * @param client client to query + * @param value pointer to user context + */ +#define GNUNET_SERVER_client_set_user_context(client,value) \ + GNUNET_SERVER_client_set_user_context_ (client, value, sizeof (*value)) + + + +/** + * Notify us when the server has enough space to transmit + * a message of the given size to the given client. + * + * @param client client to transmit message to + * @param size requested amount of buffer space + * @param timeout after how long should we give up (and call + * notify with buf NULL and size 0)? + * @param callback function to call when space is available + * @param callback_cls closure for @a callback + * @return non-NULL if the notify callback was queued; can be used + * to cancel the request using + * #GNUNET_SERVER_notify_transmit_ready_cancel. + * NULL if we are already going to notify someone else (busy) + */ +struct GNUNET_SERVER_TransmitHandle * +GNUNET_SERVER_notify_transmit_ready (struct GNUNET_SERVER_Client *client, + size_t size, + struct GNUNET_TIME_Relative timeout, + GNUNET_CONNECTION_TransmitReadyNotify callback, + void *callback_cls); + +/** + * Abort transmission request. + * + * @param th request to abort + */ +void +GNUNET_SERVER_notify_transmit_ready_cancel (struct GNUNET_SERVER_TransmitHandle *th); + + + + +/** + * Notify the server that the given client handle should + * be kept (keeps the connection up if possible, increments + * the internal reference counter). + * + * @param client the client to keep + */ +void +GNUNET_SERVER_client_keep (struct GNUNET_SERVER_Client *client); + + +/** + * Notify the server that the given client handle is no + * longer required. Decrements the reference counter. If + * that counter reaches zero an inactive connection maybe + * closed. + * + * @param client the client to drop + */ +void +GNUNET_SERVER_client_drop (struct GNUNET_SERVER_Client *client); + + +/** + * Function called by the service's run + * method to run service-specific setup code. + * + * @param cls closure + * @param server the initialized server + * @param cfg configuration to use + */ +typedef void +(*GNUNET_SERVICE_Main) (void *cls, + struct GNUNET_SERVER_Handle *server, + const struct GNUNET_CONFIGURATION_Handle *cfg); + + + +/** + * Suspend accepting connections from the listen socket temporarily. + * Resume activity using #GNUNET_SERVER_resume. + * + * @param server server to stop accepting connections. + */ +void +GNUNET_SERVER_suspend (struct GNUNET_SERVER_Handle *server); + +/** + * Notify us when the server has enough space to transmit + * a message of the given size to the given client. + * + * @param client client to transmit message to + * @param size requested amount of buffer space + * @param timeout after how long should we give up (and call + * notify with buf NULL and size 0)? + * @param callback function to call when space is available + * @param callback_cls closure for @a callback + * @return non-NULL if the notify callback was queued; can be used + * to cancel the request using + * #GNUNET_SERVER_notify_transmit_ready_cancel. + * NULL if we are already going to notify someone else (busy) + */ +struct GNUNET_SERVER_TransmitHandle * +GNUNET_SERVER_notify_transmit_ready (struct GNUNET_SERVER_Client *client, + size_t size, + struct GNUNET_TIME_Relative timeout, + GNUNET_CONNECTION_TransmitReadyNotify callback, + void *callback_cls); + + +/** + * Add a TCP socket-based connection to the set of handles managed by + * this server. Use this function for outgoing (P2P) connections that + * we initiated (and where this server should process incoming + * messages). + * + * @param server the server to use + * @param connection the connection to manage (client must + * stop using this connection from now on) + * @return the client handle + */ +struct GNUNET_SERVER_Client * +GNUNET_SERVER_connect_socket (struct GNUNET_SERVER_Handle *server, + struct GNUNET_CONNECTION_Handle *connection); + + +/** + * Resume accepting connections from the listen socket. + * + * @param server server to resume accepting connections. + */ +void +GNUNET_SERVER_resume (struct GNUNET_SERVER_Handle *server); + +/** + * Free resources held by this server. + * + * @param server server to destroy + */ +void +GNUNET_SERVER_destroy (struct GNUNET_SERVER_Handle *server); + + + + +#include "tcp_connection_legacy.c" +#include "tcp_server_mst_legacy.c" +#include "tcp_server_legacy.c" +#include "tcp_service_legacy.c" + +GNUNET_NETWORK_STRUCT_BEGIN + /** * Initial handshake message for a session. */ @@ -521,47 +1006,6 @@ struct Plugin }; -/* begin of ancient copy-and-pasted code that should be - specialized for TCP ...*/ -/** - * Add the given UNIX domain path as an address to the - * list (as the first entry). - * - * @param saddrs array to update - * @param saddrlens where to store the address length - * @param unixpath path to add - * @param abstract #GNUNET_YES to add an abstract UNIX domain socket. This - * parameter is ignore on systems other than LINUX - */ -static void -add_unixpath (struct sockaddr **saddrs, - socklen_t *saddrlens, - const char *unixpath, - int abstract) -{ -#ifdef AF_UNIX - struct sockaddr_un *un; - - un = GNUNET_new (struct sockaddr_un); - un->sun_family = AF_UNIX; - strncpy (un->sun_path, unixpath, sizeof (un->sun_path) - 1); -#ifdef LINUX - if (GNUNET_YES == abstract) - un->sun_path[0] = '\0'; -#endif -#if HAVE_SOCKADDR_UN_SUN_LEN - un->sun_len = (u_char) sizeof (struct sockaddr_un); -#endif - *saddrs = (struct sockaddr *) un; - *saddrlens = sizeof (struct sockaddr_un); -#else - /* this function should never be called - * unless AF_UNIX is defined! */ - GNUNET_assert (0); -#endif -} - - /** * Get the list of addresses that a server for the given service * should bind to. diff --git a/src/transport/plugin_transport_udp.c b/src/transport/plugin_transport_udp.c index eb48341b7..3a9013a5a 100644 --- a/src/transport/plugin_transport_udp.c +++ b/src/transport/plugin_transport_udp.c @@ -158,6 +158,11 @@ struct GNUNET_ATS_Session */ struct GNUNET_PeerIdentity target; + /** + * Tokenizer for inbound messages. + */ + struct GNUNET_MessageStreamTokenizer *mst; + /** * Plugin this session belongs to. */ @@ -626,6 +631,11 @@ free_session (struct GNUNET_ATS_Session *s) GNUNET_free (s->frag_ctx); s->frag_ctx = NULL; } + if (NULL != s->mst) + { + GNUNET_MST_destroy (s->mst); + s->mst = NULL; + } GNUNET_free (s); } @@ -2499,18 +2509,16 @@ read_process_ack (struct Plugin *plugin, * Message tokenizer has broken up an incomming message. Pass it on * to the service. * - * @param cls the `struct Plugin *` - * @param client the `struct GNUNET_ATS_Session *` + * @param cls the `struct GNUNET_ATS_Session *` * @param hdr the actual message * @return #GNUNET_OK (always) */ static int process_inbound_tokenized_messages (void *cls, - void *client, const struct GNUNET_MessageHeader *hdr) { - struct Plugin *plugin = cls; - struct GNUNET_ATS_Session *session = client; + struct GNUNET_ATS_Session *session = cls; + struct Plugin *plugin = session->plugin; if (GNUNET_YES == session->in_destroy) return GNUNET_OK; @@ -2626,6 +2634,8 @@ udp_plugin_create_session (void *cls, struct GNUNET_ATS_Session *s; s = GNUNET_new (struct GNUNET_ATS_Session); + s->mst = GNUNET_MST_create (&process_inbound_tokenized_messages, + s); s->plugin = plugin; s->address = GNUNET_HELLO_address_copy (address); s->target = address->peer; @@ -2792,12 +2802,11 @@ process_udp_message (struct Plugin *plugin, GNUNET_free (address); s->rc++; - GNUNET_SERVER_mst_receive (plugin->mst, - s, - (const char *) &msg[1], - ntohs (msg->header.size) - sizeof(struct UDPMessage), - GNUNET_YES, - GNUNET_NO); + GNUNET_MST_from_buffer (s->mst, + (const char *) &msg[1], + ntohs (msg->header.size) - sizeof(struct UDPMessage), + GNUNET_YES, + GNUNET_NO); s->rc--; if ( (0 == s->rc) && (GNUNET_YES == s->in_destroy) ) @@ -3990,8 +3999,6 @@ libgnunet_plugin_transport_udp_init (void *cls) p->sessions = GNUNET_CONTAINER_multipeermap_create (16, GNUNET_NO); p->defrag_ctxs = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN); - p->mst = GNUNET_SERVER_mst_create (&process_inbound_tokenized_messages, - p); GNUNET_BANDWIDTH_tracker_init (&p->tracker, NULL, NULL, @@ -4008,7 +4015,6 @@ libgnunet_plugin_transport_udp_init (void *cls) _("Failed to create UDP network sockets\n")); GNUNET_CONTAINER_multipeermap_destroy (p->sessions); GNUNET_CONTAINER_heap_destroy (p->defrag_ctxs); - GNUNET_SERVER_mst_destroy (p->mst); if (NULL != p->nat) GNUNET_NAT_unregister (p->nat); GNUNET_free (p); @@ -4120,11 +4126,6 @@ libgnunet_plugin_transport_udp_done (void *cls) GNUNET_CONTAINER_heap_destroy (plugin->defrag_ctxs); plugin->defrag_ctxs = NULL; } - if (NULL != plugin->mst) - { - GNUNET_SERVER_mst_destroy (plugin->mst); - plugin->mst = NULL; - } while (NULL != (udpw = plugin->ipv4_queue_head)) { dequeue (plugin, diff --git a/src/transport/plugin_transport_udp.h b/src/transport/plugin_transport_udp.h index 152b16099..48c7365c7 100644 --- a/src/transport/plugin_transport_udp.h +++ b/src/transport/plugin_transport_udp.h @@ -163,11 +163,6 @@ struct Plugin */ struct GNUNET_SCHEDULER_Task *select_task_v6; - /** - * Tokenizer for inbound messages. - */ - struct GNUNET_SERVER_MessageStreamTokenizer *mst; - /** * Bandwidth tracker to limit global UDP traffic. */ @@ -192,7 +187,7 @@ struct Plugin * Handle to NAT traversal support. */ struct GNUNET_NAT_STUN_Handle *stun; - + /** * The read socket for IPv4 */ @@ -203,11 +198,6 @@ struct Plugin */ struct GNUNET_NETWORK_Handle *sockv6; - /** - * Tokenizer for inbound messages. - */ - struct GNUNET_SERVER_MessageStreamTokenizer *broadcast_mst; - /** * Head of DLL of broadcast addresses */ diff --git a/src/transport/plugin_transport_udp_broadcasting.c b/src/transport/plugin_transport_udp_broadcasting.c index a440830fd..c6ddbce9b 100644 --- a/src/transport/plugin_transport_udp_broadcasting.c +++ b/src/transport/plugin_transport_udp_broadcasting.c @@ -133,11 +133,10 @@ struct MstContext */ static int broadcast_mst_cb (void *cls, - void *client, const struct GNUNET_MessageHeader *message) { - struct Plugin *plugin = cls; - struct MstContext *mc = client; + struct MstContext *mc = cls; + struct Plugin *plugin = mc->plugin; struct GNUNET_HELLO_Address *address; const struct GNUNET_MessageHeader *hello; const struct UDP_Beacon_Message *msg; @@ -191,16 +190,20 @@ udp_broadcast_receive (struct Plugin *plugin, size_t udp_addr_len, enum GNUNET_ATS_Network_Type network_type) { + struct GNUNET_MessageStreamTokenizer *broadcast_mst; struct MstContext mc; + broadcast_mst = GNUNET_MST_create (&broadcast_mst_cb, + &mc); + mc.plugin = plugin; mc.udp_addr = udp_addr; mc.udp_addr_len = udp_addr_len; mc.ats_address_network_type = network_type; - GNUNET_SERVER_mst_receive (plugin->broadcast_mst, - &mc, - buf, size, - GNUNET_NO, - GNUNET_NO); + GNUNET_MST_from_buffer (broadcast_mst, + buf, size, + GNUNET_NO, + GNUNET_NO); + GNUNET_MST_destroy (broadcast_mst); } @@ -546,10 +549,6 @@ setup_broadcast (struct Plugin *plugin, return; } - /* always create tokenizers */ - plugin->broadcast_mst = - GNUNET_SERVER_mst_create (&broadcast_mst_cb, plugin); - if (GNUNET_YES != plugin->enable_broadcasting) return; /* We do not send, just receive */ @@ -636,13 +635,6 @@ stop_broadcast (struct Plugin *plugin) GNUNET_free (p); } } - - /* Destroy MSTs */ - if (NULL != plugin->broadcast_mst) - { - GNUNET_SERVER_mst_destroy (plugin->broadcast_mst); - plugin->broadcast_mst = NULL; - } } /* end of plugin_transport_udp_broadcasting.c */ diff --git a/src/transport/plugin_transport_wlan.c b/src/transport/plugin_transport_wlan.c index 376065d24..3f5ada10b 100644 --- a/src/transport/plugin_transport_wlan.c +++ b/src/transport/plugin_transport_wlan.c @@ -38,6 +38,7 @@ #include "gnunet_fragmentation_lib.h" #include "gnunet_constants.h" + #if BUILD_WLAN /* begin case wlan */ #define PLUGIN_NAME "wlan" @@ -48,6 +49,7 @@ #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_wlan_done #define LOG(kind,...) GNUNET_log_from (kind, "transport-wlan",__VA_ARGS__) + /** * time out of a mac endpoint */ @@ -92,6 +94,30 @@ #error need to build wlan or bluetooth #endif + + +/** + * Functions with this signature are called whenever a + * complete message is received by the tokenizer. + * + * Do not call #GNUNET_SERVER_mst_destroy from within + * the scope of this callback. + * + * @param cls closure + * @param client identification of the client + * @param message the actual message + * @return #GNUNET_OK on success, #GNUNET_SYSERR to stop further processing + */ +typedef int +(*GNUNET_SERVER_MessageTokenizerCallback) (void *cls, + void *client, + const struct GNUNET_MessageHeader *message); + + +/* Include legacy message stream tokenizer that was removed from util (for now) */ +#include "tcp_server_mst_legacy.c" + + /** * Max size of packet (that we give to the WLAN driver for transmission) */ @@ -1728,11 +1754,10 @@ send_hello_beacon (void *cls) * Function used for to process the data from the suid process * * @param cls the plugin handle - * @param client client that send the data (not used) * @param hdr header of the GNUNET_MessageHeader */ static int -handle_helper_message (void *cls, void *client, +handle_helper_message (void *cls, const struct GNUNET_MessageHeader *hdr) { struct Plugin *plugin = cls; diff --git a/src/transport/tcp_connection_legacy.c b/src/transport/tcp_connection_legacy.c new file mode 100644 index 000000000..f5253445d --- /dev/null +++ b/src/transport/tcp_connection_legacy.c @@ -0,0 +1,1647 @@ +/* + This file is part of GNUnet. + Copyright (C) 2009-2013 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 3, 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., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * @file util/connection.c + * @brief TCP connection management + * @author Christian Grothoff + * + * This code is rather complex. Only modify it if you + * 1) Have a NEW testcase showing that the new code + * is needed and correct + * 2) All EXISTING testcases pass with the new code + * These rules should apply in general, but for this + * module they are VERY, VERY important. + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_resolver_service.h" + + + +#define LOG_STRERROR(kind,syscall) GNUNET_log_from_strerror (kind, "util-connection", syscall) + + +/** + * Transmission handle. There can only be one for each connection. + */ +struct GNUNET_CONNECTION_TransmitHandle +{ + + /** + * Function to call if the send buffer has notify_size + * bytes available. + */ + GNUNET_CONNECTION_TransmitReadyNotify notify_ready; + + /** + * Closure for notify_ready. + */ + void *notify_ready_cls; + + /** + * Our connection handle. + */ + struct GNUNET_CONNECTION_Handle *connection; + + /** + * Timeout for receiving (in absolute time). + */ + struct GNUNET_TIME_Absolute transmit_timeout; + + /** + * Task called on timeout. + */ + struct GNUNET_SCHEDULER_Task * timeout_task; + + /** + * At what number of bytes available in the + * write buffer should the notify method be called? + */ + size_t notify_size; + +}; + + +/** + * During connect, we try multiple possible IP addresses + * to find out which one might work. + */ +struct AddressProbe +{ + + /** + * This is a linked list. + */ + struct AddressProbe *next; + + /** + * This is a doubly-linked list. + */ + struct AddressProbe *prev; + + /** + * The address; do not free (allocated at the end of this struct). + */ + const struct sockaddr *addr; + + /** + * Underlying OS's socket. + */ + struct GNUNET_NETWORK_Handle *sock; + + /** + * Connection for which we are probing. + */ + struct GNUNET_CONNECTION_Handle *connection; + + /** + * Lenth of addr. + */ + socklen_t addrlen; + + /** + * Task waiting for the connection to finish connecting. + */ + struct GNUNET_SCHEDULER_Task * task; +}; + + +/** + * @brief handle for a network connection + */ +struct GNUNET_CONNECTION_Handle +{ + + /** + * Configuration to use. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Linked list of sockets we are currently trying out + * (during connect). + */ + struct AddressProbe *ap_head; + + /** + * Linked list of sockets we are currently trying out + * (during connect). + */ + struct AddressProbe *ap_tail; + + /** + * Network address of the other end-point, may be NULL. + */ + struct sockaddr *addr; + + /** + * Pointer to the hostname if connection was + * created using DNS lookup, otherwise NULL. + */ + char *hostname; + + /** + * Underlying OS's socket, set to NULL after fatal errors. + */ + struct GNUNET_NETWORK_Handle *sock; + + /** + * Function to call on data received, NULL if no receive is pending. + */ + GNUNET_CONNECTION_Receiver receiver; + + /** + * Closure for @e receiver. + */ + void *receiver_cls; + + /** + * Pointer to our write buffer. + */ + char *write_buffer; + + /** + * Current size of our @e write_buffer. + */ + size_t write_buffer_size; + + /** + * Current write-offset in @e write_buffer (where + * would we write next). + */ + size_t write_buffer_off; + + /** + * Current read-offset in @e write_buffer (how many + * bytes have already been sent). + */ + size_t write_buffer_pos; + + /** + * Length of @e addr. + */ + socklen_t addrlen; + + /** + * Read task that we may need to wait for. + */ + struct GNUNET_SCHEDULER_Task *read_task; + + /** + * Write task that we may need to wait for. + */ + struct GNUNET_SCHEDULER_Task *write_task; + + /** + * Handle to a pending DNS lookup request. + */ + struct GNUNET_RESOLVER_RequestHandle *dns_active; + + /** + * The handle we return for #GNUNET_CONNECTION_notify_transmit_ready(). + */ + struct GNUNET_CONNECTION_TransmitHandle nth; + + /** + * Timeout for receiving (in absolute time). + */ + struct GNUNET_TIME_Absolute receive_timeout; + + /** + * Maximum number of bytes to read (for receiving). + */ + size_t max; + + /** + * Port to connect to. + */ + uint16_t port; + + /** + * When shutdown, do not ever actually close the socket, but + * free resources. Only should ever be set if using program + * termination as a signal (because only then will the leaked + * socket be freed!) + */ + int8_t persist; + + /** + * Usually 0. Set to 1 if this handle is in use, and should + * #GNUNET_CONNECTION_destroy() be called right now, the action needs + * to be deferred by setting it to -1. + */ + int8_t destroy_later; + + /** + * Handle to subsequent connection after proxy handshake completes, + */ + struct GNUNET_CONNECTION_Handle *proxy_handshake; + +}; + + +/** + * Set the persist option on this connection handle. Indicates + * that the underlying socket or fd should never really be closed. + * Used for indicating process death. + * + * @param connection the connection to set persistent + */ +void +GNUNET_CONNECTION_persist_ (struct GNUNET_CONNECTION_Handle *connection) +{ + connection->persist = GNUNET_YES; +} + + +/** + * Disable the "CORK" feature for communication with the given connection, + * forcing the OS to immediately flush the buffer on transmission + * instead of potentially buffering multiple messages. Essentially + * reduces the OS send buffers to zero. + * Used to make sure that the last messages sent through the connection + * reach the other side before the process is terminated. + * + * @param connection the connection to make flushing and blocking + * @return #GNUNET_OK on success + */ +int +GNUNET_CONNECTION_disable_corking (struct GNUNET_CONNECTION_Handle *connection) +{ + return GNUNET_NETWORK_socket_disable_corking (connection->sock); +} + + +/** + * Create a connection handle by boxing an existing OS socket. The OS + * socket should henceforth be no longer used directly. + * #GNUNET_connection_destroy() will close it. + * + * @param osSocket existing socket to box + * @return the boxed connection handle + */ +struct GNUNET_CONNECTION_Handle * +GNUNET_CONNECTION_create_from_existing (struct GNUNET_NETWORK_Handle *osSocket) +{ + struct GNUNET_CONNECTION_Handle *connection; + + connection = GNUNET_new (struct GNUNET_CONNECTION_Handle); + connection->write_buffer_size = GNUNET_SERVER_MIN_BUFFER_SIZE; + connection->write_buffer = GNUNET_malloc (connection->write_buffer_size); + connection->sock = osSocket; + return connection; +} + + +/** + * Create a connection handle by accepting on a listen socket. This + * function may block if the listen socket has no connection ready. + * + * @param access_cb function to use to check if access is allowed + * @param access_cb_cls closure for @a access_cb + * @param lsock listen socket + * @return the connection handle, NULL on error + */ +struct GNUNET_CONNECTION_Handle * +GNUNET_CONNECTION_create_from_accept (GNUNET_CONNECTION_AccessCheck access_cb, + void *access_cb_cls, + struct GNUNET_NETWORK_Handle *lsock) +{ + struct GNUNET_CONNECTION_Handle *connection; + char addr[128]; + socklen_t addrlen; + struct GNUNET_NETWORK_Handle *sock; + int aret; + struct sockaddr_in *v4; + struct sockaddr_in6 *v6; + struct sockaddr *sa; + void *uaddr; +#ifdef SO_PEERCRED + struct ucred uc; + socklen_t olen; +#endif + struct GNUNET_CONNECTION_Credentials *gcp; +#if HAVE_GETPEEREID || defined(SO_PEERCRED) || HAVE_GETPEERUCRED + struct GNUNET_CONNECTION_Credentials gc; + + gc.uid = 0; + gc.gid = 0; +#endif + + addrlen = sizeof (addr); + sock = + GNUNET_NETWORK_socket_accept (lsock, + (struct sockaddr *) &addr, + &addrlen); + if (NULL == sock) + { + if (EAGAIN != errno) + LOG_STRERROR (GNUNET_ERROR_TYPE_WARNING, "accept"); + return NULL; + } + if ((addrlen > sizeof (addr)) || (addrlen < sizeof (sa_family_t))) + { + GNUNET_break (0); + GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (sock)); + return NULL; + } + + sa = (struct sockaddr *) addr; + v6 = (struct sockaddr_in6 *) addr; + if ( (AF_INET6 == sa->sa_family) && + (IN6_IS_ADDR_V4MAPPED (&v6->sin6_addr)) ) + { + /* convert to V4 address */ + v4 = GNUNET_new (struct sockaddr_in); + memset (v4, 0, sizeof (struct sockaddr_in)); + v4->sin_family = AF_INET; +#if HAVE_SOCKADDR_IN_SIN_LEN + v4->sin_len = (u_char) sizeof (struct sockaddr_in); +#endif + GNUNET_memcpy (&v4->sin_addr, + &((char *) &v6->sin6_addr)[sizeof (struct in6_addr) - + sizeof (struct in_addr)], + sizeof (struct in_addr)); + v4->sin_port = v6->sin6_port; + uaddr = v4; + addrlen = sizeof (struct sockaddr_in); + } + else + { + uaddr = GNUNET_malloc (addrlen); + GNUNET_memcpy (uaddr, addr, addrlen); + } + gcp = NULL; + if (AF_UNIX == sa->sa_family) + { +#if HAVE_GETPEEREID + /* most BSDs */ + if (0 == getpeereid (GNUNET_NETWORK_get_fd (sock), + &gc.uid, + &gc.gid)) + gcp = &gc; +#else +#ifdef SO_PEERCRED + /* largely traditional GNU/Linux */ + olen = sizeof (uc); + if ( (0 == + getsockopt (GNUNET_NETWORK_get_fd (sock), + SOL_SOCKET, + SO_PEERCRED, + &uc, + &olen)) && + (olen == sizeof (uc)) ) + { + gc.uid = uc.uid; + gc.gid = uc.gid; + gcp = &gc; + } +#else +#if HAVE_GETPEERUCRED + /* this is for Solaris 10 */ + ucred_t *uc; + + uc = NULL; + if (0 == getpeerucred (GNUNET_NETWORK_get_fd (sock), &uc)) + { + gc.uid = ucred_geteuid (uc); + gc.gid = ucred_getegid (uc); + gcp = &gc; + } + ucred_free (uc); +#endif +#endif +#endif + } + + if ( (NULL != access_cb) && + (GNUNET_YES != (aret = access_cb (access_cb_cls, + gcp, + uaddr, + addrlen))) ) + { + if (GNUNET_NO == aret) + LOG (GNUNET_ERROR_TYPE_INFO, + _("Access denied to `%s'\n"), + GNUNET_a2s (uaddr, + addrlen)); + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_shutdown (sock, + SHUT_RDWR)); + GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (sock)); + GNUNET_free (uaddr); + return NULL; + } + connection = GNUNET_new (struct GNUNET_CONNECTION_Handle); + connection->write_buffer_size = GNUNET_SERVER_MIN_BUFFER_SIZE; + connection->write_buffer = GNUNET_malloc (connection->write_buffer_size); + connection->addr = uaddr; + connection->addrlen = addrlen; + connection->sock = sock; + LOG (GNUNET_ERROR_TYPE_INFO, + _("Accepting connection from `%s': %p\n"), + GNUNET_a2s (uaddr, + addrlen), + connection); + return connection; +} + + +/** + * Obtain the network address of the other party. + * + * @param connection the client to get the address for + * @param addr where to store the address + * @param addrlen where to store the length of the @a addr + * @return #GNUNET_OK on success + */ +int +GNUNET_CONNECTION_get_address (struct GNUNET_CONNECTION_Handle *connection, + void **addr, + size_t *addrlen) +{ + if ((NULL == connection->addr) || (0 == connection->addrlen)) + return GNUNET_NO; + *addr = GNUNET_malloc (connection->addrlen); + GNUNET_memcpy (*addr, connection->addr, connection->addrlen); + *addrlen = connection->addrlen; + return GNUNET_OK; +} + + +/** + * Tell the receiver callback that we had an IO error. + * + * @param connection connection to signal error + * @param errcode error code to send + */ +static void +signal_receive_error (struct GNUNET_CONNECTION_Handle *connection, + int errcode) +{ + GNUNET_CONNECTION_Receiver receiver; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Receive encounters error (%s), connection closed (%p)\n", + STRERROR (errcode), + connection); + GNUNET_assert (NULL != (receiver = connection->receiver)); + connection->receiver = NULL; + receiver (connection->receiver_cls, + NULL, + 0, + connection->addr, + connection->addrlen, + errcode); +} + + +/** + * Tell the receiver callback that a timeout was reached. + * + * @param connection connection to signal for + */ +static void +signal_receive_timeout (struct GNUNET_CONNECTION_Handle *connection) +{ + GNUNET_CONNECTION_Receiver receiver; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Connection signals timeout to receiver (%p)!\n", + connection); + GNUNET_assert (NULL != (receiver = connection->receiver)); + connection->receiver = NULL; + receiver (connection->receiver_cls, NULL, 0, NULL, 0, 0); +} + + +/** + * We failed to transmit data to the service, signal the error. + * + * @param connection handle that had trouble + * @param ecode error code (errno) + */ +static void +signal_transmit_error (struct GNUNET_CONNECTION_Handle *connection, + int ecode) +{ + GNUNET_CONNECTION_TransmitReadyNotify notify; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Transmission encounterd error (%s), connection closed (%p)\n", + STRERROR (ecode), + connection); + if (NULL != connection->sock) + { + (void) GNUNET_NETWORK_socket_shutdown (connection->sock, + SHUT_RDWR); + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (connection->sock)); + connection->sock = NULL; + GNUNET_assert (NULL == connection->write_task); + } + if (NULL != connection->read_task) + { + /* send errors trigger read errors... */ + GNUNET_SCHEDULER_cancel (connection->read_task); + connection->read_task = NULL; + signal_receive_timeout (connection); + return; + } + if (NULL == connection->nth.notify_ready) + return; /* nobody to tell about it */ + notify = connection->nth.notify_ready; + connection->nth.notify_ready = NULL; + notify (connection->nth.notify_ready_cls, + 0, + NULL); +} + + +/** + * We've failed for good to establish a connection (timeout or + * no more addresses to try). + * + * @param connection the connection we tried to establish + */ +static void +connect_fail_continuation (struct GNUNET_CONNECTION_Handle *connection) +{ + LOG (GNUNET_ERROR_TYPE_INFO, + "Failed to establish TCP connection to `%s:%u', no further addresses to try.\n", + connection->hostname, + connection->port); + GNUNET_break (NULL == connection->ap_head); + GNUNET_break (NULL == connection->ap_tail); + GNUNET_break (GNUNET_NO == connection->dns_active); + GNUNET_break (NULL == connection->sock); + GNUNET_assert (NULL == connection->write_task); + GNUNET_assert (NULL == connection->proxy_handshake); + + /* signal errors for jobs that used to wait on the connection */ + connection->destroy_later = 1; + if (NULL != connection->receiver) + signal_receive_error (connection, + ECONNREFUSED); + if (NULL != connection->nth.notify_ready) + { + GNUNET_assert (NULL != connection->nth.timeout_task); + GNUNET_SCHEDULER_cancel (connection->nth.timeout_task); + connection->nth.timeout_task = NULL; + signal_transmit_error (connection, + ECONNREFUSED); + } + if (-1 == connection->destroy_later) + { + /* do it now */ + connection->destroy_later = 0; + GNUNET_CONNECTION_destroy (connection); + return; + } + connection->destroy_later = 0; +} + + +/** + * We are ready to transmit (or got a timeout). + * + * @param cls our connection handle + */ +static void +transmit_ready (void *cls); + + +/** + * This function is called once we either timeout or have data ready + * to read. + * + * @param cls connection to read from + */ +static void +receive_ready (void *cls); + + +/** + * We've succeeded in establishing a connection. + * + * @param connection the connection we tried to establish + */ +static void +connect_success_continuation (struct GNUNET_CONNECTION_Handle *connection) +{ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Connection to `%s' succeeded! (%p)\n", + GNUNET_a2s (connection->addr, + connection->addrlen), + connection); + /* trigger jobs that waited for the connection */ + if (NULL != connection->receiver) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Connection succeeded, starting with receiving data (%p)\n", + connection); + GNUNET_assert (NULL == connection->read_task); + connection->read_task = + GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_absolute_get_remaining + (connection->receive_timeout), + connection->sock, + &receive_ready, connection); + } + if (NULL != connection->nth.notify_ready) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Connection succeeded, starting with sending data (%p)\n", + connection); + GNUNET_assert (connection->nth.timeout_task != NULL); + GNUNET_SCHEDULER_cancel (connection->nth.timeout_task); + connection->nth.timeout_task = NULL; + GNUNET_assert (connection->write_task == NULL); + connection->write_task = + GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_absolute_get_remaining + (connection->nth.transmit_timeout), connection->sock, + &transmit_ready, connection); + } +} + + +/** + * Scheduler let us know that we're either ready to write on the + * socket OR connect timed out. Do the right thing. + * + * @param cls the `struct AddressProbe *` with the address that we are probing + */ +static void +connect_probe_continuation (void *cls) +{ + struct AddressProbe *ap = cls; + struct GNUNET_CONNECTION_Handle *connection = ap->connection; + const struct GNUNET_SCHEDULER_TaskContext *tc; + struct AddressProbe *pos; + int error; + socklen_t len; + + GNUNET_assert (NULL != ap->sock); + GNUNET_CONTAINER_DLL_remove (connection->ap_head, + connection->ap_tail, + ap); + len = sizeof (error); + errno = 0; + 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 == connection->ap_head) && + (GNUNET_NO == connection->dns_active) && + (NULL == connection->proxy_handshake) ) + connect_fail_continuation (connection); + return; + } + GNUNET_assert (NULL == connection->sock); + connection->sock = ap->sock; + GNUNET_assert (NULL == connection->addr); + connection->addr = GNUNET_malloc (ap->addrlen); + GNUNET_memcpy (connection->addr, ap->addr, ap->addrlen); + connection->addrlen = ap->addrlen; + GNUNET_free (ap); + /* cancel all other attempts */ + while (NULL != (pos = connection->ap_head)) + { + GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (pos->sock)); + GNUNET_SCHEDULER_cancel (pos->task); + GNUNET_CONTAINER_DLL_remove (connection->ap_head, + connection->ap_tail, + pos); + GNUNET_free (pos); + } + connect_success_continuation (connection); +} + + +/** + * 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_CONNECTION_Handle *` + * @param addr address to try, NULL for "last call" + * @param addrlen length of @a addr + */ +static void +try_connect_using_address (void *cls, + const struct sockaddr *addr, + socklen_t addrlen) +{ + struct GNUNET_CONNECTION_Handle *connection = cls; + struct AddressProbe *ap; + struct GNUNET_TIME_Relative delay; + + if (NULL == addr) + { + connection->dns_active = NULL; + if ((NULL == connection->ap_head) && + (NULL == connection->sock) && + (NULL == connection->proxy_handshake)) + connect_fail_continuation (connection); + return; + } + if (NULL != connection->sock) + return; /* already connected */ + GNUNET_assert (NULL == connection->addr); + /* try to connect */ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Trying to connect using address `%s:%u/%s:%u'\n", + connection->hostname, + connection->port, + GNUNET_a2s (addr, addrlen), + connection->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->connection = connection; + + switch (ap->addr->sa_family) + { + case AF_INET: + ((struct sockaddr_in *) ap->addr)->sin_port = htons (connection->port); + break; + case AF_INET6: + ((struct sockaddr_in6 *) ap->addr)->sin6_port = htons (connection->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 */ + } + LOG (GNUNET_ERROR_TYPE_INFO, + "Trying to connect to `%s' (%p)\n", + GNUNET_a2s (ap->addr, ap->addrlen), + connection); + if ((GNUNET_OK != + GNUNET_NETWORK_socket_connect (ap->sock, + ap->addr, + ap->addrlen)) && + (EINPROGRESS != errno)) + { + /* maybe refused / unsupported address, try next */ + 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 (connection->ap_head, connection->ap_tail, ap); + delay = GNUNET_CONNECTION_CONNECT_RETRY_TIMEOUT; + if (NULL != connection->nth.notify_ready) + delay = GNUNET_TIME_relative_min (delay, + GNUNET_TIME_absolute_get_remaining (connection->nth.transmit_timeout)); + if (NULL != connection->receiver) + delay = GNUNET_TIME_relative_min (delay, + GNUNET_TIME_absolute_get_remaining (connection->receive_timeout)); + ap->task = GNUNET_SCHEDULER_add_write_net (delay, + ap->sock, + &connect_probe_continuation, + ap); +} + + +/** + * Create a connection handle by (asynchronously) connecting to a host. + * This function returns immediately, even if the connection has not + * yet been established. This function only creates TCP connections. + * + * @param cfg configuration to use + * @param hostname name of the host to connect to + * @param port port to connect to + * @return the connection handle + */ +struct GNUNET_CONNECTION_Handle * +GNUNET_CONNECTION_create_from_connect (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *hostname, + uint16_t port) +{ + struct GNUNET_CONNECTION_Handle *connection; + + GNUNET_assert (0 < strlen (hostname)); /* sanity check */ + connection = GNUNET_new (struct GNUNET_CONNECTION_Handle); + connection->cfg = cfg; + connection->write_buffer_size = GNUNET_SERVER_MIN_BUFFER_SIZE; + connection->write_buffer = GNUNET_malloc (connection->write_buffer_size); + connection->port = port; + connection->hostname = GNUNET_strdup (hostname); + connection->dns_active = + GNUNET_RESOLVER_ip_get (connection->hostname, + AF_UNSPEC, + GNUNET_CONNECTION_CONNECT_RETRY_TIMEOUT, + &try_connect_using_address, + connection); + return connection; +} + + +/** + * Create a connection handle by connecting to a UNIX domain service. + * This function returns immediately, even if the connection has not + * yet been established. This function only creates UNIX connections. + * + * @param cfg configuration to use + * @param unixpath path to connect to + * @return the connection handle, NULL on systems without UNIX support + */ +struct GNUNET_CONNECTION_Handle * +GNUNET_CONNECTION_create_from_connect_to_unixpath (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *unixpath) +{ +#ifdef AF_UNIX + struct GNUNET_CONNECTION_Handle *connection; + struct sockaddr_un *un; + + GNUNET_assert (0 < strlen (unixpath)); /* sanity check */ + un = GNUNET_new (struct sockaddr_un); + un->sun_family = AF_UNIX; + strncpy (un->sun_path, unixpath, sizeof (un->sun_path) - 1); +#ifdef LINUX + { + int abstract; + + abstract = GNUNET_CONFIGURATION_get_value_yesno (cfg, + "TESTING", + "USE_ABSTRACT_SOCKETS"); + if (GNUNET_YES == abstract) + un->sun_path[0] = '\0'; + } +#endif +#if HAVE_SOCKADDR_UN_SUN_LEN + un->sun_len = (u_char) sizeof (struct sockaddr_un); +#endif + connection = GNUNET_new (struct GNUNET_CONNECTION_Handle); + connection->cfg = cfg; + connection->write_buffer_size = GNUNET_SERVER_MIN_BUFFER_SIZE; + connection->write_buffer = GNUNET_malloc (connection->write_buffer_size); + connection->port = 0; + connection->hostname = NULL; + connection->addr = (struct sockaddr *) un; + connection->addrlen = sizeof (struct sockaddr_un); + connection->sock = GNUNET_NETWORK_socket_create (AF_UNIX, + SOCK_STREAM, + 0); + if (NULL == connection->sock) + { + GNUNET_free (connection->addr); + GNUNET_free (connection->write_buffer); + GNUNET_free (connection); + return NULL; + } + if ( (GNUNET_OK != + GNUNET_NETWORK_socket_connect (connection->sock, + connection->addr, + connection->addrlen)) && + (EINPROGRESS != errno) ) + { + /* Just return; we expect everything to work eventually so don't fail HARD */ + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (connection->sock)); + connection->sock = NULL; + return connection; + } + connect_success_continuation (connection); + return connection; +#else + return NULL; +#endif +} + + +/** + * Create a connection handle by (asynchronously) connecting to a host. + * This function returns immediately, even if the connection has not + * yet been established. This function only creates TCP connections. + * + * @param s socket to connect + * @param serv_addr server address + * @param addrlen length of @a serv_addr + * @return the connection handle + */ +struct GNUNET_CONNECTION_Handle * +GNUNET_CONNECTION_connect_socket (struct GNUNET_NETWORK_Handle *s, + const struct sockaddr *serv_addr, + socklen_t addrlen) +{ + struct GNUNET_CONNECTION_Handle *connection; + + if ( (GNUNET_OK != + GNUNET_NETWORK_socket_connect (s, serv_addr, addrlen)) && + (EINPROGRESS != errno) ) + { + /* maybe refused / unsupported address, try next */ + LOG_STRERROR (GNUNET_ERROR_TYPE_DEBUG, + "connect"); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Attempt to connect to `%s' failed\n", + GNUNET_a2s (serv_addr, + addrlen)); + GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (s)); + return NULL; + } + connection = GNUNET_CONNECTION_create_from_existing (s); + connection->addr = GNUNET_malloc (addrlen); + GNUNET_memcpy (connection->addr, serv_addr, addrlen); + connection->addrlen = addrlen; + LOG (GNUNET_ERROR_TYPE_INFO, + "Trying to connect to `%s' (%p)\n", + GNUNET_a2s (serv_addr, addrlen), + connection); + return connection; +} + + +/** + * Create a connection handle by creating a socket and + * (asynchronously) connecting to a host. This function returns + * immediately, even if the connection has not yet been established. + * This function only creates TCP connections. + * + * @param af_family address family to use + * @param serv_addr server address + * @param addrlen length of @a serv_addr + * @return the connection handle + */ +struct GNUNET_CONNECTION_Handle * +GNUNET_CONNECTION_create_from_sockaddr (int af_family, + const struct sockaddr *serv_addr, + socklen_t addrlen) +{ + struct GNUNET_NETWORK_Handle *s; + + s = GNUNET_NETWORK_socket_create (af_family, SOCK_STREAM, 0); + if (NULL == s) + { + LOG_STRERROR (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK, + "socket"); + return NULL; + } + return GNUNET_CONNECTION_connect_socket (s, + serv_addr, + addrlen); +} + + +/** + * Check if connection is valid (no fatal errors have happened so far). + * Note that a connection that is still trying to connect is considered + * valid. + * + * @param connection connection to check + * @return #GNUNET_YES if valid, #GNUNET_NO otherwise + */ +int +GNUNET_CONNECTION_check (struct GNUNET_CONNECTION_Handle *connection) +{ + if ((NULL != connection->ap_head) || + (NULL != connection->dns_active) || + (NULL != connection->proxy_handshake)) + return GNUNET_YES; /* still trying to connect */ + if ( (0 != connection->destroy_later) || + (NULL == connection->sock) ) + return GNUNET_NO; + return GNUNET_YES; +} + + +/** + * Close the connection and free associated resources. There must + * not be any pending requests for reading or writing to the + * connection at this time. + * + * @param connection connection to destroy + */ +void +GNUNET_CONNECTION_destroy (struct GNUNET_CONNECTION_Handle *connection) +{ + struct AddressProbe *pos; + + if (0 != connection->destroy_later) + { + connection->destroy_later = -1; + return; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Shutting down connection (%p)\n", + connection); + GNUNET_assert (NULL == connection->nth.notify_ready); + GNUNET_assert (NULL == connection->receiver); + if (NULL != connection->write_task) + { + GNUNET_SCHEDULER_cancel (connection->write_task); + connection->write_task = NULL; + connection->write_buffer_off = 0; + } + if (NULL != connection->read_task) + { + GNUNET_SCHEDULER_cancel (connection->read_task); + connection->read_task = NULL; + } + if (NULL != connection->nth.timeout_task) + { + GNUNET_SCHEDULER_cancel (connection->nth.timeout_task); + connection->nth.timeout_task = NULL; + } + connection->nth.notify_ready = NULL; + if (NULL != connection->dns_active) + { + GNUNET_RESOLVER_request_cancel (connection->dns_active); + connection->dns_active = NULL; + } + if (NULL != connection->proxy_handshake) + { + /* GNUNET_CONNECTION_destroy (connection->proxy_handshake); */ + connection->proxy_handshake->destroy_later = -1; + connection->proxy_handshake = NULL; /* Not leaked ??? */ + } + while (NULL != (pos = connection->ap_head)) + { + GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (pos->sock)); + GNUNET_SCHEDULER_cancel (pos->task); + GNUNET_CONTAINER_DLL_remove (connection->ap_head, + connection->ap_tail, + pos); + GNUNET_free (pos); + } + if ( (NULL != connection->sock) && + (GNUNET_YES != connection->persist) ) + { + if ((GNUNET_OK != + GNUNET_NETWORK_socket_shutdown (connection->sock, + SHUT_RDWR)) && + (ENOTCONN != errno) && + (ECONNRESET != errno) ) + LOG_STRERROR (GNUNET_ERROR_TYPE_WARNING, + "shutdown"); + } + if (NULL != connection->sock) + { + if (GNUNET_YES != connection->persist) + { + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (connection->sock)); + } + else + { + GNUNET_NETWORK_socket_free_memory_only_ (connection->sock); /* at least no memory leak (we deliberately + * leak the socket in this special case) ... */ + } + } + GNUNET_free_non_null (connection->addr); + GNUNET_free_non_null (connection->hostname); + GNUNET_free (connection->write_buffer); + GNUNET_free (connection); +} + + +/** + * This function is called once we either timeout + * or have data ready to read. + * + * @param cls connection to read from + */ +static void +receive_ready (void *cls) +{ + struct GNUNET_CONNECTION_Handle *connection = cls; + const struct GNUNET_SCHEDULER_TaskContext *tc; + char buffer[connection->max]; + ssize_t ret; + GNUNET_CONNECTION_Receiver receiver; + + connection->read_task = NULL; + tc = GNUNET_SCHEDULER_get_task_context (); + if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_TIMEOUT)) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Receive from `%s' encounters error: timeout (%s, %p)\n", + GNUNET_a2s (connection->addr, + connection->addrlen), + GNUNET_STRINGS_relative_time_to_string (GNUNET_TIME_absolute_get_duration (connection->receive_timeout), + GNUNET_YES), + connection); + signal_receive_timeout (connection); + return; + } + if (NULL == connection->sock) + { + /* connect failed for good */ + signal_receive_error (connection, ECONNREFUSED); + return; + } + GNUNET_assert (GNUNET_NETWORK_fdset_isset (tc->read_ready, + connection->sock)); +RETRY: + ret = GNUNET_NETWORK_socket_recv (connection->sock, + buffer, + connection->max); + if (-1 == ret) + { + if (EINTR == errno) + goto RETRY; + signal_receive_error (connection, errno); + return; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "receive_ready read %u/%u bytes from `%s' (%p)!\n", + (unsigned int) ret, + connection->max, + GNUNET_a2s (connection->addr, + connection->addrlen), + connection); + GNUNET_assert (NULL != (receiver = connection->receiver)); + connection->receiver = NULL; + receiver (connection->receiver_cls, + buffer, + ret, + connection->addr, + connection->addrlen, + 0); +} + + +/** + * Receive data from the given connection. Note that this function + * will call @a receiver asynchronously using the scheduler. It will + * "immediately" return. Note that there MUST only be one active + * receive call per connection at any given point in time (so do not + * call receive again until the receiver callback has been invoked). + * + * @param connection connection handle + * @param max maximum number of bytes to read + * @param timeout maximum amount of time to wait + * @param receiver function to call with received data + * @param receiver_cls closure for @a receiver + */ +void +GNUNET_CONNECTION_receive (struct GNUNET_CONNECTION_Handle *connection, + size_t max, + struct GNUNET_TIME_Relative timeout, + GNUNET_CONNECTION_Receiver receiver, + void *receiver_cls) +{ + GNUNET_assert ((NULL == connection->read_task) && + (NULL == connection->receiver)); + GNUNET_assert (NULL != receiver); + connection->receiver = receiver; + connection->receiver_cls = receiver_cls; + connection->receive_timeout = GNUNET_TIME_relative_to_absolute (timeout); + connection->max = max; + if (NULL != connection->sock) + { + connection->read_task = + GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_absolute_get_remaining + (connection->receive_timeout), + connection->sock, + &receive_ready, + connection); + return; + } + if ((NULL == connection->dns_active) && + (NULL == connection->ap_head) && + (NULL == connection->proxy_handshake)) + { + connection->receiver = NULL; + receiver (receiver_cls, + NULL, 0, + NULL, 0, + ETIMEDOUT); + return; + } +} + + +/** + * Cancel receive job on the given connection. Note that the + * receiver callback must not have been called yet in order + * for the cancellation to be valid. + * + * @param connection connection handle + * @return closure of the original receiver callback closure + */ +void * +GNUNET_CONNECTION_receive_cancel (struct GNUNET_CONNECTION_Handle *connection) +{ + if (NULL != connection->read_task) + { + GNUNET_assert (connection == + GNUNET_SCHEDULER_cancel (connection->read_task)); + connection->read_task = NULL; + } + connection->receiver = NULL; + return connection->receiver_cls; +} + + +/** + * Try to call the transmit notify method (check if we do + * have enough space available first)! + * + * @param connection connection for which we should do this processing + * @return #GNUNET_YES if we were able to call notify + */ +static int +process_notify (struct GNUNET_CONNECTION_Handle *connection) +{ + size_t used; + size_t avail; + size_t size; + GNUNET_CONNECTION_TransmitReadyNotify notify; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "process_notify is running\n"); + GNUNET_assert (NULL == connection->write_task); + if (NULL == (notify = connection->nth.notify_ready)) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "No one to notify\n"); + return GNUNET_NO; + } + used = connection->write_buffer_off - connection->write_buffer_pos; + avail = connection->write_buffer_size - used; + size = connection->nth.notify_size; + if (size > avail) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Not enough buffer\n"); + return GNUNET_NO; + } + connection->nth.notify_ready = NULL; + if (connection->write_buffer_size - connection->write_buffer_off < size) + { + /* need to compact */ + memmove (connection->write_buffer, + &connection->write_buffer[connection->write_buffer_pos], + used); + connection->write_buffer_off -= connection->write_buffer_pos; + connection->write_buffer_pos = 0; + } + avail = connection->write_buffer_size - connection->write_buffer_off; + GNUNET_assert (avail >= size); + size = + notify (connection->nth.notify_ready_cls, avail, + &connection->write_buffer[connection->write_buffer_off]); + GNUNET_assert (size <= avail); + if (0 != size) + connection->write_buffer_off += size; + return GNUNET_YES; +} + + +/** + * Task invoked by the scheduler when a call to transmit + * is timing out (we never got enough buffer space to call + * the callback function before the specified timeout + * expired). + * + * This task notifies the client about the timeout. + * + * @param cls the `struct GNUNET_CONNECTION_Handle` + */ +static void +transmit_timeout (void *cls) +{ + struct GNUNET_CONNECTION_Handle *connection = cls; + GNUNET_CONNECTION_TransmitReadyNotify notify; + + connection->nth.timeout_task = NULL; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Transmit to `%s:%u/%s' fails, time out reached (%p).\n", + connection->hostname, + connection->port, + GNUNET_a2s (connection->addr, + connection->addrlen), + connection); + notify = connection->nth.notify_ready; + GNUNET_assert (NULL != notify); + connection->nth.notify_ready = NULL; + notify (connection->nth.notify_ready_cls, + 0, + NULL); +} + + +/** + * Task invoked by the scheduler when we failed to connect + * at the time of being asked to transmit. + * + * This task notifies the client about the error. + * + * @param cls the `struct GNUNET_CONNECTION_Handle` + */ +static void +connect_error (void *cls) +{ + struct GNUNET_CONNECTION_Handle *connection = cls; + GNUNET_CONNECTION_TransmitReadyNotify notify; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Transmission request of size %u fails (%s/%u), connection failed (%p).\n", + connection->nth.notify_size, + connection->hostname, + connection->port, + connection); + connection->write_task = NULL; + notify = connection->nth.notify_ready; + connection->nth.notify_ready = NULL; + notify (connection->nth.notify_ready_cls, + 0, + NULL); +} + + +/** + * We are ready to transmit (or got a timeout). + * + * @param cls our connection handle + */ +static void +transmit_ready (void *cls) +{ + struct GNUNET_CONNECTION_Handle *connection = cls; + GNUNET_CONNECTION_TransmitReadyNotify notify; + const struct GNUNET_SCHEDULER_TaskContext *tc; + ssize_t ret; + size_t have; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "transmit_ready running (%p).\n", + connection); + GNUNET_assert (NULL != connection->write_task); + connection->write_task = NULL; + GNUNET_assert (NULL == connection->nth.timeout_task); + tc = GNUNET_SCHEDULER_get_task_context (); + if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_TIMEOUT)) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Transmit to `%s' fails, time out reached (%p).\n", + GNUNET_a2s (connection->addr, + connection->addrlen), + connection); + notify = connection->nth.notify_ready; + GNUNET_assert (NULL != notify); + connection->nth.notify_ready = NULL; + notify (connection->nth.notify_ready_cls, 0, NULL); + return; + } + GNUNET_assert (NULL != connection->sock); + if (NULL == tc->write_ready) + { + /* special circumstances (in particular, PREREQ_DONE after + * connect): not yet ready to write, but no "fatal" error either. + * Hence retry. */ + goto SCHEDULE_WRITE; + } + if (! GNUNET_NETWORK_fdset_isset (tc->write_ready, + connection->sock)) + { + GNUNET_assert (NULL == connection->write_task); + /* special circumstances (in particular, shutdown): not yet ready + * to write, but no "fatal" error either. Hence retry. */ + goto SCHEDULE_WRITE; + } + GNUNET_assert (connection->write_buffer_off >= connection->write_buffer_pos); + if ((NULL != connection->nth.notify_ready) && + (connection->write_buffer_size < connection->nth.notify_size)) + { + connection->write_buffer = + GNUNET_realloc (connection->write_buffer, connection->nth.notify_size); + connection->write_buffer_size = connection->nth.notify_size; + } + process_notify (connection); + have = connection->write_buffer_off - connection->write_buffer_pos; + if (0 == have) + { + /* no data ready for writing, terminate write loop */ + return; + } + GNUNET_assert (have <= connection->write_buffer_size); + GNUNET_assert (have + connection->write_buffer_pos <= connection->write_buffer_size); + GNUNET_assert (connection->write_buffer_pos <= connection->write_buffer_size); +RETRY: + ret = + GNUNET_NETWORK_socket_send (connection->sock, + &connection->write_buffer[connection->write_buffer_pos], + have); + if (-1 == ret) + { + if (EINTR == errno) + goto RETRY; + if (NULL != connection->write_task) + { + GNUNET_SCHEDULER_cancel (connection->write_task); + connection->write_task = NULL; + } + signal_transmit_error (connection, errno); + return; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Connection transmitted %u/%u bytes to `%s' (%p)\n", + (unsigned int) ret, + have, + GNUNET_a2s (connection->addr, + connection->addrlen), + connection); + connection->write_buffer_pos += ret; + if (connection->write_buffer_pos == connection->write_buffer_off) + { + /* transmitted all pending data */ + connection->write_buffer_pos = 0; + connection->write_buffer_off = 0; + } + if ( (0 == connection->write_buffer_off) && + (NULL == connection->nth.notify_ready) ) + return; /* all data sent! */ + /* not done writing, schedule more */ +SCHEDULE_WRITE: + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Re-scheduling transmit_ready (more to do) (%p).\n", + connection); + have = connection->write_buffer_off - connection->write_buffer_pos; + GNUNET_assert ( (NULL != connection->nth.notify_ready) || + (have > 0) ); + if (NULL == connection->write_task) + connection->write_task = + GNUNET_SCHEDULER_add_write_net ((connection->nth.notify_ready == + NULL) ? GNUNET_TIME_UNIT_FOREVER_REL : + GNUNET_TIME_absolute_get_remaining + (connection->nth.transmit_timeout), + connection->sock, + &transmit_ready, connection); +} + + +/** + * Ask the connection to call us once the specified number of bytes + * are free in the transmission buffer. Will never call the @a notify + * callback in this task, but always first go into the scheduler. + * + * @param connection connection + * @param size number of bytes to send + * @param timeout after how long should we give up (and call + * @a notify with buf NULL and size 0)? + * @param notify function to call + * @param notify_cls closure for @a notify + * @return non-NULL if the notify callback was queued, + * NULL if we are already going to notify someone else (busy) + */ +struct GNUNET_CONNECTION_TransmitHandle * +GNUNET_CONNECTION_notify_transmit_ready (struct GNUNET_CONNECTION_Handle *connection, + size_t size, + struct GNUNET_TIME_Relative timeout, + GNUNET_CONNECTION_TransmitReadyNotify notify, + void *notify_cls) +{ + if (NULL != connection->nth.notify_ready) + { + GNUNET_assert (0); + return NULL; + } + GNUNET_assert (NULL != notify); + GNUNET_assert (size < GNUNET_SERVER_MAX_MESSAGE_SIZE); + GNUNET_assert (connection->write_buffer_off <= connection->write_buffer_size); + GNUNET_assert (connection->write_buffer_pos <= connection->write_buffer_size); + GNUNET_assert (connection->write_buffer_pos <= connection->write_buffer_off); + connection->nth.notify_ready = notify; + connection->nth.notify_ready_cls = notify_cls; + connection->nth.connection = connection; + connection->nth.notify_size = size; + connection->nth.transmit_timeout = GNUNET_TIME_relative_to_absolute (timeout); + GNUNET_assert (NULL == connection->nth.timeout_task); + if ((NULL == connection->sock) && + (NULL == connection->ap_head) && + (NULL == connection->dns_active) && + (NULL == connection->proxy_handshake)) + { + if (NULL != connection->write_task) + GNUNET_SCHEDULER_cancel (connection->write_task); + connection->write_task = GNUNET_SCHEDULER_add_now (&connect_error, + connection); + return &connection->nth; + } + if (NULL != connection->write_task) + return &connection->nth; /* previous transmission still in progress */ + if (NULL != connection->sock) + { + /* connected, try to transmit now */ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Scheduling transmission (%p).\n", + connection); + connection->write_task = + GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_absolute_get_remaining + (connection->nth.transmit_timeout), + connection->sock, + &transmit_ready, connection); + return &connection->nth; + } + /* not yet connected, wait for connection */ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Need to wait to schedule transmission for connection, adding timeout task (%p).\n", + connection); + connection->nth.timeout_task = + GNUNET_SCHEDULER_add_delayed (timeout, + &transmit_timeout, + connection); + return &connection->nth; +} + + +/** + * Cancel the specified transmission-ready notification. + * + * @param th notification to cancel + */ +void +GNUNET_CONNECTION_notify_transmit_ready_cancel (struct GNUNET_CONNECTION_TransmitHandle *th) +{ + GNUNET_assert (NULL != th->notify_ready); + th->notify_ready = NULL; + if (NULL != th->timeout_task) + { + GNUNET_SCHEDULER_cancel (th->timeout_task); + th->timeout_task = NULL; + } + if (NULL != th->connection->write_task) + { + GNUNET_SCHEDULER_cancel (th->connection->write_task); + th->connection->write_task = NULL; + } +} + + +/** + * Create a connection to be proxied using a given connection. + * + * @param cph connection to proxy server + * @return connection to be proxied + */ +struct GNUNET_CONNECTION_Handle * +GNUNET_CONNECTION_create_proxied_from_handshake (struct GNUNET_CONNECTION_Handle *cph) +{ + struct GNUNET_CONNECTION_Handle *proxied = GNUNET_CONNECTION_create_from_existing (NULL); + + proxied->proxy_handshake = cph; + return proxied; +} + + +/** + * Activate proxied connection and destroy initial proxy handshake connection. + * There must not be any pending requests for reading or writing to the + * proxy hadshake connection at this time. + * + * @param proxied connection connection to proxy server + */ +void +GNUNET_CONNECTION_acivate_proxied (struct GNUNET_CONNECTION_Handle *proxied) +{ + struct GNUNET_CONNECTION_Handle *cph = proxied->proxy_handshake; + + GNUNET_assert (NULL != cph); + GNUNET_assert (NULL == proxied->sock); + GNUNET_assert (NULL != cph->sock); + proxied->sock = cph->sock; + cph->sock = NULL; + GNUNET_CONNECTION_destroy (cph); + connect_success_continuation (proxied); +} + + +/* end of connection.c */ diff --git a/src/transport/tcp_server_legacy.c b/src/transport/tcp_server_legacy.c new file mode 100644 index 000000000..c055285b1 --- /dev/null +++ b/src/transport/tcp_server_legacy.c @@ -0,0 +1,1748 @@ +/* + This file is part of GNUnet. + Copyright (C) 2009-2013 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 3, 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., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * @file util/server.c + * @brief library for building GNUnet network servers + * @author Christian Grothoff + */ + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_protocols.h" + +#define LOG_STRERROR_FILE(kind,syscall,filename) GNUNET_log_from_strerror_file (kind, "util-server", syscall, filename) + + +/** + * List of arrays of message handlers. + */ +struct HandlerList +{ + /** + * This is a linked list. + */ + struct HandlerList *next; + + /** + * NULL-terminated array of handlers. + */ + const struct GNUNET_SERVER_MessageHandler *handlers; +}; + + +/** + * List of arrays of message handlers. + */ +struct NotifyList +{ + /** + * This is a doubly linked list. + */ + struct NotifyList *next; + + /** + * This is a doubly linked list. + */ + struct NotifyList *prev; + + /** + * Function to call. + */ + GNUNET_SERVER_DisconnectCallback callback; + + /** + * Closure for callback. + */ + void *callback_cls; +}; + + +/** + * @brief handle for a server + */ +struct GNUNET_SERVER_Handle +{ + /** + * List of handlers for incoming messages. + */ + struct HandlerList *handlers; + + /** + * Head of list of our current clients. + */ + struct GNUNET_SERVER_Client *clients_head; + + /** + * Head of list of our current clients. + */ + struct GNUNET_SERVER_Client *clients_tail; + + /** + * Head of linked list of functions to call on disconnects by clients. + */ + struct NotifyList *disconnect_notify_list_head; + + /** + * Tail of linked list of functions to call on disconnects by clients. + */ + struct NotifyList *disconnect_notify_list_tail; + + /** + * Head of linked list of functions to call on connects by clients. + */ + struct NotifyList *connect_notify_list_head; + + /** + * Tail of linked list of functions to call on connects by clients. + */ + struct NotifyList *connect_notify_list_tail; + + /** + * Function to call for access control. + */ + GNUNET_CONNECTION_AccessCheck access_cb; + + /** + * Closure for @e access_cb. + */ + void *access_cb_cls; + + /** + * NULL-terminated array of sockets used to listen for new + * connections. + */ + struct GNUNET_NETWORK_Handle **listen_sockets; + + /** + * After how long should an idle connection time + * out (on write). + */ + struct GNUNET_TIME_Relative idle_timeout; + + /** + * Task scheduled to do the listening. + */ + struct GNUNET_SCHEDULER_Task * listen_task; + + /** + * Alternative function to create a MST instance. + */ + GNUNET_SERVER_MstCreateCallback mst_create; + + /** + * Alternative function to destroy a MST instance. + */ + GNUNET_SERVER_MstDestroyCallback mst_destroy; + + /** + * Alternative function to give data to a MST instance. + */ + GNUNET_SERVER_MstReceiveCallback mst_receive; + + /** + * Closure for 'mst_'-callbacks. + */ + void *mst_cls; + + /** + * Do we ignore messages of types that we do not understand or do we + * require that a handler is found (and if not kill the connection)? + */ + int require_found; + + /** + * Set to #GNUNET_YES once we are in 'soft' shutdown where we wait for + * all non-monitor clients to disconnect before we call + * #GNUNET_SERVER_destroy. See test_monitor_clients(). Set to + * #GNUNET_SYSERR once the final destroy task has been scheduled + * (we cannot run it in the same task). + */ + int in_soft_shutdown; +}; + + +/** + * Handle server returns for aborting transmission to a client. + */ +struct GNUNET_SERVER_TransmitHandle +{ + /** + * Function to call to get the message. + */ + GNUNET_CONNECTION_TransmitReadyNotify callback; + + /** + * Closure for @e callback + */ + void *callback_cls; + + /** + * Active connection transmission handle. + */ + struct GNUNET_CONNECTION_TransmitHandle *cth; + +}; + + +/** + * @brief handle for a client of the server + */ +struct GNUNET_SERVER_Client +{ + + /** + * This is a doubly linked list. + */ + struct GNUNET_SERVER_Client *next; + + /** + * This is a doubly linked list. + */ + struct GNUNET_SERVER_Client *prev; + + /** + * Processing of incoming data. + */ + void *mst; + + /** + * Server that this client belongs to. + */ + struct GNUNET_SERVER_Handle *server; + + /** + * Client closure for callbacks. + */ + struct GNUNET_CONNECTION_Handle *connection; + + /** + * User context value, manipulated using + * 'GNUNET_SERVER_client_{get/set}_user_context' functions. + */ + void *user_context; + + /** + * ID of task used to restart processing. + */ + struct GNUNET_SCHEDULER_Task * restart_task; + + /** + * Task that warns about missing calls to #GNUNET_SERVER_receive_done. + */ + struct GNUNET_SCHEDULER_Task * warn_task; + + /** + * Time when the warn task was started. + */ + struct GNUNET_TIME_Absolute warn_start; + + /** + * Last activity on this socket (used to time it out + * if reference_count == 0). + */ + struct GNUNET_TIME_Absolute last_activity; + + /** + * Transmission handle we return for this client from + * #GNUNET_SERVER_notify_transmit_ready. + */ + struct GNUNET_SERVER_TransmitHandle th; + + /** + * After how long should an idle connection time + * out (on write). + */ + struct GNUNET_TIME_Relative idle_timeout; + + /** + * Number of external entities with a reference to + * this client object. + */ + unsigned int reference_count; + + /** + * Was processing if incoming messages suspended while + * we were still processing data already received? + * This is a counter saying how often processing was + * suspended (once per handler invoked). + */ + unsigned int suspended; + + /** + * Last size given when user context was initialized; used for + * sanity check. + */ + size_t user_context_size; + + /** + * Are we currently in the "process_client_buffer" function (and + * will hence restart the receive job on exit if suspended == 0 once + * we are done?). If this is set, then "receive_done" will + * essentially only decrement suspended; if this is not set, then + * "receive_done" may need to restart the receive process (either + * from the side-buffer or via select/recv). + */ + int in_process_client_buffer; + + /** + * We're about to close down this client. + */ + int shutdown_now; + + /** + * Are we currently trying to receive? (#GNUNET_YES if we are, + * #GNUNET_NO if we are not, #GNUNET_SYSERR if data is already + * available in MST). + */ + int receive_pending; + + /** + * Persist the file handle for this client no matter what happens, + * force the OS to close once the process actually dies. Should only + * be used in special cases! + */ + int persist; + + /** + * Is this client a 'monitor' client that should not be counted + * when deciding on destroying the server during soft shutdown? + * (see also #GNUNET_SERVICE_start) + */ + int is_monitor; + + /** + * Type of last message processed (for warn_no_receive_done). + */ + uint16_t warn_type; +}; + + + +/** + * Return user context associated with the given client. + * Note: you should probably use the macro (call without the underscore). + * + * @param client client to query + * @param size number of bytes in user context struct (for verification only) + * @return pointer to user context + */ +void * +GNUNET_SERVER_client_get_user_context_ (struct GNUNET_SERVER_Client *client, + size_t size) +{ + if ((0 == client->user_context_size) && + (NULL == client->user_context)) + return NULL; /* never set */ + GNUNET_assert (size == client->user_context_size); + return client->user_context; +} + + +/** + * Set user context to be associated with the given client. + * Note: you should probably use the macro (call without the underscore). + * + * @param client client to query + * @param ptr pointer to user context + * @param size number of bytes in user context struct (for verification only) + */ +void +GNUNET_SERVER_client_set_user_context_ (struct GNUNET_SERVER_Client *client, + void *ptr, + size_t size) +{ + if (NULL == ptr) + { + client->user_context_size = 0; + client->user_context = ptr; + return; + } + client->user_context_size = size; + client->user_context = ptr; +} + + +/** + * Scheduler says our listen socket is ready. Process it! + * + * @param cls handle to our server for which we are processing the listen + * socket + */ +static void +process_listen_socket (void *cls) +{ + struct GNUNET_SERVER_Handle *server = cls; + const struct GNUNET_SCHEDULER_TaskContext *tc; + struct GNUNET_CONNECTION_Handle *sock; + unsigned int i; + + server->listen_task = NULL; + tc = GNUNET_SCHEDULER_get_task_context (); + for (i = 0; NULL != server->listen_sockets[i]; i++) + { + if (GNUNET_NETWORK_fdset_isset (tc->read_ready, + server->listen_sockets[i])) + { + sock = + GNUNET_CONNECTION_create_from_accept (server->access_cb, + server->access_cb_cls, + server->listen_sockets[i]); + if (NULL != sock) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Server accepted incoming connection.\n"); + (void) GNUNET_SERVER_connect_socket (server, + sock); + } + } + } + /* listen for more! */ + GNUNET_SERVER_resume (server); +} + + +/** + * Create and initialize a listen socket for the server. + * + * @param server_addr address to listen on + * @param socklen length of @a server_addr + * @return NULL on error, otherwise the listen socket + */ +static struct GNUNET_NETWORK_Handle * +open_listen_socket (const struct sockaddr *server_addr, + socklen_t socklen) +{ + struct GNUNET_NETWORK_Handle *sock; + uint16_t port; + int eno; + + switch (server_addr->sa_family) + { + case AF_INET: + port = ntohs (((const struct sockaddr_in *) server_addr)->sin_port); + break; + case AF_INET6: + port = ntohs (((const struct sockaddr_in6 *) server_addr)->sin6_port); + break; + case AF_UNIX: + port = 0; + break; + default: + GNUNET_break (0); + port = 0; + break; + } + sock = GNUNET_NETWORK_socket_create (server_addr->sa_family, SOCK_STREAM, 0); + if (NULL == sock) + { + LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "socket"); + errno = 0; + return NULL; + } + /* bind the socket */ + if (GNUNET_OK != GNUNET_NETWORK_socket_bind (sock, server_addr, socklen)) + { + eno = errno; + if (EADDRINUSE != errno) + { + /* we don't log 'EADDRINUSE' here since an IPv4 bind may + * fail if we already took the port on IPv6; if both IPv4 and + * IPv6 binds fail, then our caller will log using the + * errno preserved in 'eno' */ + LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, + "bind"); + if (0 != port) + LOG (GNUNET_ERROR_TYPE_ERROR, + _("`%s' failed for port %d (%s).\n"), + "bind", + port, + (AF_INET == server_addr->sa_family) ? "IPv4" : "IPv6"); + eno = 0; + } + else + { + if (0 != port) + LOG (GNUNET_ERROR_TYPE_WARNING, + _("`%s' failed for port %d (%s): address already in use\n"), + "bind", port, + (AF_INET == server_addr->sa_family) ? "IPv4" : "IPv6"); + else if (AF_UNIX == server_addr->sa_family) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + _("`%s' failed for `%s': address already in use\n"), + "bind", + GNUNET_a2s (server_addr, socklen)); + } + } + GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (sock)); + errno = eno; + return NULL; + } + if (GNUNET_OK != GNUNET_NETWORK_socket_listen (sock, 5)) + { + LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, + "listen"); + GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (sock)); + errno = 0; + return NULL; + } + if (0 != port) + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Server starts to listen on port %u.\n", + port); + return sock; +} + + +/** + * Create a new server. + * + * @param access_cb function for access control + * @param access_cb_cls closure for @a access_cb + * @param lsocks NULL-terminated array of listen sockets + * @param idle_timeout after how long should we timeout idle connections? + * @param require_found if #GNUNET_YES, connections sending messages of unknown type + * will be closed + * @return handle for the new server, NULL on error + * (typically, "port" already in use) + */ +struct GNUNET_SERVER_Handle * +GNUNET_SERVER_create_with_sockets (GNUNET_CONNECTION_AccessCheck access_cb, + void *access_cb_cls, + struct GNUNET_NETWORK_Handle **lsocks, + struct GNUNET_TIME_Relative idle_timeout, + int require_found) +{ + struct GNUNET_SERVER_Handle *server; + + server = GNUNET_new (struct GNUNET_SERVER_Handle); + server->idle_timeout = idle_timeout; + server->listen_sockets = lsocks; + server->access_cb = access_cb; + server->access_cb_cls = access_cb_cls; + server->require_found = require_found; + if (NULL != lsocks) + GNUNET_SERVER_resume (server); + return server; +} + + +/** + * Create a new server. + * + * @param access_cb function for access control + * @param access_cb_cls closure for @a access_cb + * @param server_addr address to listen on (including port), NULL terminated array + * @param socklen length of server_addr + * @param idle_timeout after how long should we timeout idle connections? + * @param require_found if YES, connections sending messages of unknown type + * will be closed + * @return handle for the new server, NULL on error + * (typically, "port" already in use) + */ +struct GNUNET_SERVER_Handle * +GNUNET_SERVER_create (GNUNET_CONNECTION_AccessCheck access_cb, + void *access_cb_cls, + struct sockaddr *const *server_addr, + const socklen_t * socklen, + struct GNUNET_TIME_Relative idle_timeout, + int require_found) +{ + struct GNUNET_NETWORK_Handle **lsocks; + unsigned int i; + unsigned int j; + unsigned int k; + int seen; + + i = 0; + while (NULL != server_addr[i]) + i++; + if (i > 0) + { + lsocks = GNUNET_malloc (sizeof (struct GNUNET_NETWORK_Handle *) * (i + 1)); + i = 0; + j = 0; + while (NULL != server_addr[i]) + { + seen = 0; + for (k=0;kis_monitor = GNUNET_YES; +} + + +/** + * Helper function for #test_monitor_clients() to trigger + * #GNUNET_SERVER_destroy() after the stack has unwound. + * + * @param cls the `struct GNUNET_SERVER_Handle *` to destroy + */ +static void +do_destroy (void *cls) +{ + struct GNUNET_SERVER_Handle *server = cls; + + GNUNET_SERVER_destroy (server); +} + + +/** + * Check if only 'monitor' clients are left. If so, destroy the + * server completely. + * + * @param server server to test for full shutdown + */ +static void +test_monitor_clients (struct GNUNET_SERVER_Handle *server) +{ + struct GNUNET_SERVER_Client *client; + + if (GNUNET_YES != server->in_soft_shutdown) + return; + for (client = server->clients_head; NULL != client; client = client->next) + if (GNUNET_NO == client->is_monitor) + return; /* not done yet */ + server->in_soft_shutdown = GNUNET_SYSERR; + (void) GNUNET_SCHEDULER_add_now (&do_destroy, server); +} + + +/** + * Suspend accepting connections from the listen socket temporarily. + * + * @param server server to stop accepting connections. + */ +void +GNUNET_SERVER_suspend (struct GNUNET_SERVER_Handle *server) +{ + if (NULL != server->listen_task) + { + GNUNET_SCHEDULER_cancel (server->listen_task); + server->listen_task = NULL; + } +} + + +/** + * Resume accepting connections from the listen socket. + * + * @param server server to stop accepting connections. + */ +void +GNUNET_SERVER_resume (struct GNUNET_SERVER_Handle *server) +{ + struct GNUNET_NETWORK_FDSet *r; + unsigned int i; + + if (NULL == server->listen_sockets) + return; + if (NULL == server->listen_sockets[0]) + return; /* nothing to do, no listen sockets! */ + if (NULL == server->listen_sockets[1]) + { + /* simplified method: no fd set needed; this is then much simpler + and much more efficient */ + server->listen_task = + GNUNET_SCHEDULER_add_read_net_with_priority (GNUNET_TIME_UNIT_FOREVER_REL, + GNUNET_SCHEDULER_PRIORITY_HIGH, + server->listen_sockets[0], + &process_listen_socket, server); + return; + } + r = GNUNET_NETWORK_fdset_create (); + i = 0; + while (NULL != server->listen_sockets[i]) + GNUNET_NETWORK_fdset_set (r, server->listen_sockets[i++]); + server->listen_task = + GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH, + GNUNET_TIME_UNIT_FOREVER_REL, r, NULL, + &process_listen_socket, server); + GNUNET_NETWORK_fdset_destroy (r); +} + + +/** + * Stop the listen socket and get ready to shutdown the server + * once only 'monitor' clients are left. + * + * @param server server to stop listening on + */ +void +GNUNET_SERVER_stop_listening (struct GNUNET_SERVER_Handle *server) +{ + unsigned int i; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Server in soft shutdown\n"); + if (NULL != server->listen_task) + { + GNUNET_SCHEDULER_cancel (server->listen_task); + server->listen_task = NULL; + } + if (NULL != server->listen_sockets) + { + i = 0; + while (NULL != server->listen_sockets[i]) + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (server->listen_sockets[i++])); + GNUNET_free (server->listen_sockets); + server->listen_sockets = NULL; + } + if (GNUNET_NO == server->in_soft_shutdown) + server->in_soft_shutdown = GNUNET_YES; + test_monitor_clients (server); +} + + +/** + * Free resources held by this server. + * + * @param server server to destroy + */ +void +GNUNET_SERVER_destroy (struct GNUNET_SERVER_Handle *server) +{ + struct HandlerList *hpos; + struct NotifyList *npos; + unsigned int i; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Server shutting down.\n"); + if (NULL != server->listen_task) + { + GNUNET_SCHEDULER_cancel (server->listen_task); + server->listen_task = NULL; + } + if (NULL != server->listen_sockets) + { + i = 0; + while (NULL != server->listen_sockets[i]) + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (server->listen_sockets[i++])); + GNUNET_free (server->listen_sockets); + server->listen_sockets = NULL; + } + while (NULL != server->clients_head) + GNUNET_SERVER_client_disconnect (server->clients_head); + while (NULL != (hpos = server->handlers)) + { + server->handlers = hpos->next; + GNUNET_free (hpos); + } + while (NULL != (npos = server->disconnect_notify_list_head)) + { + npos->callback (npos->callback_cls, + NULL); + GNUNET_CONTAINER_DLL_remove (server->disconnect_notify_list_head, + server->disconnect_notify_list_tail, + npos); + GNUNET_free (npos); + } + while (NULL != (npos = server->connect_notify_list_head)) + { + npos->callback (npos->callback_cls, + NULL); + GNUNET_CONTAINER_DLL_remove (server->connect_notify_list_head, + server->connect_notify_list_tail, + npos); + GNUNET_free (npos); + } + GNUNET_free (server); +} + + +/** + * Add additional handlers to an existing server. + * + * @param server the server to add handlers to + * @param handlers array of message handlers for + * incoming messages; the last entry must + * have "NULL" for the "callback"; multiple + * entries for the same type are allowed, + * they will be called in order of occurence. + * These handlers can be removed later; + * the handlers array must exist until removed + * (or server is destroyed). + */ +void +GNUNET_SERVER_add_handlers (struct GNUNET_SERVER_Handle *server, + const struct GNUNET_SERVER_MessageHandler *handlers) +{ + struct HandlerList *p; + + p = GNUNET_new (struct HandlerList); + p->handlers = handlers; + p->next = server->handlers; + server->handlers = p; +} + + +/** + * Change functions used by the server to tokenize the message stream. + * (very rarely used). + * + * @param server server to modify + * @param create new tokenizer initialization function + * @param destroy new tokenizer destruction function + * @param receive new tokenizer receive function + * @param cls closure for @a create, @a receive, @a destroy + */ +void +GNUNET_SERVER_set_callbacks (struct GNUNET_SERVER_Handle *server, + GNUNET_SERVER_MstCreateCallback create, + GNUNET_SERVER_MstDestroyCallback destroy, + GNUNET_SERVER_MstReceiveCallback receive, + void *cls) +{ + server->mst_create = create; + server->mst_destroy = destroy; + server->mst_receive = receive; + server->mst_cls = cls; +} + + +/** + * Task run to warn about missing calls to #GNUNET_SERVER_receive_done. + * + * @param cls our `struct GNUNET_SERVER_Client *` to process more requests from + */ +static void +warn_no_receive_done (void *cls) +{ + struct GNUNET_SERVER_Client *client = cls; + + GNUNET_break (0 != client->warn_type); /* type should never be 0 here, as we don't use 0 */ + client->warn_task = + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES, + &warn_no_receive_done, client); + LOG (GNUNET_ERROR_TYPE_WARNING, + _("Processing code for message of type %u did not call `GNUNET_SERVER_receive_done' after %s\n"), + (unsigned int) client->warn_type, + GNUNET_STRINGS_relative_time_to_string (GNUNET_TIME_absolute_get_duration (client->warn_start), + GNUNET_YES)); +} + + +/** + * Disable the warning the server issues if a message is not acknowledged + * in a timely fashion. Use this call if a client is intentionally delayed + * for a while. Only applies to the current message. + * + * @param client client for which to disable the warning + */ +void +GNUNET_SERVER_disable_receive_done_warning (struct GNUNET_SERVER_Client *client) +{ + if (NULL != client->warn_task) + { + GNUNET_SCHEDULER_cancel (client->warn_task); + client->warn_task = NULL; + } +} + + +/** + * Inject a message into the server, pretend it came + * from the specified client. Delivery of the message + * will happen instantly (if a handler is installed; + * otherwise the call does nothing). + * + * @param server the server receiving the message + * @param sender the "pretended" sender of the message + * can be NULL! + * @param message message to transmit + * @return #GNUNET_OK if the message was OK and the + * connection can stay open + * #GNUNET_SYSERR if the connection to the + * client should be shut down + */ +int +GNUNET_SERVER_inject (struct GNUNET_SERVER_Handle *server, + struct GNUNET_SERVER_Client *sender, + const struct GNUNET_MessageHeader *message) +{ + struct HandlerList *pos; + const struct GNUNET_SERVER_MessageHandler *mh; + unsigned int i; + uint16_t type; + uint16_t size; + int found; + + type = ntohs (message->type); + size = ntohs (message->size); + LOG (GNUNET_ERROR_TYPE_INFO, + "Received message of type %u and size %u from client\n", + type, size); + found = GNUNET_NO; + for (pos = server->handlers; NULL != pos; pos = pos->next) + { + i = 0; + while (pos->handlers[i].callback != NULL) + { + mh = &pos->handlers[i]; + if ((mh->type == type) || (mh->type == GNUNET_MESSAGE_TYPE_ALL)) + { + if ((0 != mh->expected_size) && (mh->expected_size != size)) + { +#if GNUNET8_NETWORK_IS_DEAD + LOG (GNUNET_ERROR_TYPE_WARNING, + "Expected %u bytes for message of type %u, got %u\n", + mh->expected_size, mh->type, size); + GNUNET_break_op (0); +#else + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Expected %u bytes for message of type %u, got %u\n", + mh->expected_size, mh->type, size); +#endif + return GNUNET_SYSERR; + } + if (NULL != sender) + { + if ( (0 == sender->suspended) && + (NULL == sender->warn_task) ) + { + GNUNET_break (0 != type); /* type should never be 0 here, as we don't use 0 */ + sender->warn_start = GNUNET_TIME_absolute_get (); + sender->warn_task = + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES, + &warn_no_receive_done, + sender); + sender->warn_type = type; + } + sender->suspended++; + } + mh->callback (mh->callback_cls, sender, message); + found = GNUNET_YES; + } + i++; + } + } + if (GNUNET_NO == found) + { + LOG (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK, + "Received message of unknown type %d\n", type); + if (GNUNET_YES == server->require_found) + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * We are receiving an incoming message. Process it. + * + * @param cls our closure (handle for the client) + * @param buf buffer with data received from network + * @param available number of bytes available in buf + * @param addr address of the sender + * @param addrlen length of @a addr + * @param errCode code indicating errors receiving, 0 for success + */ +static void +process_incoming (void *cls, + const void *buf, + size_t available, + const struct sockaddr *addr, + socklen_t addrlen, + int errCode); + + +/** + * Process messages from the client's message tokenizer until either + * the tokenizer is empty (and then schedule receiving more), or + * until some handler is not immediately done (then wait for restart_processing) + * or shutdown. + * + * @param client the client to process, RC must have already been increased + * using #GNUNET_SERVER_client_keep and will be decreased by one in this + * function + * @param ret #GNUNET_NO to start processing from the buffer, + * #GNUNET_OK if the mst buffer is drained and we should instantly go back to receiving + * #GNUNET_SYSERR if we should instantly abort due to error in a previous step + */ +static void +process_mst (struct GNUNET_SERVER_Client *client, + int ret) +{ + while ((GNUNET_SYSERR != ret) && (NULL != client->server) && + (GNUNET_YES != client->shutdown_now) && (0 == client->suspended)) + { + if (GNUNET_OK == ret) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Server re-enters receive loop, timeout: %s.\n", + GNUNET_STRINGS_relative_time_to_string (client->idle_timeout, GNUNET_YES)); + client->receive_pending = GNUNET_YES; + GNUNET_CONNECTION_receive (client->connection, + GNUNET_SERVER_MAX_MESSAGE_SIZE - 1, + client->idle_timeout, + &process_incoming, + client); + break; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Server processes additional messages instantly.\n"); + if (NULL != client->server->mst_receive) + ret = + client->server->mst_receive (client->server->mst_cls, client->mst, + client, NULL, 0, GNUNET_NO, GNUNET_YES); + else + ret = + GNUNET_SERVER_mst_receive (client->mst, client, NULL, 0, GNUNET_NO, + GNUNET_YES); + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Server leaves instant processing loop: ret = %d, server = %p, shutdown = %d, suspended = %u\n", + ret, client->server, + client->shutdown_now, + client->suspended); + if (GNUNET_NO == ret) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Server has more data pending but is suspended.\n"); + client->receive_pending = GNUNET_SYSERR; /* data pending */ + } + if ( (GNUNET_SYSERR == ret) || + (GNUNET_YES == client->shutdown_now) ) + GNUNET_SERVER_client_disconnect (client); +} + + +/** + * We are receiving an incoming message. Process it. + * + * @param cls our closure (handle for the client) + * @param buf buffer with data received from network + * @param available number of bytes available in buf + * @param addr address of the sender + * @param addrlen length of @a addr + * @param errCode code indicating errors receiving, 0 for success + */ +static void +process_incoming (void *cls, + const void *buf, + size_t available, + const struct sockaddr *addr, + socklen_t addrlen, + int errCode) +{ + struct GNUNET_SERVER_Client *client = cls; + struct GNUNET_SERVER_Handle *server = client->server; + struct GNUNET_TIME_Absolute end; + struct GNUNET_TIME_Absolute now; + int ret; + + GNUNET_assert (GNUNET_YES == client->receive_pending); + client->receive_pending = GNUNET_NO; + now = GNUNET_TIME_absolute_get (); + end = GNUNET_TIME_absolute_add (client->last_activity, + client->idle_timeout); + + if ( (NULL == buf) && + (0 == available) && + (NULL == addr) && + (0 == errCode) && + (GNUNET_YES != client->shutdown_now) && + (NULL != server) && + (GNUNET_YES == GNUNET_CONNECTION_check (client->connection)) && + (end.abs_value_us > now.abs_value_us) ) + { + /* wait longer, timeout changed (i.e. due to us sending) */ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Receive time out, but no disconnect due to sending (%p)\n", + client); + client->receive_pending = GNUNET_YES; + GNUNET_CONNECTION_receive (client->connection, + GNUNET_SERVER_MAX_MESSAGE_SIZE - 1, + GNUNET_TIME_absolute_get_remaining (end), + &process_incoming, + client); + return; + } + if ( (NULL == buf) || + (0 == available) || + (0 != errCode) || + (NULL == server) || + (GNUNET_YES == client->shutdown_now) || + (GNUNET_YES != GNUNET_CONNECTION_check (client->connection)) ) + { + /* other side closed connection, error connecting, etc. */ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Failed to connect or other side closed connection (%p)\n", + client); + GNUNET_SERVER_client_disconnect (client); + return; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Server receives %u bytes from `%s'.\n", + (unsigned int) available, + GNUNET_a2s (addr, addrlen)); + GNUNET_SERVER_client_keep (client); + client->last_activity = now; + + if (NULL != server->mst_receive) + { + ret = client->server->mst_receive (client->server->mst_cls, + client->mst, + client, + buf, + available, + GNUNET_NO, + GNUNET_YES); + } + else if (NULL != client->mst) + { + ret = + GNUNET_SERVER_mst_receive (client->mst, + client, + buf, + available, + GNUNET_NO, + GNUNET_YES); + } + else + { + GNUNET_break (0); + return; + } + process_mst (client, + ret); + GNUNET_SERVER_client_drop (client); +} + + +/** + * Task run to start again receiving from the network + * and process requests. + * + * @param cls our `struct GNUNET_SERVER_Client *` to process more requests from + */ +static void +restart_processing (void *cls) +{ + struct GNUNET_SERVER_Client *client = cls; + + GNUNET_assert (GNUNET_YES != client->shutdown_now); + client->restart_task = NULL; + if (GNUNET_NO == client->receive_pending) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, "Server begins to read again from client.\n"); + client->receive_pending = GNUNET_YES; + GNUNET_CONNECTION_receive (client->connection, + GNUNET_SERVER_MAX_MESSAGE_SIZE - 1, + client->idle_timeout, + &process_incoming, + client); + return; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Server continues processing messages still in the buffer.\n"); + GNUNET_SERVER_client_keep (client); + client->receive_pending = GNUNET_NO; + process_mst (client, + GNUNET_NO); + GNUNET_SERVER_client_drop (client); +} + + +/** + * This function is called whenever our inbound message tokenizer has + * received a complete message. + * + * @param cls closure (struct GNUNET_SERVER_Handle) + * @param client identification of the client (`struct GNUNET_SERVER_Client *`) + * @param message the actual message + * + * @return #GNUNET_OK on success, #GNUNET_SYSERR to stop further processing + */ +static int +client_message_tokenizer_callback (void *cls, + void *client, + const struct GNUNET_MessageHeader *message) +{ + struct GNUNET_SERVER_Handle *server = cls; + struct GNUNET_SERVER_Client *sender = client; + int ret; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Tokenizer gives server message of type %u and size %u from client\n", + ntohs (message->type), ntohs (message->size)); + sender->in_process_client_buffer = GNUNET_YES; + ret = GNUNET_SERVER_inject (server, sender, message); + sender->in_process_client_buffer = GNUNET_NO; + if ( (GNUNET_OK != ret) || (GNUNET_YES == sender->shutdown_now) ) + { + GNUNET_SERVER_client_disconnect (sender); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Add a TCP socket-based connection to the set of handles managed by + * this server. Use this function for outgoing (P2P) connections that + * we initiated (and where this server should process incoming + * messages). + * + * @param server the server to use + * @param connection the connection to manage (client must + * stop using this connection from now on) + * @return the client handle + */ +struct GNUNET_SERVER_Client * +GNUNET_SERVER_connect_socket (struct GNUNET_SERVER_Handle *server, + struct GNUNET_CONNECTION_Handle *connection) +{ + struct GNUNET_SERVER_Client *client; + struct NotifyList *n; + + client = GNUNET_new (struct GNUNET_SERVER_Client); + client->connection = connection; + client->server = server; + client->last_activity = GNUNET_TIME_absolute_get (); + client->idle_timeout = server->idle_timeout; + GNUNET_CONTAINER_DLL_insert (server->clients_head, + server->clients_tail, + client); + if (NULL != server->mst_create) + client->mst = + server->mst_create (server->mst_cls, client); + else + client->mst = + GNUNET_SERVER_mst_create (&client_message_tokenizer_callback, + server); + GNUNET_assert (NULL != client->mst); + for (n = server->connect_notify_list_head; NULL != n; n = n->next) + n->callback (n->callback_cls, client); + client->receive_pending = GNUNET_YES; + GNUNET_CONNECTION_receive (client->connection, + GNUNET_SERVER_MAX_MESSAGE_SIZE - 1, + client->idle_timeout, + &process_incoming, + client); + return client; +} + + +/** + * Change the timeout for a particular client. Decreasing the timeout + * may not go into effect immediately (only after the previous timeout + * times out or activity happens on the socket). + * + * @param client the client to update + * @param timeout new timeout for activities on the socket + */ +void +GNUNET_SERVER_client_set_timeout (struct GNUNET_SERVER_Client *client, + struct GNUNET_TIME_Relative timeout) +{ + client->idle_timeout = timeout; +} + + +/** + * Notify the server that the given client handle should + * be kept (keeps the connection up if possible, increments + * the internal reference counter). + * + * @param client the client to keep + */ +void +GNUNET_SERVER_client_keep (struct GNUNET_SERVER_Client *client) +{ + client->reference_count++; +} + + +/** + * Notify the server that the given client handle is no + * longer required. Decrements the reference counter. If + * that counter reaches zero an inactive connection maybe + * closed. + * + * @param client the client to drop + */ +void +GNUNET_SERVER_client_drop (struct GNUNET_SERVER_Client *client) +{ + GNUNET_assert (client->reference_count > 0); + client->reference_count--; + if ((GNUNET_YES == client->shutdown_now) && (0 == client->reference_count)) + GNUNET_SERVER_client_disconnect (client); +} + + +/** + * Obtain the network address of the other party. + * + * @param client the client to get the address for + * @param addr where to store the address + * @param addrlen where to store the length of the @a addr + * @return #GNUNET_OK on success + */ +int +GNUNET_SERVER_client_get_address (struct GNUNET_SERVER_Client *client, + void **addr, size_t * addrlen) +{ + return GNUNET_CONNECTION_get_address (client->connection, addr, addrlen); +} + + +/** + * Ask the server to notify us whenever a client disconnects. + * This function is called whenever the actual network connection + * is closed; the reference count may be zero or larger than zero + * at this point. + * + * @param server the server manageing the clients + * @param callback function to call on disconnect + * @param callback_cls closure for @a callback + */ +void +GNUNET_SERVER_disconnect_notify (struct GNUNET_SERVER_Handle *server, + GNUNET_SERVER_DisconnectCallback callback, + void *callback_cls) +{ + struct NotifyList *n; + + n = GNUNET_new (struct NotifyList); + n->callback = callback; + n->callback_cls = callback_cls; + GNUNET_CONTAINER_DLL_insert (server->disconnect_notify_list_head, + server->disconnect_notify_list_tail, + n); +} + + +/** + * Ask the server to notify us whenever a client connects. + * This function is called whenever the actual network connection + * is opened. If the server is destroyed before this + * notification is explicitly cancelled, the 'callback' will + * once be called with a 'client' argument of NULL to indicate + * that the server itself is now gone (and that the callback + * won't be called anymore and also can no longer be cancelled). + * + * @param server the server manageing the clients + * @param callback function to call on sconnect + * @param callback_cls closure for @a callback + */ +void +GNUNET_SERVER_connect_notify (struct GNUNET_SERVER_Handle *server, + GNUNET_SERVER_ConnectCallback callback, + void *callback_cls) +{ + struct NotifyList *n; + struct GNUNET_SERVER_Client *client; + + n = GNUNET_new (struct NotifyList); + n->callback = callback; + n->callback_cls = callback_cls; + GNUNET_CONTAINER_DLL_insert (server->connect_notify_list_head, + server->connect_notify_list_tail, + n); + for (client = server->clients_head; NULL != client; client = client->next) + callback (callback_cls, client); +} + + +/** + * Ask the server to stop notifying us whenever a client connects. + * + * @param server the server manageing the clients + * @param callback function to call on connect + * @param callback_cls closure for @a callback + */ +void +GNUNET_SERVER_disconnect_notify_cancel (struct GNUNET_SERVER_Handle *server, + GNUNET_SERVER_DisconnectCallback callback, + void *callback_cls) +{ + struct NotifyList *pos; + + for (pos = server->disconnect_notify_list_head; NULL != pos; pos = pos->next) + if ((pos->callback == callback) && (pos->callback_cls == callback_cls)) + break; + if (NULL == pos) + { + GNUNET_break (0); + return; + } + GNUNET_CONTAINER_DLL_remove (server->disconnect_notify_list_head, + server->disconnect_notify_list_tail, + pos); + GNUNET_free (pos); +} + + +/** + * Ask the server to stop notifying us whenever a client disconnects. + * + * @param server the server manageing the clients + * @param callback function to call on disconnect + * @param callback_cls closure for @a callback + */ +void +GNUNET_SERVER_connect_notify_cancel (struct GNUNET_SERVER_Handle *server, + GNUNET_SERVER_ConnectCallback callback, + void *callback_cls) +{ + struct NotifyList *pos; + + for (pos = server->connect_notify_list_head; NULL != pos; pos = pos->next) + if ((pos->callback == callback) && (pos->callback_cls == callback_cls)) + break; + if (NULL == pos) + { + GNUNET_break (0); + return; + } + GNUNET_CONTAINER_DLL_remove (server->connect_notify_list_head, + server->connect_notify_list_tail, + pos); + GNUNET_free (pos); +} + + +/** + * Destroy the connection that is passed in via @a cls. Used + * as calling #GNUNET_CONNECTION_destroy from within a function + * that was itself called from within process_notify() of + * 'connection.c' is not allowed (see #2329). + * + * @param cls connection to destroy + */ +static void +destroy_connection (void *cls) +{ + struct GNUNET_CONNECTION_Handle *connection = cls; + + GNUNET_CONNECTION_destroy (connection); +} + + +/** + * Ask the server to disconnect from the given client. + * This is the same as returning #GNUNET_SYSERR from a message + * handler, except that it allows dropping of a client even + * when not handling a message from that client. + * + * @param client the client to disconnect from + */ +void +GNUNET_SERVER_client_disconnect (struct GNUNET_SERVER_Client *client) +{ + struct GNUNET_SERVER_Handle *server = client->server; + struct NotifyList *n; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Client is being disconnected from the server.\n"); + if (NULL != client->restart_task) + { + GNUNET_SCHEDULER_cancel (client->restart_task); + client->restart_task = NULL; + } + if (NULL != client->warn_task) + { + GNUNET_SCHEDULER_cancel (client->warn_task); + client->warn_task = NULL; + } + if (GNUNET_YES == client->receive_pending) + { + GNUNET_CONNECTION_receive_cancel (client->connection); + client->receive_pending = GNUNET_NO; + } + client->shutdown_now = GNUNET_YES; + client->reference_count++; /* make sure nobody else clean up client... */ + if ( (NULL != client->mst) && + (NULL != server) ) + { + GNUNET_CONTAINER_DLL_remove (server->clients_head, + server->clients_tail, + client); + if (NULL != server->mst_destroy) + server->mst_destroy (server->mst_cls, + client->mst); + else + GNUNET_SERVER_mst_destroy (client->mst); + client->mst = NULL; + for (n = server->disconnect_notify_list_head; NULL != n; n = n->next) + n->callback (n->callback_cls, + client); + } + client->reference_count--; + if (client->reference_count > 0) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "RC of %p still positive, not destroying everything.\n", + client); + client->server = NULL; + return; + } + if (GNUNET_YES == client->in_process_client_buffer) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Still processing inputs of %p, not destroying everything.\n", + client); + return; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "RC of %p now zero, destroying everything.\n", + client); + if (GNUNET_YES == client->persist) + GNUNET_CONNECTION_persist_ (client->connection); + if (NULL != client->th.cth) + GNUNET_SERVER_notify_transmit_ready_cancel (&client->th); + (void) GNUNET_SCHEDULER_add_now (&destroy_connection, + client->connection); + /* need to cancel again, as it might have been re-added + in the meantime (i.e. during callbacks) */ + if (NULL != client->warn_task) + { + GNUNET_SCHEDULER_cancel (client->warn_task); + client->warn_task = NULL; + } + if (GNUNET_YES == client->receive_pending) + { + GNUNET_CONNECTION_receive_cancel (client->connection); + client->receive_pending = GNUNET_NO; + } + GNUNET_free (client); + /* we might be in soft-shutdown, test if we're done */ + if (NULL != server) + test_monitor_clients (server); +} + + +/** + * Disable the "CORK" feature for communication with the given client, + * forcing the OS to immediately flush the buffer on transmission + * instead of potentially buffering multiple messages. + * + * @param client handle to the client + * @return #GNUNET_OK on success + */ +int +GNUNET_SERVER_client_disable_corking (struct GNUNET_SERVER_Client *client) +{ + return GNUNET_CONNECTION_disable_corking (client->connection); +} + + +/** + * Wrapper for transmission notification that calls the original + * callback and update the last activity time for our connection. + * + * @param cls the `struct GNUNET_SERVER_Client *` + * @param size number of bytes we can transmit + * @param buf where to copy the message + * @return number of bytes actually transmitted + */ +static size_t +transmit_ready_callback_wrapper (void *cls, size_t size, void *buf) +{ + struct GNUNET_SERVER_Client *client = cls; + GNUNET_CONNECTION_TransmitReadyNotify callback; + + client->th.cth = NULL; + callback = client->th.callback; + client->th.callback = NULL; + client->last_activity = GNUNET_TIME_absolute_get (); + return callback (client->th.callback_cls, size, buf); +} + + +/** + * Notify us when the server has enough space to transmit + * a message of the given size to the given client. + * + * @param client client to transmit message to + * @param size requested amount of buffer space + * @param timeout after how long should we give up (and call + * notify with buf NULL and size 0)? + * @param callback function to call when space is available + * @param callback_cls closure for @a callback + * @return non-NULL if the notify callback was queued; can be used + * to cancel the request using + * #GNUNET_SERVER_notify_transmit_ready_cancel(). + * NULL if we are already going to notify someone else (busy) + */ +struct GNUNET_SERVER_TransmitHandle * +GNUNET_SERVER_notify_transmit_ready (struct GNUNET_SERVER_Client *client, + size_t size, + struct GNUNET_TIME_Relative timeout, + GNUNET_CONNECTION_TransmitReadyNotify callback, + void *callback_cls) +{ + if (NULL != client->th.callback) + return NULL; + client->th.callback_cls = callback_cls; + client->th.callback = callback; + client->th.cth = GNUNET_CONNECTION_notify_transmit_ready (client->connection, size, + timeout, + &transmit_ready_callback_wrapper, + client); + return &client->th; +} + + +/** + * Abort transmission request. + * + * @param th request to abort + */ +void +GNUNET_SERVER_notify_transmit_ready_cancel (struct GNUNET_SERVER_TransmitHandle *th) +{ + GNUNET_CONNECTION_notify_transmit_ready_cancel (th->cth); + th->cth = NULL; + th->callback = NULL; +} + + +/** + * Set the persistent flag on this client, used to setup client connection + * to only be killed when the service it's connected to is actually dead. + * + * @param client the client to set the persistent flag on + */ +void +GNUNET_SERVER_client_persist_ (struct GNUNET_SERVER_Client *client) +{ + client->persist = GNUNET_YES; +} + + +/** + * Resume receiving from this client, we are done processing the + * current request. This function must be called from within each + * GNUNET_SERVER_MessageCallback (or its respective continuations). + * + * @param client client we were processing a message of + * @param success #GNUNET_OK to keep the connection open and + * continue to receive + * #GNUNET_NO to close the connection (normal behavior) + * #GNUNET_SYSERR to close the connection (signal + * serious error) + */ +void +GNUNET_SERVER_receive_done (struct GNUNET_SERVER_Client *client, + int success) +{ + if (NULL == client) + return; + GNUNET_assert (client->suspended > 0); + client->suspended--; + if (GNUNET_OK != success) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "GNUNET_SERVER_receive_done called with failure indication\n"); + if ( (client->reference_count > 0) || (client->suspended > 0) ) + client->shutdown_now = GNUNET_YES; + else + GNUNET_SERVER_client_disconnect (client); + return; + } + if (client->suspended > 0) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "GNUNET_SERVER_receive_done called, but more clients pending\n"); + return; + } + if (NULL != client->warn_task) + { + GNUNET_SCHEDULER_cancel (client->warn_task); + client->warn_task = NULL; + } + if (GNUNET_YES == client->in_process_client_buffer) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "GNUNET_SERVER_receive_done called while still in processing loop\n"); + return; + } + if ((NULL == client->server) || (GNUNET_YES == client->shutdown_now)) + { + GNUNET_SERVER_client_disconnect (client); + return; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "GNUNET_SERVER_receive_done causes restart in reading from the socket\n"); + GNUNET_assert (NULL == client->restart_task); + client->restart_task = GNUNET_SCHEDULER_add_now (&restart_processing, + client); +} + + +/* end of server.c */ diff --git a/src/transport/tcp_server_mst_legacy.c b/src/transport/tcp_server_mst_legacy.c new file mode 100644 index 000000000..ba42b1837 --- /dev/null +++ b/src/transport/tcp_server_mst_legacy.c @@ -0,0 +1,311 @@ +/* + This file is part of GNUnet. + Copyright (C) 2010 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 3, 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., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * @file util/server_mst.c + * @brief convenience functions for handling inbound message buffers + * @author Christian Grothoff + */ + +#include "platform.h" +#include "gnunet_util_lib.h" + + +#if HAVE_UNALIGNED_64_ACCESS +#define ALIGN_FACTOR 4 +#else +#define ALIGN_FACTOR 8 +#endif + + +/** + * Handle to a message stream tokenizer. + */ +struct GNUNET_SERVER_MessageStreamTokenizer +{ + + /** + * Function to call on completed messages. + */ + GNUNET_SERVER_MessageTokenizerCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Size of the buffer (starting at @e hdr). + */ + size_t curr_buf; + + /** + * How many bytes in buffer have we already processed? + */ + size_t off; + + /** + * How many bytes in buffer are valid right now? + */ + size_t pos; + + /** + * Beginning of the buffer. Typed like this to force alignment. + */ + struct GNUNET_MessageHeader *hdr; + +}; + + + +/** + * Create a message stream tokenizer. + * + * @param cb function to call on completed messages + * @param cb_cls closure for @a cb + * @return handle to tokenizer + */ +struct GNUNET_SERVER_MessageStreamTokenizer * +GNUNET_SERVER_mst_create (GNUNET_SERVER_MessageTokenizerCallback cb, + void *cb_cls) +{ + struct GNUNET_SERVER_MessageStreamTokenizer *ret; + + ret = GNUNET_new (struct GNUNET_SERVER_MessageStreamTokenizer); + ret->hdr = GNUNET_malloc (GNUNET_SERVER_MIN_BUFFER_SIZE); + ret->curr_buf = GNUNET_SERVER_MIN_BUFFER_SIZE; + ret->cb = cb; + ret->cb_cls = cb_cls; + return ret; +} + + +/** + * Add incoming data to the receive buffer and call the + * callback for all complete messages. + * + * @param mst tokenizer to use + * @param client_identity ID of client for which this is a buffer + * @param buf input data to add + * @param size number of bytes in @a buf + * @param purge should any excess bytes in the buffer be discarded + * (i.e. for packet-based services like UDP) + * @param one_shot only call callback once, keep rest of message in buffer + * @return #GNUNET_OK if we are done processing (need more data) + * #GNUNET_NO if @a one_shot was set and we have another message ready + * #GNUNET_SYSERR if the data stream is corrupt + */ +int +GNUNET_SERVER_mst_receive (struct GNUNET_SERVER_MessageStreamTokenizer *mst, + void *client_identity, + const char *buf, size_t size, + int purge, int one_shot) +{ + const struct GNUNET_MessageHeader *hdr; + size_t delta; + uint16_t want; + char *ibuf; + int need_align; + unsigned long offset; + int ret; + + GNUNET_assert (mst->off <= mst->pos); + GNUNET_assert (mst->pos <= mst->curr_buf); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Server-mst receives %u bytes with %u bytes already in private buffer\n", + (unsigned int) size, (unsigned int) (mst->pos - mst->off)); + ret = GNUNET_OK; + ibuf = (char *) mst->hdr; + while (mst->pos > 0) + { +do_align: + GNUNET_assert (mst->pos >= mst->off); + if ((mst->curr_buf - mst->off < sizeof (struct GNUNET_MessageHeader)) || + (0 != (mst->off % ALIGN_FACTOR))) + { + /* need to align or need more space */ + mst->pos -= mst->off; + memmove (ibuf, &ibuf[mst->off], mst->pos); + mst->off = 0; + } + if (mst->pos - mst->off < sizeof (struct GNUNET_MessageHeader)) + { + delta = + GNUNET_MIN (sizeof (struct GNUNET_MessageHeader) - + (mst->pos - mst->off), size); + GNUNET_memcpy (&ibuf[mst->pos], buf, delta); + mst->pos += delta; + buf += delta; + size -= delta; + } + if (mst->pos - mst->off < sizeof (struct GNUNET_MessageHeader)) + { + if (purge) + { + mst->off = 0; + mst->pos = 0; + } + return GNUNET_OK; + } + hdr = (const struct GNUNET_MessageHeader *) &ibuf[mst->off]; + want = ntohs (hdr->size); + if (want < sizeof (struct GNUNET_MessageHeader)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if ( (mst->curr_buf - mst->off < want) && + (mst->off > 0) ) + { + /* can get more space by moving */ + mst->pos -= mst->off; + memmove (ibuf, &ibuf[mst->off], mst->pos); + mst->off = 0; + } + if (mst->curr_buf < want) + { + /* need to get more space by growing buffer */ + GNUNET_assert (0 == mst->off); + mst->hdr = GNUNET_realloc (mst->hdr, want); + ibuf = (char *) mst->hdr; + mst->curr_buf = want; + } + hdr = (const struct GNUNET_MessageHeader *) &ibuf[mst->off]; + if (mst->pos - mst->off < want) + { + delta = GNUNET_MIN (want - (mst->pos - mst->off), size); + GNUNET_assert (mst->pos + delta <= mst->curr_buf); + GNUNET_memcpy (&ibuf[mst->pos], buf, delta); + mst->pos += delta; + buf += delta; + size -= delta; + } + if (mst->pos - mst->off < want) + { + if (purge) + { + mst->off = 0; + mst->pos = 0; + } + return GNUNET_OK; + } + if (one_shot == GNUNET_SYSERR) + { + /* cannot call callback again, but return value saying that + * we have another full message in the buffer */ + ret = GNUNET_NO; + goto copy; + } + if (one_shot == GNUNET_YES) + one_shot = GNUNET_SYSERR; + mst->off += want; + if (GNUNET_SYSERR == mst->cb (mst->cb_cls, client_identity, hdr)) + return GNUNET_SYSERR; + if (mst->off == mst->pos) + { + /* reset to beginning of buffer, it's free right now! */ + mst->off = 0; + mst->pos = 0; + } + } + GNUNET_assert (0 == mst->pos); + while (size > 0) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Server-mst has %u bytes left in inbound buffer\n", + (unsigned int) size); + if (size < sizeof (struct GNUNET_MessageHeader)) + break; + offset = (unsigned long) buf; + need_align = (0 != (offset % ALIGN_FACTOR)) ? GNUNET_YES : GNUNET_NO; + if (GNUNET_NO == need_align) + { + /* can try to do zero-copy and process directly from original buffer */ + hdr = (const struct GNUNET_MessageHeader *) buf; + want = ntohs (hdr->size); + if (want < sizeof (struct GNUNET_MessageHeader)) + { + GNUNET_break_op (0); + mst->off = 0; + return GNUNET_SYSERR; + } + if (size < want) + break; /* or not: buffer incomplete, so copy to private buffer... */ + if (one_shot == GNUNET_SYSERR) + { + /* cannot call callback again, but return value saying that + * we have another full message in the buffer */ + ret = GNUNET_NO; + goto copy; + } + if (one_shot == GNUNET_YES) + one_shot = GNUNET_SYSERR; + if (GNUNET_SYSERR == mst->cb (mst->cb_cls, client_identity, hdr)) + return GNUNET_SYSERR; + buf += want; + size -= want; + } + else + { + /* need to copy to private buffer to align; + * yes, we go a bit more spagetti than usual here */ + goto do_align; + } + } +copy: + if ((size > 0) && (!purge)) + { + if (size + mst->pos > mst->curr_buf) + { + mst->hdr = GNUNET_realloc (mst->hdr, size + mst->pos); + ibuf = (char *) mst->hdr; + mst->curr_buf = size + mst->pos; + } + GNUNET_assert (size + mst->pos <= mst->curr_buf); + GNUNET_memcpy (&ibuf[mst->pos], buf, size); + mst->pos += size; + } + if (purge) + { + mst->off = 0; + mst->pos = 0; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Server-mst leaves %u bytes in private buffer\n", + (unsigned int) (mst->pos - mst->off)); + return ret; +} + + +/** + * Destroys a tokenizer. + * + * @param mst tokenizer to destroy + */ +void +GNUNET_SERVER_mst_destroy (struct GNUNET_SERVER_MessageStreamTokenizer *mst) +{ + GNUNET_free (mst->hdr); + GNUNET_free (mst); +} + + + +/* end of server_mst.c */ diff --git a/src/transport/tcp_service_legacy.c b/src/transport/tcp_service_legacy.c new file mode 100644 index 000000000..c55d586f3 --- /dev/null +++ b/src/transport/tcp_service_legacy.c @@ -0,0 +1,1687 @@ +/* + This file is part of GNUnet. + Copyright (C) 2009, 2012 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 3, 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., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * @file util/service.c + * @brief functions related to starting services + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_protocols.h" +#include "gnunet_constants.h" +#include "gnunet_resolver_service.h" + +#if HAVE_MALLINFO +#include +#include "gauger.h" +#endif + + +/* ******************* access control ******************** */ + +/** + * Check if the given IP address is in the list of IP addresses. + * + * @param list a list of networks + * @param add the IP to check (in network byte order) + * @return #GNUNET_NO if the IP is not in the list, #GNUNET_YES if it it is + */ +static int +check_ipv4_listed (const struct GNUNET_STRINGS_IPv4NetworkPolicy *list, + const struct in_addr *add) +{ + unsigned int i; + + if (NULL == list) + return GNUNET_NO; + i = 0; + while ((list[i].network.s_addr != 0) || (list[i].netmask.s_addr != 0)) + { + if ((add->s_addr & list[i].netmask.s_addr) == + (list[i].network.s_addr & list[i].netmask.s_addr)) + return GNUNET_YES; + i++; + } + return GNUNET_NO; +} + + +/** + * Check if the given IP address is in the list of IP addresses. + * + * @param list a list of networks + * @param ip the IP to check (in network byte order) + * @return #GNUNET_NO if the IP is not in the list, #GNUNET_YES if it it is + */ +static int +check_ipv6_listed (const struct GNUNET_STRINGS_IPv6NetworkPolicy *list, + const struct in6_addr *ip) +{ + unsigned int i; + unsigned int j; + struct in6_addr zero; + + if (NULL == list) + return GNUNET_NO; + memset (&zero, 0, sizeof (struct in6_addr)); + i = 0; +NEXT: + while (0 != memcmp (&zero, &list[i].network, sizeof (struct in6_addr))) + { + for (j = 0; j < sizeof (struct in6_addr) / sizeof (int); j++) + if (((((int *) ip)[j] & ((int *) &list[i].netmask)[j])) != + (((int *) &list[i].network)[j] & ((int *) &list[i].netmask)[j])) + { + i++; + goto NEXT; + } + return GNUNET_YES; + } + return GNUNET_NO; +} + + +/* ****************** service struct ****************** */ + + +/** + * Context for "service_task". + */ +struct GNUNET_SERVICE_Context +{ + /** + * Our configuration. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Handle for the server. + */ + struct GNUNET_SERVER_Handle *server; + + /** + * NULL-terminated array of addresses to bind to, NULL if we got pre-bound + * listen sockets. + */ + struct sockaddr **addrs; + + /** + * Name of our service. + */ + const char *service_name; + + /** + * Main service-specific task to run. + */ + GNUNET_SERVICE_Main task; + + /** + * Closure for @e task. + */ + void *task_cls; + + /** + * IPv4 addresses that are not allowed to connect. + */ + struct GNUNET_STRINGS_IPv4NetworkPolicy *v4_denied; + + /** + * IPv6 addresses that are not allowed to connect. + */ + struct GNUNET_STRINGS_IPv6NetworkPolicy *v6_denied; + + /** + * IPv4 addresses that are allowed to connect (if not + * set, all are allowed). + */ + struct GNUNET_STRINGS_IPv4NetworkPolicy *v4_allowed; + + /** + * IPv6 addresses that are allowed to connect (if not + * set, all are allowed). + */ + struct GNUNET_STRINGS_IPv6NetworkPolicy *v6_allowed; + + /** + * My (default) message handlers. Adjusted copy + * of "defhandlers". + */ + struct GNUNET_SERVER_MessageHandler *my_handlers; + + /** + * Array of the lengths of the entries in addrs. + */ + socklen_t *addrlens; + + /** + * NULL-terminated array of listen sockets we should take over. + */ + struct GNUNET_NETWORK_Handle **lsocks; + + /** + * Task ID of the shutdown task. + */ + struct GNUNET_SCHEDULER_Task *shutdown_task; + + /** + * Idle timeout for server. + */ + struct GNUNET_TIME_Relative timeout; + + /** + * Overall success/failure of the service start. + */ + int ret; + + /** + * If we are daemonizing, this FD is set to the + * pipe to the parent. Send '.' if we started + * ok, '!' if not. -1 if we are not daemonizing. + */ + int ready_confirm_fd; + + /** + * Do we close connections if we receive messages + * for which we have no handler? + */ + int require_found; + + /** + * Do we require a matching UID for UNIX domain socket connections? + * #GNUNET_NO means that the UID does not have to match (however, + * @e match_gid may still impose other access control checks). + */ + int match_uid; + + /** + * Do we require a matching GID for UNIX domain socket connections? + * Ignored if @e match_uid is #GNUNET_YES. Note that this is about + * checking that the client's UID is in our group OR that the + * client's GID is our GID. If both "match_gid" and @e match_uid are + * #GNUNET_NO, all users on the local system have access. + */ + int match_gid; + + /** + * Our options. + */ + enum GNUNET_SERVICE_Options options; + +}; + + +/* ****************** message handlers ****************** */ + +/** + * Send a 'TEST' message back to the client. + * + * @param cls the 'struct GNUNET_SERVER_Client' to send TEST to + * @param size number of bytes available in 'buf' + * @param buf where to copy the message + * @return number of bytes written to 'buf' + */ +static size_t +write_test (void *cls, size_t size, void *buf) +{ + struct GNUNET_SERVER_Client *client = cls; + struct GNUNET_MessageHeader *msg; + + if (size < sizeof (struct GNUNET_MessageHeader)) + { + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return 0; /* client disconnected */ + } + msg = (struct GNUNET_MessageHeader *) buf; + msg->type = htons (GNUNET_MESSAGE_TYPE_TEST); + msg->size = htons (sizeof (struct GNUNET_MessageHeader)); + GNUNET_SERVER_receive_done (client, GNUNET_OK); + return sizeof (struct GNUNET_MessageHeader); +} + + +/** + * Handler for TEST message. + * + * @param cls closure (refers to service) + * @param client identification of the client + * @param message the actual message + */ +static void +handle_test (void *cls, struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message) +{ + /* simply bounce message back to acknowledge */ + if (NULL == + GNUNET_SERVER_notify_transmit_ready (client, + sizeof (struct GNUNET_MessageHeader), + GNUNET_TIME_UNIT_FOREVER_REL, + &write_test, client)) + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); +} + + +/** + * Default handlers for all services. Will be copied and the + * "callback_cls" fields will be replaced with the specific service + * struct. + */ +static const struct GNUNET_SERVER_MessageHandler defhandlers[] = { + {&handle_test, NULL, GNUNET_MESSAGE_TYPE_TEST, + sizeof (struct GNUNET_MessageHeader)}, + {NULL, NULL, 0, 0} +}; + + +/* ****************** service core routines ************** */ + + +/** + * Check if access to the service is allowed from the given address. + * + * @param cls closure + * @param uc credentials, if available, otherwise NULL + * @param addr address + * @param addrlen length of address + * @return #GNUNET_YES to allow, #GNUNET_NO to deny, #GNUNET_SYSERR + * for unknown address family (will be denied). + */ +static int +check_access (void *cls, const struct GNUNET_CONNECTION_Credentials *uc, + const struct sockaddr *addr, socklen_t addrlen) +{ + struct GNUNET_SERVICE_Context *sctx = cls; + const struct sockaddr_in *i4; + const struct sockaddr_in6 *i6; + int ret; + + switch (addr->sa_family) + { + case AF_INET: + GNUNET_assert (addrlen == sizeof (struct sockaddr_in)); + i4 = (const struct sockaddr_in *) addr; + ret = ((NULL == sctx->v4_allowed) || + (check_ipv4_listed (sctx->v4_allowed, &i4->sin_addr))) && + ((NULL == sctx->v4_denied) || + (!check_ipv4_listed (sctx->v4_denied, &i4->sin_addr))); + break; + case AF_INET6: + GNUNET_assert (addrlen == sizeof (struct sockaddr_in6)); + i6 = (const struct sockaddr_in6 *) addr; + ret = ((NULL == sctx->v6_allowed) || + (check_ipv6_listed (sctx->v6_allowed, &i6->sin6_addr))) && + ((NULL == sctx->v6_denied) || + (!check_ipv6_listed (sctx->v6_denied, &i6->sin6_addr))); + break; +#ifndef WINDOWS + case AF_UNIX: + ret = GNUNET_OK; /* controlled using file-system ACL now */ + break; +#endif + default: + LOG (GNUNET_ERROR_TYPE_WARNING, _("Unknown address family %d\n"), + addr->sa_family); + return GNUNET_SYSERR; + } + if (GNUNET_OK != ret) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + _("Access from `%s' denied to service `%s'\n"), + GNUNET_a2s (addr, addrlen), + sctx->service_name); + } + return ret; +} + + +/** + * Get the name of the file where we will + * write the PID of the service. + * + * @param sctx service context + * @return name of the file for the process ID + */ +static char * +get_pid_file_name (struct GNUNET_SERVICE_Context *sctx) +{ + char *pif; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (sctx->cfg, sctx->service_name, + "PIDFILE", &pif)) + return NULL; + return pif; +} + + +/** + * Parse an IPv4 access control list. + * + * @param ret location where to write the ACL (set) + * @param sctx service context to use to get the configuration + * @param option name of the ACL option to parse + * @return #GNUNET_SYSERR on parse error, #GNUNET_OK on success (including + * no ACL configured) + */ +static int +process_acl4 (struct GNUNET_STRINGS_IPv4NetworkPolicy **ret, + struct GNUNET_SERVICE_Context *sctx, + const char *option) +{ + char *opt; + + if (!GNUNET_CONFIGURATION_have_value (sctx->cfg, sctx->service_name, option)) + { + *ret = NULL; + return GNUNET_OK; + } + GNUNET_break (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (sctx->cfg, + sctx->service_name, + option, &opt)); + if (NULL == (*ret = GNUNET_STRINGS_parse_ipv4_policy (opt))) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + _("Could not parse IPv4 network specification `%s' for `%s:%s'\n"), + opt, sctx->service_name, option); + GNUNET_free (opt); + return GNUNET_SYSERR; + } + GNUNET_free (opt); + return GNUNET_OK; +} + + +/** + * Parse an IPv6 access control list. + * + * @param ret location where to write the ACL (set) + * @param sctx service context to use to get the configuration + * @param option name of the ACL option to parse + * @return #GNUNET_SYSERR on parse error, #GNUNET_OK on success (including + * no ACL configured) + */ +static int +process_acl6 (struct GNUNET_STRINGS_IPv6NetworkPolicy **ret, + struct GNUNET_SERVICE_Context *sctx, + const char *option) +{ + char *opt; + + if (!GNUNET_CONFIGURATION_have_value (sctx->cfg, sctx->service_name, option)) + { + *ret = NULL; + return GNUNET_OK; + } + GNUNET_break (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (sctx->cfg, + sctx->service_name, + option, &opt)); + if (NULL == (*ret = GNUNET_STRINGS_parse_ipv6_policy (opt))) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + _("Could not parse IPv6 network specification `%s' for `%s:%s'\n"), + opt, sctx->service_name, option); + GNUNET_free (opt); + return GNUNET_SYSERR; + } + GNUNET_free (opt); + return GNUNET_OK; +} + + +/** + * Add the given UNIX domain path as an address to the + * list (as the first entry). + * + * @param saddrs array to update + * @param saddrlens where to store the address length + * @param unixpath path to add + * @param abstract #GNUNET_YES to add an abstract UNIX domain socket. This + * parameter is ignore on systems other than LINUX + */ +static void +add_unixpath (struct sockaddr **saddrs, + socklen_t *saddrlens, + const char *unixpath, + int abstract) +{ +#ifdef AF_UNIX + struct sockaddr_un *un; + + un = GNUNET_new (struct sockaddr_un); + un->sun_family = AF_UNIX; + strncpy (un->sun_path, unixpath, sizeof (un->sun_path) - 1); +#ifdef LINUX + if (GNUNET_YES == abstract) + un->sun_path[0] = '\0'; +#endif +#if HAVE_SOCKADDR_UN_SUN_LEN + un->sun_len = (u_char) sizeof (struct sockaddr_un); +#endif + *saddrs = (struct sockaddr *) un; + *saddrlens = sizeof (struct sockaddr_un); +#else + /* this function should never be called + * unless AF_UNIX is defined! */ + GNUNET_assert (0); +#endif +} + + +/** + * Get the list of addresses that a server for the given service + * should bind to. + * + * @param service_name name of the service + * @param cfg configuration (which specifies the addresses) + * @param addrs set (call by reference) to an array of pointers to the + * addresses the server should bind to and listen on; the + * array will be NULL-terminated (on success) + * @param addr_lens set (call by reference) to an array of the lengths + * of the respective `struct sockaddr` struct in the @a addrs + * array (on success) + * @return number of addresses found on success, + * #GNUNET_SYSERR if the configuration + * did not specify reasonable finding information or + * if it specified a hostname that could not be resolved; + * #GNUNET_NO if the number of addresses configured is + * zero (in this case, `*addrs` and `*addr_lens` will be + * set to NULL). + */ +int +GNUNET_SERVICE_get_server_addresses (const char *service_name, + const struct GNUNET_CONFIGURATION_Handle *cfg, + struct sockaddr ***addrs, + socklen_t ** addr_lens) +{ + int disablev6; + struct GNUNET_NETWORK_Handle *desc; + unsigned long long port; + char *unixpath; + struct addrinfo hints; + struct addrinfo *res; + struct addrinfo *pos; + struct addrinfo *next; + unsigned int i; + int resi; + int ret; + int abstract; + struct sockaddr **saddrs; + socklen_t *saddrlens; + char *hostname; + + *addrs = NULL; + *addr_lens = NULL; + desc = NULL; + if (GNUNET_CONFIGURATION_have_value (cfg, service_name, "DISABLEV6")) + { + if (GNUNET_SYSERR == + (disablev6 = + GNUNET_CONFIGURATION_get_value_yesno (cfg, service_name, "DISABLEV6"))) + return GNUNET_SYSERR; + } + else + disablev6 = GNUNET_NO; + + if (! disablev6) + { + /* probe IPv6 support */ + desc = GNUNET_NETWORK_socket_create (PF_INET6, SOCK_STREAM, 0); + if (NULL == desc) + { + if ((ENOBUFS == errno) || (ENOMEM == errno) || (ENFILE == errno) || + (EACCES == errno)) + { + LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "socket"); + return GNUNET_SYSERR; + } + LOG (GNUNET_ERROR_TYPE_INFO, + _("Disabling IPv6 support for service `%s', failed to create IPv6 socket: %s\n"), + service_name, STRERROR (errno)); + disablev6 = GNUNET_YES; + } + else + { + GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (desc)); + desc = NULL; + } + } + + port = 0; + if (GNUNET_CONFIGURATION_have_value (cfg, service_name, "PORT")) + { + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, service_name, + "PORT", &port)) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Require valid port number for service `%s' in configuration!\n"), + service_name); + } + if (port > 65535) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Require valid port number for service `%s' in configuration!\n"), + service_name); + return GNUNET_SYSERR; + } + } + + if (GNUNET_CONFIGURATION_have_value (cfg, service_name, "BINDTO")) + { + GNUNET_break (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (cfg, service_name, + "BINDTO", &hostname)); + } + else + hostname = NULL; + + unixpath = NULL; + abstract = GNUNET_NO; +#ifdef AF_UNIX + if ((GNUNET_YES == + GNUNET_CONFIGURATION_have_value (cfg, service_name, "UNIXPATH")) && + (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_filename (cfg, service_name, "UNIXPATH", + &unixpath)) && + (0 < strlen (unixpath))) + { + /* probe UNIX support */ + struct sockaddr_un s_un; + + if (strlen (unixpath) >= sizeof (s_un.sun_path)) + { + 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); + } +#ifdef LINUX + abstract = GNUNET_CONFIGURATION_get_value_yesno (cfg, + "TESTING", + "USE_ABSTRACT_SOCKETS"); + if (GNUNET_SYSERR == abstract) + abstract = GNUNET_NO; +#endif + if ((GNUNET_YES != abstract) + && (GNUNET_OK != + GNUNET_DISK_directory_create_for_file (unixpath))) + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "mkdir", + unixpath); + } + if (NULL != unixpath) + { + desc = GNUNET_NETWORK_socket_create (AF_UNIX, SOCK_STREAM, 0); + if (NULL == desc) + { + if ((ENOBUFS == errno) || (ENOMEM == errno) || (ENFILE == errno) || + (EACCES == errno)) + { + LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "socket"); + GNUNET_free_non_null (hostname); + GNUNET_free (unixpath); + return GNUNET_SYSERR; + } + LOG (GNUNET_ERROR_TYPE_INFO, + _("Disabling UNIX domain socket support for service `%s', failed to create UNIX domain socket: %s\n"), + service_name, + STRERROR (errno)); + GNUNET_free (unixpath); + unixpath = NULL; + } + else + { + GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (desc)); + desc = NULL; + } + } +#endif + + if ((0 == port) && (NULL == unixpath)) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Have neither PORT nor UNIXPATH for service `%s', but one is required\n"), + service_name); + GNUNET_free_non_null (hostname); + return GNUNET_SYSERR; + } + if (0 == port) + { + saddrs = GNUNET_malloc (2 * sizeof (struct sockaddr *)); + saddrlens = GNUNET_malloc (2 * sizeof (socklen_t)); + add_unixpath (saddrs, saddrlens, unixpath, abstract); + GNUNET_free_non_null (unixpath); + GNUNET_free_non_null (hostname); + *addrs = saddrs; + *addr_lens = saddrlens; + return 1; + } + + if (NULL != hostname) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Resolving `%s' since that is where `%s' will bind to.\n", + hostname, + service_name); + memset (&hints, 0, sizeof (struct addrinfo)); + if (disablev6) + hints.ai_family = AF_INET; + hints.ai_protocol = IPPROTO_TCP; + if ((0 != (ret = getaddrinfo (hostname, NULL, &hints, &res))) || + (NULL == res)) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Failed to resolve `%s': %s\n"), + hostname, + gai_strerror (ret)); + GNUNET_free (hostname); + GNUNET_free_non_null (unixpath); + return GNUNET_SYSERR; + } + next = res; + i = 0; + while (NULL != (pos = next)) + { + next = pos->ai_next; + if ((disablev6) && (pos->ai_family == AF_INET6)) + continue; + i++; + } + if (0 == i) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Failed to find %saddress for `%s'.\n"), + disablev6 ? "IPv4 " : "", + hostname); + freeaddrinfo (res); + GNUNET_free (hostname); + GNUNET_free_non_null (unixpath); + return GNUNET_SYSERR; + } + resi = i; + if (NULL != unixpath) + resi++; + saddrs = GNUNET_malloc ((resi + 1) * sizeof (struct sockaddr *)); + saddrlens = GNUNET_malloc ((resi + 1) * sizeof (socklen_t)); + i = 0; + if (NULL != unixpath) + { + add_unixpath (saddrs, saddrlens, unixpath, abstract); + i++; + } + next = res; + while (NULL != (pos = next)) + { + next = pos->ai_next; + if ((disablev6) && (AF_INET6 == pos->ai_family)) + continue; + if ((IPPROTO_TCP != pos->ai_protocol) && (0 != pos->ai_protocol)) + continue; /* not TCP */ + if ((SOCK_STREAM != pos->ai_socktype) && (0 != pos->ai_socktype)) + continue; /* huh? */ + LOG (GNUNET_ERROR_TYPE_DEBUG, "Service `%s' will bind to `%s'\n", + service_name, GNUNET_a2s (pos->ai_addr, pos->ai_addrlen)); + if (AF_INET == pos->ai_family) + { + GNUNET_assert (sizeof (struct sockaddr_in) == pos->ai_addrlen); + saddrlens[i] = pos->ai_addrlen; + saddrs[i] = GNUNET_malloc (saddrlens[i]); + GNUNET_memcpy (saddrs[i], pos->ai_addr, saddrlens[i]); + ((struct sockaddr_in *) saddrs[i])->sin_port = htons (port); + } + else + { + GNUNET_assert (AF_INET6 == pos->ai_family); + GNUNET_assert (sizeof (struct sockaddr_in6) == pos->ai_addrlen); + saddrlens[i] = pos->ai_addrlen; + saddrs[i] = GNUNET_malloc (saddrlens[i]); + GNUNET_memcpy (saddrs[i], pos->ai_addr, saddrlens[i]); + ((struct sockaddr_in6 *) saddrs[i])->sin6_port = htons (port); + } + i++; + } + GNUNET_free (hostname); + freeaddrinfo (res); + resi = i; + } + else + { + /* will bind against everything, just set port */ + if (disablev6) + { + /* V4-only */ + resi = 1; + if (NULL != unixpath) + resi++; + i = 0; + saddrs = GNUNET_malloc ((resi + 1) * sizeof (struct sockaddr *)); + saddrlens = GNUNET_malloc ((resi + 1) * sizeof (socklen_t)); + if (NULL != unixpath) + { + add_unixpath (saddrs, saddrlens, unixpath, abstract); + i++; + } + saddrlens[i] = sizeof (struct sockaddr_in); + saddrs[i] = GNUNET_malloc (saddrlens[i]); +#if HAVE_SOCKADDR_IN_SIN_LEN + ((struct sockaddr_in *) saddrs[i])->sin_len = saddrlens[i]; +#endif + ((struct sockaddr_in *) saddrs[i])->sin_family = AF_INET; + ((struct sockaddr_in *) saddrs[i])->sin_port = htons (port); + } + else + { + /* dual stack */ + resi = 2; + if (NULL != unixpath) + resi++; + saddrs = GNUNET_malloc ((resi + 1) * sizeof (struct sockaddr *)); + saddrlens = GNUNET_malloc ((resi + 1) * sizeof (socklen_t)); + i = 0; + if (NULL != unixpath) + { + add_unixpath (saddrs, saddrlens, unixpath, abstract); + i++; + } + saddrlens[i] = sizeof (struct sockaddr_in6); + saddrs[i] = GNUNET_malloc (saddrlens[i]); +#if HAVE_SOCKADDR_IN_SIN_LEN + ((struct sockaddr_in6 *) saddrs[i])->sin6_len = saddrlens[0]; +#endif + ((struct sockaddr_in6 *) saddrs[i])->sin6_family = AF_INET6; + ((struct sockaddr_in6 *) saddrs[i])->sin6_port = htons (port); + i++; + saddrlens[i] = sizeof (struct sockaddr_in); + saddrs[i] = GNUNET_malloc (saddrlens[i]); +#if HAVE_SOCKADDR_IN_SIN_LEN + ((struct sockaddr_in *) saddrs[i])->sin_len = saddrlens[1]; +#endif + ((struct sockaddr_in *) saddrs[i])->sin_family = AF_INET; + ((struct sockaddr_in *) saddrs[i])->sin_port = htons (port); + } + } + GNUNET_free_non_null (unixpath); + *addrs = saddrs; + *addr_lens = saddrlens; + return resi; +} + + +#ifdef MINGW +/** + * Read listen sockets from the parent process (ARM). + * + * @param sctx service context to initialize + * @return #GNUNET_YES if ok, #GNUNET_NO if not ok (must bind yourself), + * and #GNUNET_SYSERR on error. + */ +static int +receive_sockets_from_parent (struct GNUNET_SERVICE_Context *sctx) +{ + const char *env_buf; + int fail; + uint64_t count; + uint64_t i; + HANDLE lsocks_pipe; + + env_buf = getenv ("GNUNET_OS_READ_LSOCKS"); + if ((NULL == env_buf) || (strlen (env_buf) <= 0)) + return GNUNET_NO; + /* Using W32 API directly here, because this pipe will + * never be used outside of this function, and it's just too much of a bother + * to create a GNUnet API that boxes a HANDLE (the way it is done with socks) + */ + lsocks_pipe = (HANDLE) strtoul (env_buf, NULL, 10); + if ( (0 == lsocks_pipe) || (INVALID_HANDLE_VALUE == lsocks_pipe)) + return GNUNET_NO; + fail = 1; + do + { + int ret; + int fail2; + DWORD rd; + + ret = ReadFile (lsocks_pipe, &count, sizeof (count), &rd, NULL); + if ((0 == ret) || (sizeof (count) != rd) || (0 == count)) + break; + sctx->lsocks = + GNUNET_malloc (sizeof (struct GNUNET_NETWORK_Handle *) * (count + 1)); + + fail2 = 1; + for (i = 0; i < count; i++) + { + WSAPROTOCOL_INFOA pi; + uint64_t size; + SOCKET s; + + ret = ReadFile (lsocks_pipe, &size, sizeof (size), &rd, NULL); + if ( (0 == ret) || (sizeof (size) != rd) || (sizeof (pi) != size) ) + break; + ret = ReadFile (lsocks_pipe, &pi, sizeof (pi), &rd, NULL); + if ( (0 == ret) || (sizeof (pi) != rd)) + break; + s = WSASocketA (pi.iAddressFamily, pi.iSocketType, pi.iProtocol, &pi, 0, WSA_FLAG_OVERLAPPED); + sctx->lsocks[i] = GNUNET_NETWORK_socket_box_native (s); + if (NULL == sctx->lsocks[i]) + break; + else if (i == count - 1) + fail2 = 0; + } + if (fail2) + break; + sctx->lsocks[count] = NULL; + fail = 0; + } + while (fail); + + CloseHandle (lsocks_pipe); + + if (fail) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Could not access a pre-bound socket, will try to bind myself\n")); + for (i = 0; (i < count) && (NULL != sctx->lsocks[i]); i++) + GNUNET_break (0 == GNUNET_NETWORK_socket_close (sctx->lsocks[i])); + GNUNET_free_non_null (sctx->lsocks); + sctx->lsocks = NULL; + return GNUNET_NO; + } + return GNUNET_YES; +} +#endif + + +/** + * Setup addr, addrlen, idle_timeout + * based on configuration! + * + * Configuration may specify: + * - PORT (where to bind to for TCP) + * - UNIXPATH (where to bind to for UNIX domain sockets) + * - TIMEOUT (after how many ms does an inactive service timeout); + * - DISABLEV6 (disable support for IPv6, otherwise we use dual-stack) + * - BINDTO (hostname or IP address to bind to, otherwise we take everything) + * - ACCEPT_FROM (only allow connections from specified IPv4 subnets) + * - ACCEPT_FROM6 (only allow connections from specified IPv6 subnets) + * - REJECT_FROM (disallow allow connections from specified IPv4 subnets) + * - REJECT_FROM6 (disallow allow connections from specified IPv6 subnets) + * + * @param sctx service context to initialize + * @return #GNUNET_OK if configuration succeeded + */ +static int +setup_service (struct GNUNET_SERVICE_Context *sctx) +{ + struct GNUNET_TIME_Relative idleout; + int tolerant; + +#ifndef MINGW + const char *nfds; + unsigned int cnt; + int flags; +#endif + + if (GNUNET_CONFIGURATION_have_value (sctx->cfg, sctx->service_name, "TIMEOUT")) + { + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (sctx->cfg, sctx->service_name, + "TIMEOUT", &idleout)) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Specified value for `%s' of service `%s' is invalid\n"), + "TIMEOUT", sctx->service_name); + return GNUNET_SYSERR; + } + sctx->timeout = idleout; + } + else + sctx->timeout = GNUNET_TIME_UNIT_FOREVER_REL; + + if (GNUNET_CONFIGURATION_have_value + (sctx->cfg, sctx->service_name, "TOLERANT")) + { + if (GNUNET_SYSERR == + (tolerant = + GNUNET_CONFIGURATION_get_value_yesno (sctx->cfg, sctx->service_name, + "TOLERANT"))) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Specified value for `%s' of service `%s' is invalid\n"), + "TOLERANT", sctx->service_name); + return GNUNET_SYSERR; + } + } + else + tolerant = GNUNET_NO; + +#ifndef MINGW + errno = 0; + if ((NULL != (nfds = getenv ("LISTEN_FDS"))) && + (1 == SSCANF (nfds, "%u", &cnt)) && (cnt > 0) && (cnt < FD_SETSIZE) && + (cnt + 4 < FD_SETSIZE)) + { + sctx->lsocks = + GNUNET_malloc (sizeof (struct GNUNET_NETWORK_Handle *) * (cnt + 1)); + while (0 < cnt--) + { + flags = fcntl (3 + cnt, F_GETFD); + if ((flags < 0) || (0 != (flags & FD_CLOEXEC)) || + (NULL == + (sctx->lsocks[cnt] = GNUNET_NETWORK_socket_box_native (3 + cnt)))) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _ + ("Could not access pre-bound socket %u, will try to bind myself\n"), + (unsigned int) 3 + cnt); + cnt++; + while (sctx->lsocks[cnt] != NULL) + GNUNET_break (0 == GNUNET_NETWORK_socket_close (sctx->lsocks[cnt++])); + GNUNET_free (sctx->lsocks); + sctx->lsocks = NULL; + break; + } + } + unsetenv ("LISTEN_FDS"); + } +#else + if (getenv ("GNUNET_OS_READ_LSOCKS") != NULL) + { + receive_sockets_from_parent (sctx); + putenv ("GNUNET_OS_READ_LSOCKS="); + } +#endif + + if ((NULL == sctx->lsocks) && + (GNUNET_SYSERR == + GNUNET_SERVICE_get_server_addresses (sctx->service_name, sctx->cfg, + &sctx->addrs, &sctx->addrlens))) + return GNUNET_SYSERR; + sctx->require_found = tolerant ? GNUNET_NO : GNUNET_YES; + sctx->match_uid = + GNUNET_CONFIGURATION_get_value_yesno (sctx->cfg, sctx->service_name, + "UNIX_MATCH_UID"); + sctx->match_gid = + GNUNET_CONFIGURATION_get_value_yesno (sctx->cfg, sctx->service_name, + "UNIX_MATCH_GID"); + process_acl4 (&sctx->v4_denied, sctx, "REJECT_FROM"); + process_acl4 (&sctx->v4_allowed, sctx, "ACCEPT_FROM"); + process_acl6 (&sctx->v6_denied, sctx, "REJECT_FROM6"); + process_acl6 (&sctx->v6_allowed, sctx, "ACCEPT_FROM6"); + + return GNUNET_OK; +} + + +/** + * Get the name of the user that'll be used + * to provide the service. + * + * @param sctx service context + * @return value of the 'USERNAME' option + */ +static char * +get_user_name (struct GNUNET_SERVICE_Context *sctx) +{ + char *un; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (sctx->cfg, sctx->service_name, + "USERNAME", &un)) + return NULL; + return un; +} + + +/** + * Write PID file. + * + * @param sctx service context + * @param pid PID to write (should be equal to 'getpid()' + * @return #GNUNET_OK on success (including no work to be done) + */ +static int +write_pid_file (struct GNUNET_SERVICE_Context *sctx, pid_t pid) +{ + FILE *pidfd; + char *pif; + char *user; + char *rdir; + int len; + + if (NULL == (pif = get_pid_file_name (sctx))) + return GNUNET_OK; /* no file desired */ + user = get_user_name (sctx); + rdir = GNUNET_strdup (pif); + len = strlen (rdir); + while ((len > 0) && (rdir[len] != DIR_SEPARATOR)) + len--; + rdir[len] = '\0'; + if (0 != ACCESS (rdir, F_OK)) + { + /* we get to create a directory -- and claim it + * as ours! */ + (void) GNUNET_DISK_directory_create (rdir); + if ((NULL != user) && (0 < strlen (user))) + GNUNET_DISK_file_change_owner (rdir, user); + } + if (0 != ACCESS (rdir, W_OK | X_OK)) + { + LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "access", rdir); + GNUNET_free (rdir); + GNUNET_free_non_null (user); + GNUNET_free (pif); + return GNUNET_SYSERR; + } + GNUNET_free (rdir); + pidfd = FOPEN (pif, "w"); + if (NULL == pidfd) + { + LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "fopen", pif); + GNUNET_free (pif); + GNUNET_free_non_null (user); + return GNUNET_SYSERR; + } + if (0 > FPRINTF (pidfd, "%u", pid)) + LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "fprintf", pif); + GNUNET_break (0 == FCLOSE (pidfd)); + if ((NULL != user) && (0 < strlen (user))) + GNUNET_DISK_file_change_owner (pif, user); + GNUNET_free_non_null (user); + GNUNET_free (pif); + return GNUNET_OK; +} + + +/** + * Task run during shutdown. Stops the server/service. + * + * @param cls the `struct GNUNET_SERVICE_Context` + */ +static void +shutdown_task (void *cls) +{ + struct GNUNET_SERVICE_Context *service = cls; + struct GNUNET_SERVER_Handle *server = service->server; + + service->shutdown_task = NULL; + if (0 != (service->options & GNUNET_SERVICE_OPTION_SOFT_SHUTDOWN)) + GNUNET_SERVER_stop_listening (server); + else + GNUNET_SERVER_destroy (server); +} + + +/** + * Initial task for the service. + * + * @param cls service context + */ +static void +service_task (void *cls) +{ + struct GNUNET_SERVICE_Context *sctx = cls; + unsigned int i; + + GNUNET_RESOLVER_connect (sctx->cfg); + if (NULL != sctx->lsocks) + sctx->server + = GNUNET_SERVER_create_with_sockets (&check_access, sctx, sctx->lsocks, + sctx->timeout, sctx->require_found); + else + sctx->server + = GNUNET_SERVER_create (&check_access, sctx, sctx->addrs, sctx->addrlens, + sctx->timeout, sctx->require_found); + if (NULL == sctx->server) + { + if (NULL != sctx->addrs) + for (i = 0; NULL != sctx->addrs[i]; i++) + LOG (GNUNET_ERROR_TYPE_INFO, + _("Failed to start `%s' at `%s'\n"), + sctx->service_name, GNUNET_a2s (sctx->addrs[i], sctx->addrlens[i])); + sctx->ret = GNUNET_SYSERR; + return; + } +#ifndef WINDOWS + if (NULL != sctx->addrs) + for (i = 0; NULL != sctx->addrs[i]; i++) + if ((AF_UNIX == sctx->addrs[i]->sa_family) + && ('\0' != ((const struct sockaddr_un *)sctx->addrs[i])->sun_path[0])) + GNUNET_DISK_fix_permissions (((const struct sockaddr_un *)sctx->addrs[i])->sun_path, + sctx->match_uid, + sctx->match_gid); +#endif + + + if (0 == (sctx->options & GNUNET_SERVICE_OPTION_MANUAL_SHUTDOWN)) + { + /* install a task that will kill the server + * process if the scheduler ever gets a shutdown signal */ + sctx->shutdown_task = GNUNET_SCHEDULER_add_shutdown (&shutdown_task, + sctx); + } + sctx->my_handlers = GNUNET_malloc (sizeof (defhandlers)); + GNUNET_memcpy (sctx->my_handlers, defhandlers, sizeof (defhandlers)); + i = 0; + while (NULL != sctx->my_handlers[i].callback) + sctx->my_handlers[i++].callback_cls = sctx; + GNUNET_SERVER_add_handlers (sctx->server, sctx->my_handlers); + if (-1 != sctx->ready_confirm_fd) + { + GNUNET_break (1 == WRITE (sctx->ready_confirm_fd, ".", 1)); + GNUNET_break (0 == CLOSE (sctx->ready_confirm_fd)); + sctx->ready_confirm_fd = -1; + write_pid_file (sctx, getpid ()); + } + if (NULL != sctx->addrs) + { + i = 0; + while (NULL != sctx->addrs[i]) + { + LOG (GNUNET_ERROR_TYPE_INFO, _("Service `%s' runs at %s\n"), + sctx->service_name, GNUNET_a2s (sctx->addrs[i], sctx->addrlens[i])); + i++; + } + } + sctx->task (sctx->task_cls, sctx->server, sctx->cfg); +} + + +/** + * Detach from terminal. + * + * @param sctx service context + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +detach_terminal (struct GNUNET_SERVICE_Context *sctx) +{ +#ifndef MINGW + pid_t pid; + int nullfd; + int filedes[2]; + + if (0 != PIPE (filedes)) + { + LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "pipe"); + return GNUNET_SYSERR; + } + pid = fork (); + if (pid < 0) + { + LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "fork"); + return GNUNET_SYSERR; + } + if (0 != pid) + { + /* Parent */ + char c; + + GNUNET_break (0 == CLOSE (filedes[1])); + c = 'X'; + if (1 != READ (filedes[0], &c, sizeof (char))) + LOG_STRERROR (GNUNET_ERROR_TYPE_WARNING, "read"); + fflush (stdout); + switch (c) + { + case '.': + exit (0); + case 'I': + LOG (GNUNET_ERROR_TYPE_INFO, _("Service process failed to initialize\n")); + break; + case 'S': + LOG (GNUNET_ERROR_TYPE_INFO, + _("Service process could not initialize server function\n")); + break; + case 'X': + LOG (GNUNET_ERROR_TYPE_INFO, + _("Service process failed to report status\n")); + break; + } + exit (1); /* child reported error */ + } + GNUNET_break (0 == CLOSE (0)); + GNUNET_break (0 == CLOSE (1)); + GNUNET_break (0 == CLOSE (filedes[0])); + nullfd = OPEN ("/dev/null", O_RDWR | O_APPEND); + if (nullfd < 0) + return GNUNET_SYSERR; + /* set stdin/stdout to /dev/null */ + if ((dup2 (nullfd, 0) < 0) || (dup2 (nullfd, 1) < 0)) + { + LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "dup2"); + (void) CLOSE (nullfd); + return GNUNET_SYSERR; + } + (void) CLOSE (nullfd); + /* Detach from controlling terminal */ + pid = setsid (); + if (-1 == pid) + LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "setsid"); + sctx->ready_confirm_fd = filedes[1]; +#else + /* FIXME: we probably need to do something else + * elsewhere in order to fork the process itself... */ + FreeConsole (); +#endif + return GNUNET_OK; +} + + +/** + * Set user ID. + * + * @param sctx service context + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +set_user_id (struct GNUNET_SERVICE_Context *sctx) +{ + char *user; + + if (NULL == (user = get_user_name (sctx))) + return GNUNET_OK; /* keep */ +#ifndef MINGW + struct passwd *pws; + + errno = 0; + pws = getpwnam (user); + if (NULL == pws) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Cannot obtain information about user `%s': %s\n"), user, + errno == 0 ? _("No such user") : STRERROR (errno)); + GNUNET_free (user); + return GNUNET_SYSERR; + } + if ((0 != setgid (pws->pw_gid)) || (0 != setegid (pws->pw_gid)) || +#if HAVE_INITGROUPS + (0 != initgroups (user, pws->pw_gid)) || +#endif + (0 != setuid (pws->pw_uid)) || (0 != seteuid (pws->pw_uid))) + { + if ((0 != setregid (pws->pw_gid, pws->pw_gid)) || + (0 != setreuid (pws->pw_uid, pws->pw_uid))) + { + LOG (GNUNET_ERROR_TYPE_ERROR, _("Cannot change user/group to `%s': %s\n"), + user, STRERROR (errno)); + GNUNET_free (user); + return GNUNET_SYSERR; + } + } +#endif + GNUNET_free (user); + return GNUNET_OK; +} + + +/** + * Delete the PID file that was created by our parent. + * + * @param sctx service context + */ +static void +pid_file_delete (struct GNUNET_SERVICE_Context *sctx) +{ + char *pif = get_pid_file_name (sctx); + + if (NULL == pif) + return; /* no PID file */ + if (0 != UNLINK (pif)) + LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "unlink", pif); + GNUNET_free (pif); +} + + +/** + * Run a standard GNUnet service startup sequence (initialize loggers + * and configuration, parse options). + * + * @param argc number of command line arguments + * @param argv command line arguments + * @param service_name our service name + * @param options service options + * @param task main task of the service + * @param task_cls closure for @a task + * @return #GNUNET_SYSERR on error, #GNUNET_OK + * if we shutdown nicely + */ +int +GNUNET_SERVICE_run (int argc, char *const *argv, + const char *service_name, + enum GNUNET_SERVICE_Options options, + GNUNET_SERVICE_Main task, + void *task_cls) +{ +#define HANDLE_ERROR do { GNUNET_break (0); goto shutdown; } while (0) + + int err; + int ret; + char *cfg_fn; + char *opt_cfg_fn; + char *loglev; + char *logfile; + int do_daemonize; + unsigned int i; + unsigned long long skew_offset; + unsigned long long skew_variance; + long long clock_offset; + struct GNUNET_SERVICE_Context sctx; + struct GNUNET_CONFIGURATION_Handle *cfg; + const char *xdg; + + struct GNUNET_GETOPT_CommandLineOption service_options[] = { + GNUNET_GETOPT_OPTION_CFG_FILE (&opt_cfg_fn), + {'d', "daemonize", NULL, + gettext_noop ("do daemonize (detach from terminal)"), 0, + GNUNET_GETOPT_set_one, &do_daemonize}, + GNUNET_GETOPT_OPTION_HELP (NULL), + GNUNET_GETOPT_OPTION_LOGLEVEL (&loglev), + GNUNET_GETOPT_OPTION_LOGFILE (&logfile), + GNUNET_GETOPT_OPTION_VERSION (PACKAGE_VERSION " " VCS_VERSION), + GNUNET_GETOPT_OPTION_END + }; + err = 1; + do_daemonize = 0; + logfile = NULL; + loglev = NULL; + opt_cfg_fn = NULL; + xdg = getenv ("XDG_CONFIG_HOME"); + if (NULL != xdg) + GNUNET_asprintf (&cfg_fn, + "%s%s%s", + xdg, + DIR_SEPARATOR_STR, + GNUNET_OS_project_data_get ()->config_file); + else + cfg_fn = GNUNET_strdup (GNUNET_OS_project_data_get ()->user_config_file); + memset (&sctx, 0, sizeof (sctx)); + sctx.options = options; + sctx.ready_confirm_fd = -1; + sctx.ret = GNUNET_OK; + sctx.timeout = GNUNET_TIME_UNIT_FOREVER_REL; + sctx.task = task; + sctx.task_cls = task_cls; + sctx.service_name = service_name; + sctx.cfg = cfg = GNUNET_CONFIGURATION_create (); + + /* setup subsystems */ + ret = GNUNET_GETOPT_run (service_name, service_options, argc, argv); + if (GNUNET_SYSERR == ret) + goto shutdown; + if (GNUNET_NO == ret) + { + err = 0; + goto shutdown; + } + if (GNUNET_OK != GNUNET_log_setup (service_name, loglev, logfile)) + HANDLE_ERROR; + if (NULL == opt_cfg_fn) + opt_cfg_fn = GNUNET_strdup (cfg_fn); + if (GNUNET_YES == GNUNET_DISK_file_test (opt_cfg_fn)) + { + if (GNUNET_SYSERR == GNUNET_CONFIGURATION_load (cfg, opt_cfg_fn)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Malformed configuration file `%s', exit ...\n"), + opt_cfg_fn); + goto shutdown; + } + } + else + { + if (GNUNET_SYSERR == GNUNET_CONFIGURATION_load (cfg, NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Malformed configuration, exit ...\n")); + goto shutdown; + } + if (0 != strcmp (opt_cfg_fn, cfg_fn)) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Could not access configuration file `%s'\n"), + opt_cfg_fn); + } + if (GNUNET_OK != setup_service (&sctx)) + goto shutdown; + if ((1 == do_daemonize) && (GNUNET_OK != detach_terminal (&sctx))) + HANDLE_ERROR; + if (GNUNET_OK != set_user_id (&sctx)) + goto shutdown; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Service `%s' runs with configuration from `%s'\n", + service_name, + opt_cfg_fn); + if ((GNUNET_OK == + GNUNET_CONFIGURATION_get_value_number (sctx.cfg, "TESTING", + "SKEW_OFFSET", &skew_offset)) && + (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_number (sctx.cfg, "TESTING", + "SKEW_VARIANCE", &skew_variance))) + { + clock_offset = skew_offset - skew_variance; + GNUNET_TIME_set_offset (clock_offset); + LOG (GNUNET_ERROR_TYPE_DEBUG, "Skewing clock by %dll ms\n", clock_offset); + } + /* actually run service */ + err = 0; + GNUNET_SCHEDULER_run (&service_task, &sctx); + /* shutdown */ + if ((1 == do_daemonize) && (NULL != sctx.server)) + pid_file_delete (&sctx); + GNUNET_free_non_null (sctx.my_handlers); + +shutdown: + if (-1 != sctx.ready_confirm_fd) + { + if (1 != WRITE (sctx.ready_confirm_fd, err ? "I" : "S", 1)) + LOG_STRERROR (GNUNET_ERROR_TYPE_WARNING, "write"); + GNUNET_break (0 == CLOSE (sctx.ready_confirm_fd)); + } +#if HAVE_MALLINFO + { + char *counter; + + if ( (GNUNET_YES == + GNUNET_CONFIGURATION_have_value (sctx.cfg, service_name, + "GAUGER_HEAP")) && + (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (sctx.cfg, service_name, + "GAUGER_HEAP", + &counter)) ) + { + struct mallinfo mi; + + mi = mallinfo (); + GAUGER (service_name, counter, mi.usmblks, "blocks"); + GNUNET_free (counter); + } + } +#endif + GNUNET_CONFIGURATION_destroy (cfg); + i = 0; + if (NULL != sctx.addrs) + while (NULL != sctx.addrs[i]) + GNUNET_free (sctx.addrs[i++]); + GNUNET_free_non_null (sctx.addrs); + GNUNET_free_non_null (sctx.addrlens); + GNUNET_free_non_null (logfile); + GNUNET_free_non_null (loglev); + GNUNET_free (cfg_fn); + GNUNET_free_non_null (opt_cfg_fn); + GNUNET_free_non_null (sctx.v4_denied); + GNUNET_free_non_null (sctx.v6_denied); + GNUNET_free_non_null (sctx.v4_allowed); + GNUNET_free_non_null (sctx.v6_allowed); + + return err ? GNUNET_SYSERR : sctx.ret; +} + + +/** + * Run a service startup sequence within an existing + * initialized system. + * + * @param service_name our service name + * @param cfg configuration to use + * @param options service options + * @return NULL on error, service handle + */ +struct GNUNET_SERVICE_Context * +GNUNET_SERVICE_start (const char *service_name, + const struct GNUNET_CONFIGURATION_Handle *cfg, + enum GNUNET_SERVICE_Options options) +{ + int i; + struct GNUNET_SERVICE_Context *sctx; + + sctx = GNUNET_new (struct GNUNET_SERVICE_Context); + sctx->ready_confirm_fd = -1; /* no daemonizing */ + sctx->ret = GNUNET_OK; + sctx->timeout = GNUNET_TIME_UNIT_FOREVER_REL; + sctx->service_name = service_name; + sctx->cfg = cfg; + sctx->options = options; + + /* setup subsystems */ + if (GNUNET_OK != setup_service (sctx)) + { + GNUNET_SERVICE_stop (sctx); + return NULL; + } + if (NULL != sctx->lsocks) + sctx->server = + GNUNET_SERVER_create_with_sockets (&check_access, sctx, sctx->lsocks, + sctx->timeout, sctx->require_found); + else + sctx->server = + GNUNET_SERVER_create (&check_access, sctx, sctx->addrs, sctx->addrlens, + sctx->timeout, sctx->require_found); + + if (NULL == sctx->server) + { + GNUNET_SERVICE_stop (sctx); + return NULL; + } +#ifndef WINDOWS + if (NULL != sctx->addrs) + for (i = 0; NULL != sctx->addrs[i]; i++) + if ((AF_UNIX == sctx->addrs[i]->sa_family) + && ('\0' != ((const struct sockaddr_un *)sctx->addrs[i])->sun_path[0])) + GNUNET_DISK_fix_permissions (((const struct sockaddr_un *)sctx->addrs[i])->sun_path, + sctx->match_uid, + sctx->match_gid); +#endif + sctx->my_handlers = GNUNET_malloc (sizeof (defhandlers)); + GNUNET_memcpy (sctx->my_handlers, defhandlers, sizeof (defhandlers)); + i = 0; + while ((sctx->my_handlers[i].callback != NULL)) + sctx->my_handlers[i++].callback_cls = sctx; + GNUNET_SERVER_add_handlers (sctx->server, sctx->my_handlers); + return sctx; +} + + +/** + * Obtain the server used by a service. Note that the server must NOT + * be destroyed by the caller. + * + * @param ctx the service context returned from the start function + * @return handle to the server for this service, NULL if there is none + */ +struct GNUNET_SERVER_Handle * +GNUNET_SERVICE_get_server (struct GNUNET_SERVICE_Context *ctx) +{ + return ctx->server; +} + + +/** + * Get the NULL-terminated array of listen sockets for this service. + * + * @param ctx service context to query + * @return NULL if there are no listen sockets, otherwise NULL-terminated + * array of listen sockets. + */ +struct GNUNET_NETWORK_Handle *const* +GNUNET_SERVICE_get_listen_sockets (struct GNUNET_SERVICE_Context *ctx) +{ + return ctx->lsocks; +} + + +/** + * Stop a service that was started with "GNUNET_SERVICE_start". + * + * @param sctx the service context returned from the start function + */ +void +GNUNET_SERVICE_stop (struct GNUNET_SERVICE_Context *sctx) +{ + unsigned int i; + +#if HAVE_MALLINFO + { + char *counter; + + if ( (GNUNET_YES == + GNUNET_CONFIGURATION_have_value (sctx->cfg, sctx->service_name, + "GAUGER_HEAP")) && + (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (sctx->cfg, sctx->service_name, + "GAUGER_HEAP", + &counter)) ) + { + struct mallinfo mi; + + mi = mallinfo (); + GAUGER (sctx->service_name, counter, mi.usmblks, "blocks"); + GNUNET_free (counter); + } + } +#endif + if (NULL != sctx->shutdown_task) + { + GNUNET_SCHEDULER_cancel (sctx->shutdown_task); + sctx->shutdown_task = NULL; + } + if (NULL != sctx->server) + GNUNET_SERVER_destroy (sctx->server); + GNUNET_free_non_null (sctx->my_handlers); + if (NULL != sctx->addrs) + { + i = 0; + while (NULL != sctx->addrs[i]) + GNUNET_free (sctx->addrs[i++]); + GNUNET_free (sctx->addrs); + } + GNUNET_free_non_null (sctx->addrlens); + GNUNET_free_non_null (sctx->v4_denied); + GNUNET_free_non_null (sctx->v6_denied); + GNUNET_free_non_null (sctx->v4_allowed); + GNUNET_free_non_null (sctx->v6_allowed); + GNUNET_free (sctx); +} + + +/* end of service.c */ diff --git a/src/transport/test_plugin_transport.c b/src/transport/test_plugin_transport.c index be79d5499..1d92588ea 100644 --- a/src/transport/test_plugin_transport.c +++ b/src/transport/test_plugin_transport.c @@ -552,7 +552,7 @@ setup_plugin_environment () static int -handle_helper_message (void *cls, void *client, +handle_helper_message (void *cls, const struct GNUNET_MessageHeader *hdr) { return GNUNET_OK; -- cgit v1.2.3