From 65d6d14a16172c3a5128bb6732a3d1c35eeb0425 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 26 May 2018 16:06:07 +0200 Subject: xt transport for experiments: --- src/transport/plugin_transport_xt.c | 4101 +++++++++++++++++++++++++++++++++++ 1 file changed, 4101 insertions(+) create mode 100644 src/transport/plugin_transport_xt.c (limited to 'src/transport') diff --git a/src/transport/plugin_transport_xt.c b/src/transport/plugin_transport_xt.c new file mode 100644 index 000000000..9ed0f43c9 --- /dev/null +++ b/src/transport/plugin_transport_xt.c @@ -0,0 +1,4101 @@ +/* + This file is part of GNUnet + Copyright (C) 2002--2015 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 transport/plugin_transport_xt.c + * @brief Implementation of the TCP transport service + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_hello_lib.h" +#include "gnunet_constants.h" +#include "gnunet_util_lib.h" +#include "gnunet_nat_service.h" +#include "gnunet_protocols.h" +#include "gnunet_resolver_service.h" +#include "gnunet_signatures.h" +#include "gnunet_statistics_service.h" +#include "gnunet_transport_service.h" +#include "gnunet_transport_plugin.h" +#include "transport.h" + +#define LOG(kind,...) GNUNET_log_from (kind, "transport-xt",__VA_ARGS__) + +#define PLUGIN_NAME "xt" + +/** + * How long until we give up on establishing an NAT connection? + * Must be > 4 RTT + */ +#define NAT_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10) + +/** + * 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; + +/** + * @brief handle for a network service + */ +struct LEGACY_SERVICE_Context; + + +/** + * Stops a service that was started with #GNUNET_SERVICE_start(). + * + * @param srv service to stop + */ +void +LEGACY_SERVICE_stop (struct LEGACY_SERVICE_Context *srv); + + + +/** + * 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; + +}; + + +/** + * Options for the service (bitmask). + */ +enum LEGACY_SERVICE_Options +{ + /** + * Use defaults. Terminates all client connections and the listen + * sockets immediately upon receiving the shutdown signal. + */ + LEGACY_SERVICE_OPTION_NONE = 0, + + /** + * Do not trigger server shutdown on signal at all; instead, allow + * for the user to terminate the server explicitly when needed + * by calling #LEGACY_SERVICE_shutdown(). + */ + LEGACY_SERVICE_OPTION_MANUAL_SHUTDOWN = 1, + + /** + * Trigger a SOFT server shutdown on signals, allowing active + * non-monitor clients to complete their transactions. + */ + LEGACY_SERVICE_OPTION_SOFT_SHUTDOWN = 2 +}; + + + +/** + * 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 +(*LEGACY_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. + */ +struct WelcomeMessage +{ + /** + * Type is #GNUNET_MESSAGE_TYPE_TRANSPORT_TCP_WELCOME. + */ + struct GNUNET_MessageHeader header; + + /** + * Identity of the node connecting (TCP client) + */ + struct GNUNET_PeerIdentity clientIdentity; + +}; + +/** + * Basically a WELCOME message, but with the purpose + * of giving the waiting peer a client handle to use + */ +struct TCP_NAT_ProbeMessage +{ + /** + * Type is #GNUNET_MESSAGE_TYPE_TRANSPORT_TCP_NAT_PROBE. + */ + struct GNUNET_MessageHeader header; + + /** + * Identity of the sender of the message. + */ + struct GNUNET_PeerIdentity clientIdentity; + +}; +GNUNET_NETWORK_STRUCT_END + +/** + * Context for sending a NAT probe via TCP. + */ +struct TCPProbeContext +{ + + /** + * Active probes are kept in a DLL. + */ + struct TCPProbeContext *next; + + /** + * Active probes are kept in a DLL. + */ + struct TCPProbeContext *prev; + + /** + * Probe connection. + */ + struct GNUNET_CONNECTION_Handle *sock; + + /** + * Message to be sent. + */ + struct TCP_NAT_ProbeMessage message; + + /** + * Handle to the transmission. + */ + struct GNUNET_CONNECTION_TransmitHandle *transmit_handle; + + /** + * Transport plugin handle. + */ + struct Plugin *plugin; +}; + +/** + * Bits in the `options` field of TCP addresses. + */ +enum TcpAddressOptions +{ + + /** + * No bits set. + */ + TCP_OPTIONS_NONE = 0, + + /** + * See #HTTP_OPTIONS_VERIFY_CERTIFICATE. + */ + TCP_OPTIONS_RESERVED = 1, + + /** + * Enable TCP Stealth-style port knocking. + */ + TCP_OPTIONS_TCP_STEALTH = 2 +}; + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Network format for IPv4 addresses. + */ +struct IPv4TcpAddress +{ + /** + * Optional options and flags for this address, + * see `enum TcpAddressOptions` + */ + uint32_t options GNUNET_PACKED; + + /** + * IPv4 address, in network byte order. + */ + uint32_t ipv4_addr GNUNET_PACKED; + + /** + * Port number, in network byte order. + */ + uint16_t t4_port GNUNET_PACKED; + +}; + +/** + * Network format for IPv6 addresses. + */ +struct IPv6TcpAddress +{ + /** + * Optional flags for this address + * see `enum TcpAddressOptions` + */ + uint32_t options GNUNET_PACKED; + + /** + * IPv6 address. + */ + struct in6_addr ipv6_addr GNUNET_PACKED; + + /** + * Port number, in network byte order. + */ + uint16_t t6_port GNUNET_PACKED; + +}; +GNUNET_NETWORK_STRUCT_END + +/** + * Encapsulation of all of the state of the plugin. + */ +struct Plugin; + +/** + * Information kept for each message that is yet to + * be transmitted. + */ +struct PendingMessage +{ + + /** + * This is a doubly-linked list. + */ + struct PendingMessage *next; + + /** + * This is a doubly-linked list. + */ + struct PendingMessage *prev; + + /** + * The pending message + */ + const char *msg; + + /** + * Continuation function to call once the message + * has been sent. Can be NULL if there is no + * continuation to call. + */ + GNUNET_TRANSPORT_TransmitContinuation transmit_cont; + + /** + * Closure for @e transmit_cont. + */ + void *transmit_cont_cls; + + /** + * Timeout value for the pending message. + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * So that the gnunet-service-transport can group messages together, + * these pending messages need to accept a message buffer and size + * instead of just a `struct GNUNET_MessageHeader`. + */ + size_t message_size; + +}; + +/** + * Session handle for TCP connections. + */ +struct GNUNET_ATS_Session +{ + /** + * To whom are we talking to (set to our identity + * if we are still waiting for the welcome message) + */ + struct GNUNET_PeerIdentity target; + + /** + * Pointer to the global plugin struct. + */ + struct Plugin *plugin; + + /** + * The client (used to identify this connection) + */ + struct GNUNET_SERVER_Client *client; + + /** + * Task cleaning up a NAT client connection establishment attempt; + */ + struct GNUNET_SCHEDULER_Task *nat_connection_timeout; + + /** + * Messages currently pending for transmission + * to this peer, if any. + */ + struct PendingMessage *pending_messages_head; + + /** + * Messages currently pending for transmission + * to this peer, if any. + */ + struct PendingMessage *pending_messages_tail; + + /** + * Handle for pending transmission request. + */ + struct GNUNET_SERVER_TransmitHandle *transmit_handle; + + /** + * Address of the other peer. + */ + struct GNUNET_HELLO_Address *address; + + /** + * ID of task used to delay receiving more to throttle sender. + */ + struct GNUNET_SCHEDULER_Task *receive_delay_task; + + /** + * Session timeout task + */ + struct GNUNET_SCHEDULER_Task *timeout_task; + + /** + * When will this session time out? + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * When will we continue to read from the socket? + * (used to enforce inbound quota). + */ + struct GNUNET_TIME_Absolute receive_delay; + + /** + * Last activity on this connection. Used to select preferred + * connection. + */ + struct GNUNET_TIME_Absolute last_activity; + + /** + * Number of bytes waiting for transmission to this peer. + */ + unsigned long long bytes_in_queue; + + /** + * Number of messages waiting for transmission to this peer. + */ + unsigned int msgs_in_queue; + + /** + * Network type of the address. + */ + enum GNUNET_ATS_Network_Type scope; + + /** + * Are we still expecting the welcome message? (#GNUNET_YES/#GNUNET_NO) + */ + int expecting_welcome; + + /** + * Was this session created using NAT traversal? + */ + int is_nat; + +}; + + +/** + * Context for address to string conversion, closure + * for #append_port(). + */ +struct PrettyPrinterContext +{ + /** + * DLL + */ + struct PrettyPrinterContext *next; + + /** + * DLL + */ + struct PrettyPrinterContext *prev; + + /** + * Our plugin. + */ + struct Plugin *plugin; + + /** + * Timeout task + */ + struct GNUNET_SCHEDULER_Task *timeout_task; + + /** + * Resolver handle + */ + struct GNUNET_RESOLVER_RequestHandle *resolver_handle; + + /** + * Function to call with the result. + */ + GNUNET_TRANSPORT_AddressStringCallback asc; + + /** + * Clsoure for @e asc. + */ + void *asc_cls; + + /** + * IPv6 address + */ + int ipv6; + + /** + * Options + */ + uint32_t options; + + /** + * Port to add after the IP address. + */ + uint16_t port; +}; + + +/** + * Encapsulation of all of the state of the plugin. + */ +struct Plugin +{ + /** + * Our environment. + */ + struct GNUNET_TRANSPORT_PluginEnvironment *env; + + /** + * The listen socket. + */ + struct GNUNET_CONNECTION_Handle *lsock; + + /** + * Our handle to the NAT module. + */ + struct GNUNET_NAT_Handle *nat; + + /** + * Map from peer identities to sessions for the given peer. + */ + struct GNUNET_CONTAINER_MultiPeerMap *sessionmap; + + /** + * Handle to the network service. + */ + struct LEGACY_SERVICE_Context *service; + + /** + * Handle to the server for this service. + */ + struct GNUNET_SERVER_Handle *server; + + /** + * Copy of the handler array where the closures are + * set to this struct's instance. + */ + struct GNUNET_SERVER_MessageHandler *handlers; + + /** + * Map of peers we have tried to contact behind a NAT + */ + struct GNUNET_CONTAINER_MultiPeerMap *nat_wait_conns; + + /** + * List of active TCP probes. + */ + struct TCPProbeContext *probe_head; + + /** + * List of active TCP probes. + */ + struct TCPProbeContext *probe_tail; + + /** + * Function to call about session status changes. + */ + GNUNET_TRANSPORT_SessionInfoCallback sic; + + /** + * Closure for @e sic. + */ + void *sic_cls; + + /** + * ID of task used to update our addresses when one expires. + */ + struct GNUNET_SCHEDULER_Task *address_update_task; + + /** + * Running pretty printers: head + */ + struct PrettyPrinterContext *ppc_dll_head; + + /** + * Running pretty printers: tail + */ + struct PrettyPrinterContext *ppc_dll_tail; + + /** + * Welcome message used by this peer. + */ + struct WelcomeMessage my_welcome; + + /** + * How many more TCP sessions are we allowed to open right now? + */ + unsigned long long max_connections; + + /** + * How many more TCP sessions do we have right now? + */ + unsigned long long cur_connections; + + /** + * Address options + */ + uint32_t myoptions; + + /** + * Port that we are actually listening on. + */ + uint16_t open_port; + + /** + * Port that the user said we would have visible to the + * rest of the world. + */ + uint16_t adv_port; + +}; + + +/** + * 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). + */ +static int +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)) + { + GNUNET_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)) + { + GNUNET_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; +} +/* end ancient copy-and-paste */ + + +/** + * If a session monitor is attached, notify it about the new + * session state. + * + * @param plugin our plugin + * @param session session that changed state + * @param state new state of the session + */ +static void +notify_session_monitor (struct Plugin *plugin, + struct GNUNET_ATS_Session *session, + enum GNUNET_TRANSPORT_SessionState state) +{ + struct GNUNET_TRANSPORT_SessionInfo info; + + if (NULL == plugin->sic) + return; + memset (&info, 0, sizeof (info)); + info.state = state; + info.is_inbound = GNUNET_HELLO_address_check_option (session->address, + GNUNET_HELLO_ADDRESS_INFO_INBOUND); + info.num_msg_pending = session->msgs_in_queue; + info.num_bytes_pending = session->bytes_in_queue; + if (NULL != session->receive_delay_task) + info.receive_delay = session->receive_delay; + info.session_timeout = session->timeout; + info.address = session->address; + plugin->sic (plugin->sic_cls, + session, + &info); +} + + +/** + * Our external IP address/port mapping has changed. + * + * @param cls closure, the `struct Plugin` + * @param add_remove #GNUNET_YES to mean the new public IP address, #GNUNET_NO to mean + * the previous (now invalid) one + * @param ac address class the address belongs to + * @param addr either the previous or the new public IP address + * @param addrlen actual length of @a addr + */ +static void +tcp_nat_port_map_callback (void *cls, + int add_remove, + enum GNUNET_NAT_AddressClass ac, + const struct sockaddr *addr, + socklen_t addrlen) +{ + struct Plugin *plugin = cls; + struct GNUNET_HELLO_Address *address; + struct IPv4TcpAddress t4; + struct IPv6TcpAddress t6; + void *arg; + size_t args; + + LOG (GNUNET_ERROR_TYPE_INFO, + "NAT notification to %s address `%s'\n", + (GNUNET_YES == add_remove) ? "add" : "remove", + GNUNET_a2s (addr, addrlen)); + /* convert 'addr' to our internal format */ + switch (addr->sa_family) + { + case AF_INET: + GNUNET_assert(addrlen == sizeof(struct sockaddr_in)); + memset (&t4, 0, sizeof(t4)); + t4.options = htonl (plugin->myoptions); + t4.ipv4_addr = ((struct sockaddr_in *) addr)->sin_addr.s_addr; + t4.t4_port = ((struct sockaddr_in *) addr)->sin_port; + arg = &t4; + args = sizeof (t4); + break; + case AF_INET6: + GNUNET_assert(addrlen == sizeof(struct sockaddr_in6)); + memset (&t6, 0, sizeof(t6)); + GNUNET_memcpy (&t6.ipv6_addr, + &((struct sockaddr_in6 *) addr)->sin6_addr, + sizeof(struct in6_addr)); + t6.options = htonl (plugin->myoptions); + t6.t6_port = ((struct sockaddr_in6 *) addr)->sin6_port; + arg = &t6; + args = sizeof (t6); + break; + default: + GNUNET_break(0); + return; + } + /* modify our published address list */ + GNUNET_assert ((args == sizeof (struct IPv4TcpAddress)) || + (args == sizeof (struct IPv6TcpAddress))); + /* TODO: use 'ac' here in the future... */ + address = GNUNET_HELLO_address_allocate (plugin->env->my_identity, + PLUGIN_NAME, + arg, + args, + GNUNET_HELLO_ADDRESS_INFO_NONE); + plugin->env->notify_address (plugin->env->cls, + add_remove, + address); + GNUNET_HELLO_address_free (address); +} + + +/** + * Function called for a quick conversion of the binary address to + * a numeric address. Note that the caller must not free the + * address and that the next call to this function is allowed + * to override the address again. + * + * @param cls closure (`struct Plugin*`) + * @param addr binary address + * @param addrlen length of @a addr + * @return string representing the same address + */ +static const char * +tcp_plugin_address_to_string (void *cls, + const void *addr, + size_t addrlen) +{ + static char rbuf[INET6_ADDRSTRLEN + 12]; + char buf[INET6_ADDRSTRLEN]; + const void *sb; + struct in_addr a4; + struct in6_addr a6; + const struct IPv4TcpAddress *t4; + const struct IPv6TcpAddress *t6; + int af; + uint16_t port; + uint32_t options; + + switch (addrlen) + { + case sizeof(struct IPv6TcpAddress): + t6 = addr; + af = AF_INET6; + port = ntohs (t6->t6_port); + options = ntohl (t6->options); + GNUNET_memcpy (&a6, &t6->ipv6_addr, sizeof(a6)); + sb = &a6; + break; + case sizeof(struct IPv4TcpAddress): + t4 = addr; + af = AF_INET; + port = ntohs (t4->t4_port); + options = ntohl (t4->options); + GNUNET_memcpy (&a4, &t4->ipv4_addr, sizeof(a4)); + sb = &a4; + break; + default: + LOG (GNUNET_ERROR_TYPE_WARNING, + _("Unexpected address length: %u bytes\n"), + (unsigned int) addrlen); + return NULL ; + } + if (NULL == inet_ntop (af, sb, buf, INET6_ADDRSTRLEN)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "inet_ntop"); + return NULL ; + } + GNUNET_snprintf (rbuf, sizeof(rbuf), + (af == AF_INET6) ? "%s.%u.[%s]:%u" : "%s.%u.%s:%u", + PLUGIN_NAME, + options, + buf, + port); + return rbuf; +} + + +/** + * Function called to convert a string address to + * a binary address. + * + * @param cls closure (`struct Plugin*`) + * @param addr string address + * @param addrlen length of the address + * @param buf location to store the buffer + * @param added location to store the number of bytes in the buffer. + * If the function returns #GNUNET_SYSERR, its contents are undefined. + * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure + */ +static int +tcp_plugin_string_to_address (void *cls, + const char *addr, + uint16_t addrlen, + void **buf, + size_t *added) +{ + struct sockaddr_storage socket_address; + char *address; + char *plugin; + char *optionstr; + uint32_t options; + + /* Format tcp.options.address:port */ + address = NULL; + plugin = NULL; + optionstr = NULL; + if ((NULL == addr) || (0 == addrlen)) + { + GNUNET_break(0); + return GNUNET_SYSERR; + } + if ('\0' != addr[addrlen - 1]) + { + GNUNET_break(0); + return GNUNET_SYSERR; + } + if (strlen (addr) != addrlen - 1) + { + GNUNET_break(0); + return GNUNET_SYSERR; + } + plugin = GNUNET_strdup (addr); + optionstr = strchr (plugin, '.'); + if (NULL == optionstr) + { + GNUNET_break(0); + GNUNET_free(plugin); + return GNUNET_SYSERR; + } + optionstr[0] = '\0'; + optionstr++; + options = atol (optionstr); + address = strchr (optionstr, '.'); + if (NULL == address) + { + GNUNET_break(0); + GNUNET_free(plugin); + return GNUNET_SYSERR; + } + address[0] = '\0'; + address++; + + if (GNUNET_OK != + GNUNET_STRINGS_to_address_ip (address, + strlen (address), + &socket_address)) + { + GNUNET_break(0); + GNUNET_free(plugin); + return GNUNET_SYSERR; + } + + GNUNET_free(plugin); + switch (socket_address.ss_family) + { + case AF_INET: + { + struct IPv4TcpAddress *t4; + struct sockaddr_in *in4 = (struct sockaddr_in *) &socket_address; + t4 = GNUNET_new (struct IPv4TcpAddress); + t4->options = htonl (options); + t4->ipv4_addr = in4->sin_addr.s_addr; + t4->t4_port = in4->sin_port; + *buf = t4; + *added = sizeof(struct IPv4TcpAddress); + return GNUNET_OK; + } + case AF_INET6: + { + struct IPv6TcpAddress *t6; + struct sockaddr_in6 *in6 = (struct sockaddr_in6 *) &socket_address; + t6 = GNUNET_new (struct IPv6TcpAddress); + t6->options = htonl (options); + t6->ipv6_addr = in6->sin6_addr; + t6->t6_port = in6->sin6_port; + *buf = t6; + *added = sizeof(struct IPv6TcpAddress); + return GNUNET_OK; + } + default: + return GNUNET_SYSERR; + } +} + + +/** + * Find the session handle for the given client. + * Currently uses both the hashmap and the client + * context, as the client context is new and the + * logic still needs to be tested. + * + * @param plugin the plugin + * @param client which client to find the session handle for + * @return NULL if no matching session exists + */ +static struct GNUNET_ATS_Session * +lookup_session_by_client (struct Plugin *plugin, + struct GNUNET_SERVER_Client *client) +{ + return GNUNET_SERVER_client_get_user_context (client, + struct GNUNET_ATS_Session); +} + + +/** + * Functions with this signature are called whenever we need + * to close a session due to a disconnect or failure to + * establish a connection. + * + * @param cls the `struct Plugin` + * @param session session to close down + * @return #GNUNET_OK on success + */ +static int +tcp_plugin_disconnect_session (void *cls, + struct GNUNET_ATS_Session *session) +{ + struct Plugin *plugin = cls; + struct PendingMessage *pm; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Disconnecting session of peer `%s' address `%s'\n", + GNUNET_i2s (&session->target), + tcp_plugin_address_to_string (session->plugin, + session->address->address, + session->address->address_length)); + + if (NULL != session->timeout_task) + { + GNUNET_SCHEDULER_cancel (session->timeout_task); + session->timeout_task = NULL; + session->timeout = GNUNET_TIME_UNIT_ZERO_ABS; + } + + if (GNUNET_YES == + GNUNET_CONTAINER_multipeermap_remove (plugin->sessionmap, + &session->target, + session)) + { + GNUNET_STATISTICS_update (session->plugin->env->stats, + gettext_noop ("# TCP sessions active"), + -1, + GNUNET_NO); + } + else + { + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multipeermap_remove (plugin->nat_wait_conns, + &session->target, + session)); + } + if (NULL != session->client) + GNUNET_SERVER_client_set_user_context (session->client, + NULL); + + /* clean up state */ + if (NULL != session->transmit_handle) + { + GNUNET_SERVER_notify_transmit_ready_cancel (session->transmit_handle); + session->transmit_handle = NULL; + } + session->plugin->env->session_end (session->plugin->env->cls, + session->address, + session); + + if (NULL != session->nat_connection_timeout) + { + GNUNET_SCHEDULER_cancel (session->nat_connection_timeout); + session->nat_connection_timeout = NULL; + } + + while (NULL != (pm = session->pending_messages_head)) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + (NULL != pm->transmit_cont) + ? "Could not deliver message to `%s' at %s.\n" + : "Could not deliver message to `%s' at %s, notifying.\n", + GNUNET_i2s (&session->target), + tcp_plugin_address_to_string (session->plugin, + session->address->address, + session->address->address_length)); + GNUNET_STATISTICS_update (session->plugin->env->stats, + gettext_noop ("# bytes currently in TCP buffers"), + -(int64_t) pm->message_size, GNUNET_NO); + GNUNET_STATISTICS_update (session->plugin->env->stats, + gettext_noop ("# bytes discarded by TCP (disconnect)"), + pm->message_size, + GNUNET_NO); + GNUNET_CONTAINER_DLL_remove (session->pending_messages_head, + session->pending_messages_tail, + pm); + GNUNET_assert (0 < session->msgs_in_queue); + session->msgs_in_queue--; + GNUNET_assert (pm->message_size <= session->bytes_in_queue); + session->bytes_in_queue -= pm->message_size; + if (NULL != pm->transmit_cont) + pm->transmit_cont (pm->transmit_cont_cls, + &session->target, + GNUNET_SYSERR, + pm->message_size, + 0); + GNUNET_free (pm); + } + GNUNET_assert (0 == session->msgs_in_queue); + GNUNET_assert (0 == session->bytes_in_queue); + notify_session_monitor (session->plugin, + session, + GNUNET_TRANSPORT_SS_DONE); + + if (NULL != session->receive_delay_task) + { + GNUNET_SCHEDULER_cancel (session->receive_delay_task); + session->receive_delay_task = NULL; + } + if (NULL != session->client) + { + GNUNET_SERVER_client_disconnect (session->client); + session->client = NULL; + } + GNUNET_HELLO_address_free (session->address); + GNUNET_assert (NULL == session->transmit_handle); + GNUNET_free (session); + return GNUNET_OK; +} + + +/** + * Function that is called to get the keepalive factor. + * #GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT is divided by this number to + * calculate the interval between keepalive packets. + * + * @param cls closure with the `struct Plugin` + * @return keepalive factor + */ +static unsigned int +tcp_plugin_query_keepalive_factor (void *cls) +{ + return 3; +} + + +/** + * Session was idle for too long, so disconnect it + * + * @param cls the `struct GNUNET_ATS_Session` of the idle session + */ +static void +session_timeout (void *cls) +{ + struct GNUNET_ATS_Session *s = cls; + struct GNUNET_TIME_Relative left; + + s->timeout_task = NULL; + left = GNUNET_TIME_absolute_get_remaining (s->timeout); + if (0 != left.rel_value_us) + { + /* not actually our turn yet, but let's at least update + the monitor, it may think we're about to die ... */ + notify_session_monitor (s->plugin, + s, + GNUNET_TRANSPORT_SS_UPDATE); + s->timeout_task = GNUNET_SCHEDULER_add_delayed (left, + &session_timeout, + s); + return; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Session %p was idle for %s, disconnecting\n", + s, + GNUNET_STRINGS_relative_time_to_string (GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT, + GNUNET_YES)); + /* call session destroy function */ + tcp_plugin_disconnect_session (s->plugin, + s); +} + + +/** + * Increment session timeout due to activity. + * + * @param s session to increment timeout for + */ +static void +reschedule_session_timeout (struct GNUNET_ATS_Session *s) +{ + GNUNET_assert (NULL != s->timeout_task); + s->timeout = GNUNET_TIME_relative_to_absolute (GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT); +} + + +/** + * Create a new session. Also queues a welcome message. + * + * @param plugin the plugin + * @param address the address to create the session for + * @param scope network scope the address is from + * @param client client to use, reference counter must have already been increased + * @param is_nat this a NAT session, we should wait for a client to + * connect to us from an address, then assign that to + * the session + * @return new session object + */ +static struct GNUNET_ATS_Session * +create_session (struct Plugin *plugin, + const struct GNUNET_HELLO_Address *address, + enum GNUNET_ATS_Network_Type scope, + struct GNUNET_SERVER_Client *client, + int is_nat) +{ + struct GNUNET_ATS_Session *session; + struct PendingMessage *pm; + + if (GNUNET_YES != is_nat) + GNUNET_assert (NULL != client); + else + GNUNET_assert (NULL == client); + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Creating new session for peer `%s' at address %s\n", + GNUNET_i2s (&address->peer), + tcp_plugin_address_to_string (plugin, + address->address, + address->address_length)); + session = GNUNET_new (struct GNUNET_ATS_Session); + session->last_activity = GNUNET_TIME_absolute_get (); + session->plugin = plugin; + session->is_nat = is_nat; + if (NULL != client) + { + session->client = client; + GNUNET_SERVER_client_set_user_context (client, + session); + } + session->address = GNUNET_HELLO_address_copy (address); + session->target = address->peer; + session->expecting_welcome = GNUNET_YES; + session->scope = scope; + pm = GNUNET_malloc (sizeof (struct PendingMessage) + + sizeof (struct WelcomeMessage)); + pm->msg = (const char *) &pm[1]; + pm->message_size = sizeof(struct WelcomeMessage); + GNUNET_memcpy (&pm[1], + &plugin->my_welcome, + sizeof(struct WelcomeMessage)); + pm->timeout = GNUNET_TIME_UNIT_FOREVER_ABS; + GNUNET_STATISTICS_update (plugin->env->stats, + gettext_noop ("# bytes currently in TCP buffers"), + pm->message_size, + GNUNET_NO); + GNUNET_CONTAINER_DLL_insert (session->pending_messages_head, + session->pending_messages_tail, + pm); + session->msgs_in_queue++; + session->bytes_in_queue += pm->message_size; + session->timeout = GNUNET_TIME_relative_to_absolute (GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT); + session->timeout_task = GNUNET_SCHEDULER_add_delayed (GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT, + &session_timeout, + session); + notify_session_monitor (session->plugin, + session, + GNUNET_TRANSPORT_SS_INIT); + if (GNUNET_YES != is_nat) + { + GNUNET_STATISTICS_update (plugin->env->stats, + gettext_noop ("# TCP sessions active"), + 1, + GNUNET_NO); + notify_session_monitor (session->plugin, + session, + GNUNET_TRANSPORT_SS_UP); + } + else + { + notify_session_monitor (session->plugin, + session, + GNUNET_TRANSPORT_SS_HANDSHAKE); + } + return session; +} + + +/** + * If we have pending messages, ask the server to + * transmit them (schedule the respective tasks, etc.) + * + * @param session for which session should we do this + */ +static void +process_pending_messages (struct GNUNET_ATS_Session *session); + + +/** + * Function called to notify a client about the socket + * being ready to queue more data. "buf" will be + * NULL and "size" zero if the socket was closed for + * writing in the meantime. + * + * @param cls closure + * @param size number of bytes available in @a buf + * @param buf where the callee should write the message + * @return number of bytes written to @a buf + */ +static size_t +do_transmit (void *cls, + size_t size, + void *buf) +{ + struct GNUNET_ATS_Session *session = cls; + struct GNUNET_PeerIdentity pid; + struct Plugin *plugin; + struct PendingMessage *pos; + struct PendingMessage *hd; + struct PendingMessage *tl; + struct GNUNET_TIME_Absolute now; + char *cbuf; + size_t ret; + + session->transmit_handle = NULL; + plugin = session->plugin; + if (NULL == buf) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Timeout trying to transmit to peer `%s', discarding message queue.\n", + GNUNET_i2s (&session->target)); + /* timeout; cancel all messages that have already expired */ + hd = NULL; + tl = NULL; + ret = 0; + now = GNUNET_TIME_absolute_get (); + while ( (NULL != (pos = session->pending_messages_head)) && + (pos->timeout.abs_value_us <= now.abs_value_us) ) + { + GNUNET_CONTAINER_DLL_remove (session->pending_messages_head, + session->pending_messages_tail, + pos); + GNUNET_assert (0 < session->msgs_in_queue); + session->msgs_in_queue--; + GNUNET_assert (pos->message_size <= session->bytes_in_queue); + session->bytes_in_queue -= pos->message_size; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Failed to transmit %u byte message to `%s'.\n", + pos->message_size, + GNUNET_i2s (&session->target)); + ret += pos->message_size; + GNUNET_CONTAINER_DLL_insert_after (hd, + tl, + tl, + pos); + } + /* do this call before callbacks (so that if callbacks destroy + * session, they have a chance to cancel actions done by this + * call) */ + process_pending_messages (session); + pid = session->target; + /* no do callbacks and do not use session again since + * the callbacks may abort the session */ + while (NULL != (pos = hd)) + { + GNUNET_CONTAINER_DLL_remove (hd, + tl, + pos); + if (NULL != pos->transmit_cont) + pos->transmit_cont (pos->transmit_cont_cls, + &pid, + GNUNET_SYSERR, + pos->message_size, + 0); + GNUNET_free (pos); + } + GNUNET_STATISTICS_update (plugin->env->stats, + gettext_noop ("# bytes currently in TCP buffers"), -(int64_t) ret, + GNUNET_NO); + GNUNET_STATISTICS_update (plugin->env->stats, + gettext_noop ("# bytes discarded by TCP (timeout)"), + ret, + GNUNET_NO); + if (0 < ret) + notify_session_monitor (session->plugin, + session, + GNUNET_TRANSPORT_SS_UPDATE); + return 0; + } + /* copy all pending messages that would fit */ + ret = 0; + cbuf = buf; + hd = NULL; + tl = NULL; + while (NULL != (pos = session->pending_messages_head)) + { + if (ret + pos->message_size > size) + break; + GNUNET_CONTAINER_DLL_remove (session->pending_messages_head, + session->pending_messages_tail, + pos); + GNUNET_assert (0 < session->msgs_in_queue); + session->msgs_in_queue--; + GNUNET_assert (pos->message_size <= session->bytes_in_queue); + session->bytes_in_queue -= pos->message_size; + GNUNET_assert(size >= pos->message_size); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting message of type %u size %u to peer %s at %s\n", + ntohs (((struct GNUNET_MessageHeader *) pos->msg)->type), + pos->message_size, + GNUNET_i2s (&session->target), + tcp_plugin_address_to_string (session->plugin, + session->address->address, + session->address->address_length)); + /* FIXME: this GNUNET_memcpy can be up to 7% of our total runtime */ + GNUNET_memcpy (cbuf, + pos->msg, + pos->message_size); + cbuf += pos->message_size; + ret += pos->message_size; + size -= pos->message_size; + GNUNET_CONTAINER_DLL_insert_tail (hd, + tl, + pos); + } + notify_session_monitor (session->plugin, + session, + GNUNET_TRANSPORT_SS_UPDATE); + /* schedule 'continuation' before callbacks so that callbacks that + * cancel everything don't cause us to use a session that no longer + * exists... */ + process_pending_messages (session); + session->last_activity = GNUNET_TIME_absolute_get (); + pid = session->target; + /* we'll now call callbacks that may cancel the session; hence + * we should not use 'session' after this point */ + while (NULL != (pos = hd)) + { + GNUNET_CONTAINER_DLL_remove (hd, tl, pos); + if (NULL != pos->transmit_cont) + pos->transmit_cont (pos->transmit_cont_cls, + &pid, + GNUNET_OK, + pos->message_size, + pos->message_size); /* FIXME: include TCP overhead */ + GNUNET_free (pos); + } + GNUNET_assert (NULL == hd); + GNUNET_assert (NULL == tl); + GNUNET_STATISTICS_update (plugin->env->stats, + gettext_noop ("# bytes currently in TCP buffers"), + - (int64_t) ret, + GNUNET_NO); + GNUNET_STATISTICS_update (plugin->env->stats, + gettext_noop ("# bytes transmitted via TCP"), + ret, + GNUNET_NO); + return ret; +} + + +/** + * If we have pending messages, ask the server to + * transmit them (schedule the respective tasks, etc.) + * + * @param session for which session should we do this + */ +static void +process_pending_messages (struct GNUNET_ATS_Session *session) +{ + struct PendingMessage *pm; + + GNUNET_assert (NULL != session->client); + if (NULL != session->transmit_handle) + return; + if (NULL == (pm = session->pending_messages_head)) + return; + + session->transmit_handle + = GNUNET_SERVER_notify_transmit_ready (session->client, + pm->message_size, + GNUNET_TIME_absolute_get_remaining (pm->timeout), + &do_transmit, + session); +} + + +/** + * Function that can be used by the transport service to transmit + * a message using the plugin. Note that in the case of a + * peer disconnecting, the continuation MUST be called + * prior to the disconnect notification itself. This function + * will be called with this peer's HELLO message to initiate + * a fresh connection to another peer. + * + * @param cls closure + * @param session which session must be used + * @param msgbuf the message to transmit + * @param msgbuf_size number of bytes in @a msgbuf + * @param priority how important is the message (most plugins will + * ignore message priority and just FIFO) + * @param to how long to wait at most for the transmission (does not + * require plugins to discard the message after the timeout, + * just advisory for the desired delay; most plugins will ignore + * this as well) + * @param cont continuation to call once the message has + * been transmitted (or if the transport is ready + * for the next transmission call; or if the + * peer disconnected...); can be NULL + * @param cont_cls closure for @a cont + * @return number of bytes used (on the physical network, with overheads); + * -1 on hard errors (i.e. address invalid); 0 is a legal value + * and does NOT mean that the message was not transmitted (DV) + */ +static ssize_t +tcp_plugin_send (void *cls, + struct GNUNET_ATS_Session *session, + const char *msgbuf, + size_t msgbuf_size, + unsigned int priority, + struct GNUNET_TIME_Relative to, + GNUNET_TRANSPORT_TransmitContinuation cont, + void *cont_cls) +{ + struct Plugin * plugin = cls; + struct PendingMessage *pm; + + /* create new message entry */ + pm = GNUNET_malloc (sizeof (struct PendingMessage) + msgbuf_size); + pm->msg = (const char *) &pm[1]; + GNUNET_memcpy (&pm[1], msgbuf, msgbuf_size); + pm->message_size = msgbuf_size; + pm->timeout = GNUNET_TIME_relative_to_absolute (to); + pm->transmit_cont = cont; + pm->transmit_cont_cls = cont_cls; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Asked to transmit %u bytes to `%s', added message to list.\n", + msgbuf_size, + GNUNET_i2s (&session->target)); + + if (GNUNET_YES == + GNUNET_CONTAINER_multipeermap_contains_value (plugin->sessionmap, + &session->target, + session)) + { + GNUNET_assert (NULL != session->client); + GNUNET_SERVER_client_set_timeout (session->client, + GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT); + GNUNET_STATISTICS_update (plugin->env->stats, + gettext_noop ("# bytes currently in TCP buffers"), + msgbuf_size, + GNUNET_NO); + + /* append pm to pending_messages list */ + GNUNET_CONTAINER_DLL_insert_tail (session->pending_messages_head, + session->pending_messages_tail, + pm); + notify_session_monitor (session->plugin, + session, + GNUNET_TRANSPORT_SS_UPDATE); + session->msgs_in_queue++; + session->bytes_in_queue += pm->message_size; + process_pending_messages (session); + return msgbuf_size; + } + if (GNUNET_YES == + GNUNET_CONTAINER_multipeermap_contains_value (plugin->nat_wait_conns, + &session->target, + session)) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "This NAT WAIT session for peer `%s' is not yet ready!\n", + GNUNET_i2s (&session->target)); + GNUNET_STATISTICS_update (plugin->env->stats, + gettext_noop ("# bytes currently in TCP buffers"), msgbuf_size, + GNUNET_NO); + /* append pm to pending_messages list */ + GNUNET_CONTAINER_DLL_insert_tail (session->pending_messages_head, + session->pending_messages_tail, + pm); + session->msgs_in_queue++; + session->bytes_in_queue += pm->message_size; + notify_session_monitor (session->plugin, + session, + GNUNET_TRANSPORT_SS_HANDSHAKE); + return msgbuf_size; + } + LOG (GNUNET_ERROR_TYPE_ERROR, + "Invalid session %p\n", + session); + if (NULL != cont) + cont (cont_cls, + &session->target, + GNUNET_SYSERR, + pm->message_size, + 0); + GNUNET_break (0); + GNUNET_free (pm); + return GNUNET_SYSERR; /* session does not exist here */ +} + + +/** + * Closure for #session_lookup_it(). + */ +struct GNUNET_ATS_SessionItCtx +{ + /** + * Address we are looking for. + */ + const struct GNUNET_HELLO_Address *address; + + /** + * Where to store the session (if we found it). + */ + struct GNUNET_ATS_Session *result; + +}; + + +/** + * Look for a session by address. + * + * @param cls the `struct GNUNET_ATS_SessionItCtx` + * @param key unused + * @param value a `struct GNUNET_ATS_Session` + * @return #GNUNET_YES to continue looking, #GNUNET_NO if we found the session + */ +static int +session_lookup_it (void *cls, + const struct GNUNET_PeerIdentity *key, + void *value) +{ + struct GNUNET_ATS_SessionItCtx *si_ctx = cls; + struct GNUNET_ATS_Session *session = value; + + if (0 != + GNUNET_HELLO_address_cmp (si_ctx->address, + session->address)) + return GNUNET_YES; + si_ctx->result = session; + return GNUNET_NO; +} + + +/** + * Task cleaning up a NAT connection attempt after timeout + * + * @param cls the `struct GNUNET_ATS_Session` + */ +static void +nat_connect_timeout (void *cls) +{ + struct GNUNET_ATS_Session *session = cls; + + session->nat_connection_timeout = NULL; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "NAT WAIT connection to `%4s' at `%s' could not be established, removing session\n", + GNUNET_i2s (&session->target), + tcp_plugin_address_to_string (session->plugin, + session->address->address, + session->address->address_length)); + tcp_plugin_disconnect_session (session->plugin, + session); +} + + +/** + * Function that will be called whenever the transport service wants to + * notify the plugin that a session is still active and in use and + * therefore the session timeout for this session has to be updated + * + * @param cls closure + * @param peer which peer was the session for + * @param session which session is being updated + */ +static void +tcp_plugin_update_session_timeout (void *cls, + const struct GNUNET_PeerIdentity *peer, + struct GNUNET_ATS_Session *session) +{ + reschedule_session_timeout (session); +} + + +/** + * Task to signal the server that we can continue + * receiving from the TCP client now. + * + * @param cls the `struct GNUNET_ATS_Session *` + */ +static void +delayed_done (void *cls) +{ + struct GNUNET_ATS_Session *session = cls; + + session->receive_delay_task = NULL; + reschedule_session_timeout (session); + GNUNET_SERVER_receive_done (session->client, + GNUNET_OK); +} + + +/** + * Function that will be called whenever the transport service wants to + * notify the plugin that the inbound quota changed and that the plugin + * should update it's delay for the next receive value + * + * @param cls closure + * @param peer which peer was the session for + * @param session which session is being updated + * @param delay new delay to use for receiving + */ +static void +tcp_plugin_update_inbound_delay (void *cls, + const struct GNUNET_PeerIdentity *peer, + struct GNUNET_ATS_Session *session, + struct GNUNET_TIME_Relative delay) +{ + if (NULL == session->receive_delay_task) + return; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "New inbound delay %s\n", + GNUNET_STRINGS_relative_time_to_string (delay, + GNUNET_NO)); + session->receive_delay = GNUNET_TIME_relative_to_absolute (delay); + GNUNET_SCHEDULER_cancel (session->receive_delay_task); + session->receive_delay_task = GNUNET_SCHEDULER_add_delayed (delay, + &delayed_done, + session); +} + + +/** + * Create a new session to transmit data to the target + * This session will used to send data to this peer and the plugin will + * notify us by calling the env->session_end function + * + * @param cls closure + * @param address the address to use + * @return the session if the address is valid, NULL otherwise + */ +static struct GNUNET_ATS_Session * +tcp_plugin_get_session (void *cls, + const struct GNUNET_HELLO_Address *address) +{ + struct Plugin *plugin = cls; + struct GNUNET_ATS_Session *session = NULL; + int af; + const void *sb; + size_t sbs; + struct GNUNET_CONNECTION_Handle *sa; + struct sockaddr_in a4; + struct sockaddr_in6 a6; + const struct IPv4TcpAddress *t4; + const struct IPv6TcpAddress *t6; + unsigned int options; + enum GNUNET_ATS_Network_Type net_type; + unsigned int is_natd = GNUNET_NO; + size_t addrlen; +#ifdef TCP_STEALTH + struct GNUNET_NETWORK_Handle *s; +#endif + + addrlen = address->address_length; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Trying to get session for `%s' address of peer `%s'\n", + tcp_plugin_address_to_string (plugin, + address->address, + address->address_length), + GNUNET_i2s (&address->peer)); + + if (GNUNET_HELLO_address_check_option (address, + GNUNET_HELLO_ADDRESS_INFO_INBOUND)) + { + GNUNET_break (0); + return NULL; + } + + /* look for existing session */ + if (GNUNET_YES == + GNUNET_CONTAINER_multipeermap_contains (plugin->sessionmap, + &address->peer)) + { + struct GNUNET_ATS_SessionItCtx si_ctx; + + si_ctx.address = address; + si_ctx.result = NULL; + GNUNET_CONTAINER_multipeermap_get_multiple (plugin->sessionmap, + &address->peer, + &session_lookup_it, + &si_ctx); + if (NULL != si_ctx.result) + { + session = si_ctx.result; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Found existing session for `%s' address `%s'\n", + GNUNET_i2s (&address->peer), + tcp_plugin_address_to_string (plugin, + address->address, + address->address_length)); + return session; + } + /* This is a bit of a hack, limiting TCP to never allow more than + one TCP connection to any given peer at the same time. + Without this, peers sometimes disagree about which of the TCP + connections they should use, causing one side to believe that + they transmit successfully, while the other receives nothing. */ + return NULL; /* Refuse to have more than one TCP connection per + peer pair at the same time. */ + } + + if (addrlen == sizeof(struct IPv6TcpAddress)) + { + GNUNET_assert (NULL != address->address); /* make static analysis happy */ + t6 = address->address; + options = t6->options; + af = AF_INET6; + memset (&a6, 0, sizeof(a6)); +#if HAVE_SOCKADDR_IN_SIN_LEN + a6.sin6_len = sizeof (a6); +#endif + a6.sin6_family = AF_INET6; + a6.sin6_port = t6->t6_port; + if (t6->t6_port == 0) + is_natd = GNUNET_YES; + GNUNET_memcpy (&a6.sin6_addr, &t6->ipv6_addr, sizeof(struct in6_addr)); + sb = &a6; + sbs = sizeof(a6); + } + else if (addrlen == sizeof(struct IPv4TcpAddress)) + { + GNUNET_assert(NULL != address->address); /* make static analysis happy */ + t4 = address->address; + options = t4->options; + af = AF_INET; + memset (&a4, 0, sizeof(a4)); +#if HAVE_SOCKADDR_IN_SIN_LEN + a4.sin_len = sizeof (a4); +#endif + a4.sin_family = AF_INET; + a4.sin_port = t4->t4_port; + if (t4->t4_port == 0) + is_natd = GNUNET_YES; + a4.sin_addr.s_addr = t4->ipv4_addr; + sb = &a4; + sbs = sizeof(a4); + } + else + { + GNUNET_STATISTICS_update (plugin->env->stats, + gettext_noop ("# requests to create session with invalid address"), + 1, + GNUNET_NO); + return NULL; + } + + net_type = plugin->env->get_address_type (plugin->env->cls, + sb, + sbs); + GNUNET_break (net_type != GNUNET_ATS_NET_UNSPECIFIED); + + if ( (is_natd == GNUNET_YES) && + (addrlen == sizeof(struct IPv6TcpAddress)) ) + { + /* NAT client only works with IPv4 addresses */ + return NULL; + } + + if (plugin->cur_connections >= plugin->max_connections) + { + /* saturated */ + return NULL; + } + + if ( (is_natd == GNUNET_YES) && + (GNUNET_YES == + GNUNET_CONTAINER_multipeermap_contains (plugin->nat_wait_conns, + &address->peer))) + { + /* Only do one NAT punch attempt per peer identity */ + return NULL; + } + + if ( (is_natd == GNUNET_YES) && + (NULL != plugin->nat) && + (GNUNET_NO == + GNUNET_CONTAINER_multipeermap_contains (plugin->nat_wait_conns, + &address->peer))) + { + struct sockaddr_in local_sa; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Found valid IPv4 NAT address (creating session)!\n"); + session = create_session (plugin, + address, + net_type, + NULL, + GNUNET_YES); + session->nat_connection_timeout = GNUNET_SCHEDULER_add_delayed (NAT_TIMEOUT, + &nat_connect_timeout, + session); + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multipeermap_put (plugin->nat_wait_conns, + &session->target, + session, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Created NAT WAIT connection to `%s' at `%s'\n", + GNUNET_i2s (&session->target), + GNUNET_a2s (sb, sbs)); + memset (&local_sa, + 0, + sizeof (local_sa)); + local_sa.sin_family = AF_INET; + local_sa.sin_port = htons (plugin->open_port); + /* We leave sin_address at 0, let the kernel figure it out, + even if our bind() is more specific. (May want to reconsider + later.) */ + if (GNUNET_OK == + GNUNET_NAT_request_reversal (plugin->nat, + &local_sa, + &a4)) + return session; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Running NAT client for `%s' at `%s' failed\n", + GNUNET_i2s (&session->target), + GNUNET_a2s (sb, sbs)); + tcp_plugin_disconnect_session (plugin, + session); + return NULL; + } + + /* create new outbound session */ + if (0 != (options & TCP_OPTIONS_TCP_STEALTH)) + { +#ifdef TCP_STEALTH + s = GNUNET_NETWORK_socket_create (af, SOCK_STREAM, 0); + if (NULL == s) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK, + "socket"); + sa = NULL; + } + else + { + if ( (GNUNET_OK != + GNUNET_NETWORK_socket_setsockopt (s, + IPPROTO_TCP, + TCP_STEALTH, + &session->target, + sizeof (struct GNUNET_PeerIdentity))) || + (GNUNET_OK != + GNUNET_NETWORK_socket_setsockopt (s, + IPPROTO_TCP, + TCP_STEALTH_INTEGRITY, + &plugin->my_welcome, + sizeof (struct WelcomeMessage))) ) + { + /* TCP STEALTH not supported by kernel */ + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (s)); + sa = NULL; + } + else + { + sa = GNUNET_CONNECTION_connect_socket (s, sb, sbs); + } + } +#else + sa = NULL; +#endif + } + else + { + sa = GNUNET_CONNECTION_create_from_sockaddr (af, sb, sbs); + } + if (NULL == sa) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Failed to create connection to `%s' at `%s'\n", + GNUNET_i2s (&address->peer), + GNUNET_a2s (sb, sbs)); + return NULL; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Asked to transmit to `%s', creating fresh session using address `%s'.\n", + GNUNET_i2s (&address->peer), + GNUNET_a2s (sb, sbs)); + + session = create_session (plugin, + address, + net_type, + GNUNET_SERVER_connect_socket (plugin->server, + sa), + GNUNET_NO); + (void) GNUNET_CONTAINER_multipeermap_put (plugin->sessionmap, + &session->target, + session, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + /* Send TCP Welcome */ + process_pending_messages (session); + + return session; +} + + +/** + * We have been asked to destroy all connections to a particular peer. + * This function is called on each applicable session and must tear it + * down. + * + * @param cls the `struct Plugin *` + * @param key the peer which the session belongs to (unused) + * @param value the `struct GNUNET_ATS_Session` + * @return #GNUNET_YES (continue to iterate) + */ +static int +session_disconnect_it (void *cls, + const struct GNUNET_PeerIdentity *key, + void *value) +{ + struct Plugin *plugin = cls; + struct GNUNET_ATS_Session *session = value; + + GNUNET_STATISTICS_update (session->plugin->env->stats, + gettext_noop ("# transport-service disconnect requests for TCP"), + 1, + GNUNET_NO); + tcp_plugin_disconnect_session (plugin, + session); + return GNUNET_YES; +} + + +/** + * Function that can be called to force a disconnect from the + * specified neighbour. This should also cancel all previously + * scheduled transmissions. Obviously the transmission may have been + * partially completed already, which is OK. The plugin is supposed + * to close the connection (if applicable) and no longer call the + * transmit continuation(s). + * + * Finally, plugin MUST NOT call the services's receive function to + * notify the service that the connection to the specified target was + * closed after a getting this call. + * + * @param cls closure + * @param target peer for which the last transmission is + * to be cancelled + */ +static void +tcp_plugin_disconnect (void *cls, + const struct GNUNET_PeerIdentity *target) +{ + struct Plugin *plugin = cls; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Disconnecting peer `%s'\n", + GNUNET_i2s (target)); + GNUNET_CONTAINER_multipeermap_get_multiple (plugin->sessionmap, + target, + &session_disconnect_it, + plugin); + GNUNET_CONTAINER_multipeermap_get_multiple (plugin->nat_wait_conns, + target, + &session_disconnect_it, + plugin); +} + + +/** + * We are processing an address pretty printing request and finished + * the IP resolution (if applicable). Append our port and forward the + * result. If called with @a hostname NULL, we are done and should + * clean up the pretty printer (otherwise, there might be multiple + * hostnames for the IP address and we might receive more). + * + * @param cls the `struct PrettyPrinterContext *` + * @param hostname hostname part of the address + */ +static void +append_port (void *cls, + const char *hostname) +{ + struct PrettyPrinterContext *ppc = cls; + struct Plugin *plugin = ppc->plugin; + char *ret; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "append_port called with hostname `%s'\n", + hostname); + if (NULL == hostname) + { + /* Final call, done */ + ppc->resolver_handle = NULL; + GNUNET_CONTAINER_DLL_remove (plugin->ppc_dll_head, + plugin->ppc_dll_tail, + ppc); + ppc->asc (ppc->asc_cls, + NULL, + GNUNET_OK); + GNUNET_free (ppc); + return; + } + if (GNUNET_YES == ppc->ipv6) + GNUNET_asprintf (&ret, + "%s.%u.[%s]:%d", + PLUGIN_NAME, + ppc->options, + hostname, + ppc->port); + else + GNUNET_asprintf (&ret, + "%s.%u.%s:%d", + PLUGIN_NAME, + ppc->options, + hostname, + ppc->port); + ppc->asc (ppc->asc_cls, + ret, + GNUNET_OK); + GNUNET_free (ret); +} + + +/** + * Convert the transports address to a nice, human-readable format. + * + * @param cls closure with the `struct Plugin` + * @param type name of the transport that generated the address + * @param addr one of the addresses of the host, NULL for the last address + * the specific address format depends on the transport + * @param addrlen length of the @a addr + * @param numeric should (IP) addresses be displayed in numeric form? + * @param timeout after how long should we give up? + * @param asc function to call on each string + * @param asc_cls closure for @a asc + */ +static void +tcp_plugin_address_pretty_printer (void *cls, + const char *type, + const void *addr, + size_t addrlen, + int numeric, + struct GNUNET_TIME_Relative timeout, + GNUNET_TRANSPORT_AddressStringCallback asc, + void *asc_cls) +{ + struct Plugin *plugin = cls; + struct PrettyPrinterContext *ppc; + const void *sb; + size_t sbs; + struct sockaddr_in a4; + struct sockaddr_in6 a6; + const struct IPv4TcpAddress *t4; + const struct IPv6TcpAddress *t6; + uint16_t port; + uint32_t options; + + if (sizeof(struct IPv6TcpAddress) == addrlen) + { + t6 = addr; + memset (&a6, 0, sizeof(a6)); + a6.sin6_family = AF_INET6; + a6.sin6_port = t6->t6_port; + GNUNET_memcpy (&a6.sin6_addr, &t6->ipv6_addr, sizeof(struct in6_addr)); + port = ntohs (t6->t6_port); + options = ntohl (t6->options); + sb = &a6; + sbs = sizeof(a6); + } + else if (sizeof(struct IPv4TcpAddress) == addrlen) + { + t4 = addr; + memset (&a4, 0, sizeof(a4)); + a4.sin_family = AF_INET; + a4.sin_port = t4->t4_port; + a4.sin_addr.s_addr = t4->ipv4_addr; + port = ntohs (t4->t4_port); + options = ntohl (t4->options); + sb = &a4; + sbs = sizeof(a4); + } + else + { + /* invalid address */ + LOG (GNUNET_ERROR_TYPE_WARNING, + _("Unexpected address length: %u bytes\n"), + (unsigned int) addrlen); + asc (asc_cls, NULL, GNUNET_SYSERR); + asc (asc_cls, NULL, GNUNET_OK); + return; + } + ppc = GNUNET_new (struct PrettyPrinterContext); + ppc->plugin = plugin; + if (addrlen == sizeof(struct IPv6TcpAddress)) + ppc->ipv6 = GNUNET_YES; + else + ppc->ipv6 = GNUNET_NO; + ppc->asc = asc; + ppc->asc_cls = asc_cls; + ppc->port = port; + ppc->options = options; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting DNS reverse lookup\n"); + ppc->resolver_handle = GNUNET_RESOLVER_hostname_get (sb, + sbs, + ! numeric, + timeout, + &append_port, + ppc); + if (NULL == ppc->resolver_handle) + { + GNUNET_break (0); + GNUNET_free (ppc); + return; + } + GNUNET_CONTAINER_DLL_insert (plugin->ppc_dll_head, + plugin->ppc_dll_tail, + ppc); +} + + +/** + * Function that will be called to check if a binary address for this + * plugin is well-formed and corresponds to an address for THIS peer + * (as per our configuration). Naturally, if absolutely necessary, + * plugins can be a bit conservative in their answer, but in general + * plugins should make sure that the address does not redirect + * traffic to a 3rd party that might try to man-in-the-middle our + * traffic. + * + * @param cls closure, our `struct Plugin *` + * @param addr pointer to the address + * @param addrlen length of @a addr + * @return #GNUNET_OK if this is a plausible address for this peer + * and transport, #GNUNET_SYSERR if not + */ +static int +tcp_plugin_check_address (void *cls, + const void *addr, + size_t addrlen) +{ + struct Plugin *plugin = cls; + const struct IPv4TcpAddress *v4; + const struct IPv6TcpAddress *v6; + + if ( (addrlen != sizeof(struct IPv4TcpAddress)) && + (addrlen != sizeof(struct IPv6TcpAddress)) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (addrlen == sizeof(struct IPv4TcpAddress)) + { + struct sockaddr_in s4; + + v4 = (const struct IPv4TcpAddress *) addr; + if (0 != memcmp (&v4->options, + &plugin->myoptions, + sizeof(uint32_t))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + memset (&s4, 0, sizeof (s4)); + s4.sin_family = AF_INET; +#if HAVE_SOCKADDR_IN_SIN_LEN + s4.sin_len = sizeof (s4); +#endif + s4.sin_port = v4->t4_port; + s4.sin_addr.s_addr = v4->ipv4_addr; + + if (GNUNET_OK != + GNUNET_NAT_test_address (plugin->nat, + &s4, + sizeof (struct sockaddr_in))) + return GNUNET_SYSERR; + } + else + { + struct sockaddr_in6 s6; + + v6 = (const struct IPv6TcpAddress *) addr; + if (IN6_IS_ADDR_LINKLOCAL (&v6->ipv6_addr)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 != memcmp (&v6->options, + &plugin->myoptions, + sizeof (uint32_t))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + memset (&s6, 0, sizeof (s6)); + s6.sin6_family = AF_INET6; +#if HAVE_SOCKADDR_IN_SIN_LEN + s6.sin6_len = sizeof (s6); +#endif + s6.sin6_port = v6->t6_port; + s6.sin6_addr = v6->ipv6_addr; + + if (GNUNET_OK != + GNUNET_NAT_test_address (plugin->nat, + &s6, + sizeof(struct sockaddr_in6))) + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * We've received a nat probe from this peer via TCP. Finish + * creating the client session and resume sending of queued + * messages. + * + * @param cls closure + * @param client identification of the client + * @param message the actual message + */ +static void +handle_tcp_nat_probe (void *cls, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message) +{ + struct Plugin *plugin = cls; + struct GNUNET_ATS_Session *session; + const struct TCP_NAT_ProbeMessage *tcp_nat_probe; + size_t alen; + void *vaddr; + struct IPv4TcpAddress *t4; + struct IPv6TcpAddress *t6; + const struct sockaddr_in *s4; + const struct sockaddr_in6 *s6; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received NAT probe\n"); + /* We have received a TCP NAT probe, meaning we (hopefully) initiated + * a connection to this peer by running gnunet-nat-client. This peer + * received the punch message and now wants us to use the new connection + * as the default for that peer. Do so and then send a WELCOME message + * so we can really be connected! + */ + if (ntohs (message->size) != sizeof(struct TCP_NAT_ProbeMessage)) + { + GNUNET_break_op(0); + GNUNET_SERVER_receive_done (client, + GNUNET_SYSERR); + return; + } + + tcp_nat_probe = (const struct TCP_NAT_ProbeMessage *) message; + if (0 == memcmp (&tcp_nat_probe->clientIdentity, plugin->env->my_identity, + sizeof(struct GNUNET_PeerIdentity))) + { + /* refuse connections from ourselves */ + GNUNET_SERVER_receive_done (client, + GNUNET_SYSERR); + return; + } + + session = GNUNET_CONTAINER_multipeermap_get (plugin->nat_wait_conns, + &tcp_nat_probe->clientIdentity); + if (NULL == session) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Did NOT find session for NAT probe!\n"); + GNUNET_SERVER_receive_done (client, + GNUNET_OK); + return; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Found session for NAT probe!\n"); + + if (NULL != session->nat_connection_timeout) + { + GNUNET_SCHEDULER_cancel (session->nat_connection_timeout); + session->nat_connection_timeout = NULL; + } + + if (GNUNET_OK != + GNUNET_SERVER_client_get_address (client, + &vaddr, + &alen)) + { + GNUNET_break(0); + GNUNET_SERVER_receive_done (client, + GNUNET_SYSERR); + tcp_plugin_disconnect_session (plugin, + session); + return; + } + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multipeermap_remove (plugin->nat_wait_conns, + &tcp_nat_probe->clientIdentity, + session)); + GNUNET_SERVER_client_set_user_context (client, + session); + (void) GNUNET_CONTAINER_multipeermap_put (plugin->sessionmap, + &session->target, + session, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + session->last_activity = GNUNET_TIME_absolute_get (); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Found address `%s' for incoming connection\n", + GNUNET_a2s (vaddr, alen)); + switch (((const struct sockaddr *) vaddr)->sa_family) + { + case AF_INET: + s4 = vaddr; + t4 = GNUNET_new (struct IPv4TcpAddress); + t4->options = htonl (TCP_OPTIONS_NONE); + t4->t4_port = s4->sin_port; + t4->ipv4_addr = s4->sin_addr.s_addr; + session->address = GNUNET_HELLO_address_allocate (&tcp_nat_probe->clientIdentity, + PLUGIN_NAME, + &t4, + sizeof(struct IPv4TcpAddress), + GNUNET_HELLO_ADDRESS_INFO_NONE); + break; + case AF_INET6: + s6 = vaddr; + t6 = GNUNET_new (struct IPv6TcpAddress); + t6->options = htonl (TCP_OPTIONS_NONE); + t6->t6_port = s6->sin6_port; + GNUNET_memcpy (&t6->ipv6_addr, &s6->sin6_addr, sizeof(struct in6_addr)); + session->address = GNUNET_HELLO_address_allocate (&tcp_nat_probe->clientIdentity, + PLUGIN_NAME, + &t6, + sizeof(struct IPv6TcpAddress), + GNUNET_HELLO_ADDRESS_INFO_NONE); + break; + default: + GNUNET_break_op(0); + LOG(GNUNET_ERROR_TYPE_DEBUG, + "Bad address for incoming connection!\n"); + GNUNET_free(vaddr); + GNUNET_SERVER_receive_done (client, + GNUNET_SYSERR); + tcp_plugin_disconnect_session (plugin, + session); + return; + } + GNUNET_free (vaddr); + GNUNET_break (NULL == session->client); + session->client = client; + GNUNET_STATISTICS_update (plugin->env->stats, + gettext_noop ("# TCP sessions active"), + 1, + GNUNET_NO); + process_pending_messages (session); + GNUNET_SERVER_receive_done (client, + GNUNET_OK); +} + + +/** + * We've received a welcome from this peer via TCP. Possibly create a + * fresh client record and send back our welcome. + * + * @param cls closure + * @param client identification of the client + * @param message the actual message + */ +static void +handle_tcp_welcome (void *cls, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message) +{ + struct Plugin *plugin = cls; + const struct WelcomeMessage *wm = (const struct WelcomeMessage *) message; + struct GNUNET_HELLO_Address *address; + struct GNUNET_ATS_Session *session; + size_t alen; + void *vaddr; + struct IPv4TcpAddress t4; + struct IPv6TcpAddress t6; + const struct sockaddr_in *s4; + const struct sockaddr_in6 *s6; + + if (0 == memcmp (&wm->clientIdentity, + plugin->env->my_identity, + sizeof(struct GNUNET_PeerIdentity))) + { + /* refuse connections from ourselves */ + if (GNUNET_OK == + GNUNET_SERVER_client_get_address (client, + &vaddr, + &alen)) + { + LOG (GNUNET_ERROR_TYPE_INFO, + "Received WELCOME message from my own identity `%s' on address `%s'\n", + GNUNET_i2s (&wm->clientIdentity), + GNUNET_a2s (vaddr, alen)); + GNUNET_free (vaddr); + } + GNUNET_SERVER_receive_done (client, + GNUNET_SYSERR); + return; + } + + if (GNUNET_OK == + GNUNET_SERVER_client_get_address (client, + &vaddr, + &alen)) + { + LOG(GNUNET_ERROR_TYPE_DEBUG, + "Received WELCOME message from `%s' on address `%s'\n", + GNUNET_i2s (&wm->clientIdentity), + GNUNET_a2s (vaddr, alen)); + GNUNET_free (vaddr); + } + GNUNET_STATISTICS_update (plugin->env->stats, + gettext_noop ("# TCP WELCOME messages received"), + 1, + GNUNET_NO); + session = lookup_session_by_client (plugin, + client); + if (NULL != session) + { + if (GNUNET_OK == + GNUNET_SERVER_client_get_address (client, + &vaddr, + &alen)) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Found existing session %p for peer `%s'\n", + session, + GNUNET_a2s (vaddr, alen)); + GNUNET_free (vaddr); + } + } + else + { + if (GNUNET_OK == + GNUNET_SERVER_client_get_address (client, &vaddr, &alen)) + { + if (alen == sizeof(struct sockaddr_in)) + { + s4 = vaddr; + memset (&t4, '\0', sizeof (t4)); + t4.options = htonl (TCP_OPTIONS_NONE); + t4.t4_port = s4->sin_port; + t4.ipv4_addr = s4->sin_addr.s_addr; + address = GNUNET_HELLO_address_allocate (&wm->clientIdentity, + PLUGIN_NAME, + &t4, + sizeof(t4), + GNUNET_HELLO_ADDRESS_INFO_INBOUND); + } + else if (alen == sizeof(struct sockaddr_in6)) + { + s6 = vaddr; + memset (&t6, '\0', sizeof (t6)); + t6.options = htonl (TCP_OPTIONS_NONE); + t6.t6_port = s6->sin6_port; + GNUNET_memcpy (&t6.ipv6_addr, &s6->sin6_addr, sizeof(struct in6_addr)); + address = GNUNET_HELLO_address_allocate (&wm->clientIdentity, + PLUGIN_NAME, + &t6, + sizeof (t6), + GNUNET_HELLO_ADDRESS_INFO_INBOUND); + } + else + { + GNUNET_break (0); + GNUNET_free_non_null (vaddr); + GNUNET_SERVER_receive_done (client, + GNUNET_SYSERR); + return; + } + session = create_session (plugin, + address, + plugin->env->get_address_type (plugin->env->cls, + vaddr, + alen), + client, + GNUNET_NO); + GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != session->scope); + GNUNET_HELLO_address_free (address); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Creating new%s session %p for peer `%s' client %p\n", + GNUNET_HELLO_address_check_option (session->address, + GNUNET_HELLO_ADDRESS_INFO_INBOUND) + ? " inbound" : "", + session, + tcp_plugin_address_to_string (plugin, + session->address->address, + session->address->address_length), + client); + GNUNET_free (vaddr); + (void) GNUNET_CONTAINER_multipeermap_put (plugin->sessionmap, + &session->target, + session, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + /* Notify transport and ATS about new session */ + plugin->env->session_start (plugin->env->cls, + session->address, + session, + session->scope); + } + else + { + LOG(GNUNET_ERROR_TYPE_DEBUG, + "Did not obtain TCP socket address for incoming connection\n"); + GNUNET_break(0); + GNUNET_SERVER_receive_done (client, + GNUNET_SYSERR); + return; + } + } + + if (GNUNET_YES != session->expecting_welcome) + { + GNUNET_break_op (0); + GNUNET_SERVER_receive_done (client, + GNUNET_SYSERR); + return; + } + session->last_activity = GNUNET_TIME_absolute_get (); + session->expecting_welcome = GNUNET_NO; + + process_pending_messages (session); + GNUNET_SERVER_client_set_timeout (client, + GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT); + GNUNET_SERVER_receive_done (client, + GNUNET_OK); +} + + +/** + * We've received data for this peer via TCP. Unbox, + * compute latency and forward. + * + * @param cls closure + * @param client identification of the client + * @param message the actual message + */ +static void +handle_tcp_data (void *cls, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message) +{ + struct Plugin *plugin = cls; + struct GNUNET_ATS_Session *session; + struct GNUNET_TIME_Relative delay; + uint16_t type; + + type = ntohs (message->type); + if ( (GNUNET_MESSAGE_TYPE_TRANSPORT_TCP_WELCOME == type) || + (GNUNET_MESSAGE_TYPE_TRANSPORT_TCP_NAT_PROBE == type) ) + { + /* We don't want to propagate WELCOME and NAT Probe messages up! */ + GNUNET_SERVER_receive_done (client, + GNUNET_OK); + return; + } + session = lookup_session_by_client (plugin, client); + if (NULL == session) + { + /* No inbound session found */ + void *vaddr = NULL; + size_t alen; + + GNUNET_assert (GNUNET_OK == + GNUNET_SERVER_client_get_address (client, + &vaddr, + &alen)); + LOG (GNUNET_ERROR_TYPE_ERROR, + "Received unexpected %u bytes of type %u from `%s'\n", + (unsigned int) ntohs (message->size), + (unsigned int) ntohs (message->type), + GNUNET_a2s (vaddr, + alen)); + GNUNET_break_op(0); + GNUNET_SERVER_receive_done (client, + GNUNET_SYSERR); + GNUNET_free_non_null (vaddr); + return; + } + if (GNUNET_YES == session->expecting_welcome) + { + /* Session is expecting WELCOME message */ + void *vaddr = NULL; + size_t alen; + + GNUNET_SERVER_client_get_address (client, + &vaddr, + &alen); + LOG (GNUNET_ERROR_TYPE_ERROR, + "Received unexpected %u bytes of type %u from `%s'\n", + (unsigned int) ntohs (message->size), + (unsigned int) ntohs (message->type), + GNUNET_a2s (vaddr, alen)); + GNUNET_break_op(0); + GNUNET_SERVER_receive_done (client, + GNUNET_SYSERR); + GNUNET_free_non_null (vaddr); + return; + } + + session->last_activity = GNUNET_TIME_absolute_get (); + { + void *vaddr = NULL; + size_t alen; + + GNUNET_SERVER_client_get_address (client, + &vaddr, + &alen); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Passing %u bytes of type %u from `%s' at %s to transport service.\n", + (unsigned int) ntohs (message->size), + (unsigned int) ntohs (message->type), + GNUNET_i2s (&session->target), + GNUNET_a2s (vaddr, alen)); + GNUNET_free_non_null (vaddr); + } + + GNUNET_STATISTICS_update (plugin->env->stats, + gettext_noop ("# bytes received via TCP"), + ntohs (message->size), + GNUNET_NO); + + GNUNET_assert (GNUNET_CONTAINER_multipeermap_contains_value (plugin->sessionmap, + &session->target, + session)); + delay = plugin->env->receive (plugin->env->cls, + session->address, + session, + message); + reschedule_session_timeout (session); + if (0 == delay.rel_value_us) + { + GNUNET_SERVER_receive_done (client, + GNUNET_OK); + } + else + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Throttling receiving from `%s' for %s\n", + GNUNET_i2s (&session->target), + GNUNET_STRINGS_relative_time_to_string (delay, + GNUNET_YES)); + GNUNET_SERVER_disable_receive_done_warning (client); + GNUNET_assert (NULL == session->receive_delay_task); + session->receive_delay_task = GNUNET_SCHEDULER_add_delayed (delay, + &delayed_done, + session); + } +} + + +/** + * Function called whenever a peer is connected on the "SERVER" level. + * Increments number of active connections and suspends server if we + * have reached the limit. + * + * @param cls closure + * @param client identification of the client + */ +static void +connect_notify (void *cls, + struct GNUNET_SERVER_Client *client) +{ + struct Plugin *plugin = cls; + + if (NULL == client) + return; + plugin->cur_connections++; + GNUNET_STATISTICS_set (plugin->env->stats, + gettext_noop ("# TCP server connections active"), + plugin->cur_connections, + GNUNET_NO); + GNUNET_STATISTICS_update (plugin->env->stats, + gettext_noop ("# TCP server connect events"), + 1, + GNUNET_NO); + if (plugin->cur_connections != plugin->max_connections) + return; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + _("TCP connection limit reached, suspending server\n")); + GNUNET_STATISTICS_update (plugin->env->stats, + gettext_noop ("# TCP service suspended"), + 1, + GNUNET_NO); + GNUNET_SERVER_suspend (plugin->server); /* Maximum number of connections rechead */ +} + + +/** + * Function called whenever a peer is disconnected on the "SERVER" + * level. Cleans up the connection, decrements number of active + * connections and if applicable resumes listening. + * + * @param cls closure + * @param client identification of the client + */ +static void +disconnect_notify (void *cls, + struct GNUNET_SERVER_Client *client) +{ + struct Plugin *plugin = cls; + struct GNUNET_ATS_Session *session; + + if (NULL == client) + return; + GNUNET_assert (plugin->cur_connections >= 1); + plugin->cur_connections--; + session = lookup_session_by_client (plugin, + client); + if (NULL == session) + return; /* unknown, nothing to do */ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Destroying session of `%s' with %s due to network-level disconnect.\n", + GNUNET_i2s (&session->target), + tcp_plugin_address_to_string (session->plugin, + session->address->address, + session->address->address_length)); + + if (plugin->cur_connections == plugin->max_connections) + { + GNUNET_STATISTICS_update (session->plugin->env->stats, + gettext_noop ("# TCP service resumed"), + 1, + GNUNET_NO); + GNUNET_SERVER_resume (plugin->server); /* Resume server */ + } + GNUNET_STATISTICS_set (plugin->env->stats, + gettext_noop ("# TCP server connections active"), + plugin->cur_connections, + GNUNET_NO); + GNUNET_STATISTICS_update (session->plugin->env->stats, + gettext_noop ("# network-level TCP disconnect events"), + 1, + GNUNET_NO); + tcp_plugin_disconnect_session (plugin, + session); +} + + +/** + * We can now send a probe message, copy into buffer to really send. + * + * @param cls closure, a `struct TCPProbeContext` + * @param size max size to copy + * @param buf buffer to copy message to + * @return number of bytes copied into @a buf + */ +static size_t +notify_send_probe (void *cls, + size_t size, + void *buf) +{ + struct TCPProbeContext *tcp_probe_ctx = cls; + struct Plugin *plugin = tcp_probe_ctx->plugin; + size_t ret; + + tcp_probe_ctx->transmit_handle = NULL; + GNUNET_CONTAINER_DLL_remove (plugin->probe_head, + plugin->probe_tail, + tcp_probe_ctx); + if (NULL == buf) + { + GNUNET_CONNECTION_destroy (tcp_probe_ctx->sock); + GNUNET_free(tcp_probe_ctx); + return 0; + } + GNUNET_assert(size >= sizeof(tcp_probe_ctx->message)); + GNUNET_memcpy (buf, + &tcp_probe_ctx->message, + sizeof(tcp_probe_ctx->message)); + GNUNET_SERVER_connect_socket (tcp_probe_ctx->plugin->server, + tcp_probe_ctx->sock); + ret = sizeof(tcp_probe_ctx->message); + GNUNET_free (tcp_probe_ctx); + return ret; +} + + +/** + * Function called by the NAT subsystem suggesting another peer wants + * to connect to us via connection reversal. Try to connect back to the + * given IP. + * + * @param cls closure + * @param addr address to try + * @param addrlen number of bytes in @a addr + */ +static void +try_connection_reversal (void *cls, + const struct sockaddr *addr, + socklen_t addrlen) +{ + struct Plugin *plugin = cls; + struct GNUNET_CONNECTION_Handle *sock; + struct TCPProbeContext *tcp_probe_ctx; + + /** + * We have received an ICMP response, ostensibly from a peer + * that wants to connect to us! Send a message to establish a connection. + */ + sock = GNUNET_CONNECTION_create_from_sockaddr (AF_INET, + addr, + addrlen); + if (NULL == sock) + { + /* failed for some odd reason (out of sockets?); ignore attempt */ + return; + } + + tcp_probe_ctx = GNUNET_new (struct TCPProbeContext); + tcp_probe_ctx->message.header.size + = htons (sizeof (struct TCP_NAT_ProbeMessage)); + tcp_probe_ctx->message.header.type + = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_TCP_NAT_PROBE); + tcp_probe_ctx->message.clientIdentity + = *plugin->env->my_identity; + tcp_probe_ctx->plugin = plugin; + tcp_probe_ctx->sock = sock; + GNUNET_CONTAINER_DLL_insert (plugin->probe_head, + plugin->probe_tail, + tcp_probe_ctx); + tcp_probe_ctx->transmit_handle + = GNUNET_CONNECTION_notify_transmit_ready (sock, + ntohs (tcp_probe_ctx->message.header.size), + GNUNET_TIME_UNIT_FOREVER_REL, + ¬ify_send_probe, + tcp_probe_ctx); +} + + +/** + * Function obtain the network type for a session + * + * @param cls closure (`struct Plugin *`) + * @param session the session + * @return the network type in HBO or #GNUNET_SYSERR + */ +static enum GNUNET_ATS_Network_Type +tcp_plugin_get_network (void *cls, + struct GNUNET_ATS_Session *session) +{ + return session->scope; +} + + +/** + * Function obtain the network type for an address. + * + * @param cls closure (`struct Plugin *`) + * @param address the address + * @return the network type + */ +static enum GNUNET_ATS_Network_Type +tcp_plugin_get_network_for_address (void *cls, + const struct GNUNET_HELLO_Address *address) +{ + struct Plugin *plugin = cls; + size_t addrlen; + struct sockaddr_in a4; + struct sockaddr_in6 a6; + const struct IPv4TcpAddress *t4; + const struct IPv6TcpAddress *t6; + const void *sb; + size_t sbs; + + addrlen = address->address_length; + if (addrlen == sizeof(struct IPv6TcpAddress)) + { + GNUNET_assert (NULL != address->address); /* make static analysis happy */ + t6 = address->address; + memset (&a6, 0, sizeof(a6)); +#if HAVE_SOCKADDR_IN_SIN_LEN + a6.sin6_len = sizeof (a6); +#endif + a6.sin6_family = AF_INET6; + a6.sin6_port = t6->t6_port; + GNUNET_memcpy (&a6.sin6_addr, &t6->ipv6_addr, sizeof(struct in6_addr)); + sb = &a6; + sbs = sizeof(a6); + } + else if (addrlen == sizeof(struct IPv4TcpAddress)) + { + GNUNET_assert (NULL != address->address); /* make static analysis happy */ + t4 = address->address; + memset (&a4, 0, sizeof(a4)); +#if HAVE_SOCKADDR_IN_SIN_LEN + a4.sin_len = sizeof (a4); +#endif + a4.sin_family = AF_INET; + a4.sin_port = t4->t4_port; + a4.sin_addr.s_addr = t4->ipv4_addr; + sb = &a4; + sbs = sizeof(a4); + } + else + { + GNUNET_break (0); + return GNUNET_ATS_NET_UNSPECIFIED; + } + return plugin->env->get_address_type (plugin->env->cls, + sb, + sbs); +} + + +/** + * Return information about the given session to the + * monitor callback. + * + * @param cls the `struct Plugin` with the monitor callback (`sic`) + * @param peer peer we send information about + * @param value our `struct GNUNET_ATS_Session` to send information about + * @return #GNUNET_OK (continue to iterate) + */ +static int +send_session_info_iter (void *cls, + const struct GNUNET_PeerIdentity *peer, + void *value) +{ + struct Plugin *plugin = cls; + struct GNUNET_ATS_Session *session = value; + + notify_session_monitor (plugin, + session, + GNUNET_TRANSPORT_SS_INIT); + /* FIXME: cannot tell if this is up or not from current + session state... */ + notify_session_monitor (plugin, + session, + GNUNET_TRANSPORT_SS_UP); + return GNUNET_OK; +} + + +/** + * Begin monitoring sessions of a plugin. There can only + * be one active monitor per plugin (i.e. if there are + * multiple monitors, the transport service needs to + * multiplex the generated events over all of them). + * + * @param cls closure of the plugin + * @param sic callback to invoke, NULL to disable monitor; + * plugin will being by iterating over all active + * sessions immediately and then enter monitor mode + * @param sic_cls closure for @a sic + */ +static void +tcp_plugin_setup_monitor (void *cls, + GNUNET_TRANSPORT_SessionInfoCallback sic, + void *sic_cls) +{ + struct Plugin *plugin = cls; + + plugin->sic = sic; + plugin->sic_cls = sic_cls; + if (NULL != sic) + { + GNUNET_CONTAINER_multipeermap_iterate (plugin->sessionmap, + &send_session_info_iter, + plugin); + /* signal end of first iteration */ + sic (sic_cls, NULL, NULL); + } +} + + +/** + * Entry point for the plugin. + * + * @param cls closure, the `struct GNUNET_TRANSPORT_PluginEnvironment *` + * @return the `struct GNUNET_TRANSPORT_PluginFunctions *` or NULL on error + */ +void * +libgnunet_plugin_transport_xt_init (void *cls) +{ + static const struct GNUNET_SERVER_MessageHandler my_handlers[] = { + { &handle_tcp_welcome, NULL, + GNUNET_MESSAGE_TYPE_TRANSPORT_TCP_WELCOME, + sizeof(struct WelcomeMessage) }, + { &handle_tcp_nat_probe, NULL, + GNUNET_MESSAGE_TYPE_TRANSPORT_TCP_NAT_PROBE, + sizeof(struct TCP_NAT_ProbeMessage) }, + { &handle_tcp_data, NULL, + GNUNET_MESSAGE_TYPE_ALL, 0 }, + { NULL, NULL, 0, 0 } + }; + struct GNUNET_TRANSPORT_PluginEnvironment *env = cls; + struct GNUNET_TRANSPORT_PluginFunctions *api; + struct Plugin *plugin; + struct LEGACY_SERVICE_Context *service; + unsigned long long aport; + unsigned long long bport; + unsigned long long max_connections; + unsigned int i; + struct GNUNET_TIME_Relative idle_timeout; +#ifdef TCP_STEALTH + struct GNUNET_NETWORK_Handle *const*lsocks; +#endif + int ret; + int ret_s; + struct sockaddr **addrs; + socklen_t *addrlens; + + if (NULL == env->receive) + { + /* run in 'stub' mode (i.e. as part of gnunet-peerinfo), don't fully + initialze the plugin or the API */ + api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions); + api->cls = NULL; + api->address_pretty_printer = &tcp_plugin_address_pretty_printer; + api->address_to_string = &tcp_plugin_address_to_string; + api->string_to_address = &tcp_plugin_string_to_address; + return api; + } + + GNUNET_assert (NULL != env->cfg); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (env->cfg, + "transport-xt", + "MAX_CONNECTIONS", + &max_connections)) + max_connections = 128; + + aport = 0; + if ((GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (env->cfg, + "transport-xt", + "PORT", &bport)) || + (bport > 65535) || + ((GNUNET_OK == + GNUNET_CONFIGURATION_get_value_number (env->cfg, + "transport-xt", + "ADVERTISED-PORT", &aport)) && + (aport > 65535) )) + { + LOG(GNUNET_ERROR_TYPE_ERROR, + _("Require valid port number for service `%s' in configuration!\n"), + "transport-xt"); + return NULL ; + } + if (0 == aport) + aport = bport; + if (0 == bport) + aport = 0; + if (0 != bport) + { + service = LEGACY_SERVICE_start ("transport-xt", + env->cfg, + LEGACY_SERVICE_OPTION_NONE); + if (NULL == service) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + _("Failed to start service.\n")); + return NULL; + } + } + else + service = NULL; + + api = NULL; + plugin = GNUNET_new (struct Plugin); + plugin->sessionmap = GNUNET_CONTAINER_multipeermap_create (max_connections, + GNUNET_YES); + plugin->max_connections = max_connections; + plugin->open_port = bport; + plugin->adv_port = aport; + plugin->env = env; + plugin->my_welcome.header.size = htons (sizeof(struct WelcomeMessage)); + plugin->my_welcome.header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_TCP_WELCOME); + plugin->my_welcome.clientIdentity = *plugin->env->my_identity; + + if ( (NULL != service) && + (GNUNET_YES == + GNUNET_CONFIGURATION_get_value_yesno (env->cfg, + "transport-xt", + "TCP_STEALTH")) ) + { +#ifdef TCP_STEALTH + plugin->myoptions |= TCP_OPTIONS_TCP_STEALTH; + lsocks = LEGACY_SERVICE_get_listen_sockets (service); + if (NULL != lsocks) + { + uint32_t len = sizeof (struct WelcomeMessage); + + for (i=0;NULL!=lsocks[i];i++) + { + if ( (GNUNET_OK != + GNUNET_NETWORK_socket_setsockopt (lsocks[i], + IPPROTO_TCP, + TCP_STEALTH, + env->my_identity, + sizeof (struct GNUNET_PeerIdentity))) || + (GNUNET_OK != + GNUNET_NETWORK_socket_setsockopt (lsocks[i], + IPPROTO_TCP, + TCP_STEALTH_INTEGRITY_LEN, + &len, + sizeof (len))) ) + { + /* TCP STEALTH not supported by kernel */ + GNUNET_assert (0 == i); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("TCP_STEALTH not supported on this platform.\n")); + goto die; + } + } + } +#else + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("TCP_STEALTH not supported on this platform.\n")); + goto die; +#endif + } + + if ( (NULL != service) && + (GNUNET_SYSERR != + (ret_s = + get_server_addresses ("transport-xt", + env->cfg, + &addrs, + &addrlens)))) + { + for (ret = ret_s-1; ret >= 0; ret--) + LOG (GNUNET_ERROR_TYPE_INFO, + "Binding to address `%s'\n", + GNUNET_a2s (addrs[ret], addrlens[ret])); + plugin->nat + = GNUNET_NAT_register (env->cfg, + "transport-xt", + IPPROTO_TCP, + (unsigned int) ret_s, + (const struct sockaddr **) addrs, + addrlens, + &tcp_nat_port_map_callback, + &try_connection_reversal, + plugin); + for (ret = ret_s -1; ret >= 0; ret--) + GNUNET_free (addrs[ret]); + GNUNET_free_non_null (addrs); + GNUNET_free_non_null (addrlens); + } + else + { + plugin->nat = GNUNET_NAT_register (plugin->env->cfg, + "transport-xt", + IPPROTO_TCP, + 0, + NULL, + NULL, + NULL, + &try_connection_reversal, + plugin); + } + api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions); + api->cls = plugin; + api->send = &tcp_plugin_send; + api->get_session = &tcp_plugin_get_session; + api->disconnect_session = &tcp_plugin_disconnect_session; + api->query_keepalive_factor = &tcp_plugin_query_keepalive_factor; + api->disconnect_peer = &tcp_plugin_disconnect; + api->address_pretty_printer = &tcp_plugin_address_pretty_printer; + api->check_address = &tcp_plugin_check_address; + api->address_to_string = &tcp_plugin_address_to_string; + api->string_to_address = &tcp_plugin_string_to_address; + api->get_network = &tcp_plugin_get_network; + api->get_network_for_address = &tcp_plugin_get_network_for_address; + api->update_session_timeout = &tcp_plugin_update_session_timeout; + api->update_inbound_delay = &tcp_plugin_update_inbound_delay; + api->setup_monitor = &tcp_plugin_setup_monitor; + plugin->service = service; + if (NULL != service) + { + plugin->server = LEGACY_SERVICE_get_server (service); + } + else + { + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (env->cfg, + "transport-xt", + "TIMEOUT", + &idle_timeout)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "transport-xt", + "TIMEOUT"); + goto die; + } + plugin->server + = GNUNET_SERVER_create_with_sockets (NULL, + plugin, + NULL, + idle_timeout, + GNUNET_YES); + } + plugin->handlers = GNUNET_malloc (sizeof (my_handlers)); + GNUNET_memcpy (plugin->handlers, + my_handlers, + sizeof(my_handlers)); + for (i = 0;i < sizeof(my_handlers) / sizeof(struct GNUNET_SERVER_MessageHandler);i++) + plugin->handlers[i].callback_cls = plugin; + + GNUNET_SERVER_add_handlers (plugin->server, + plugin->handlers); + GNUNET_SERVER_connect_notify (plugin->server, + &connect_notify, + plugin); + GNUNET_SERVER_disconnect_notify (plugin->server, + &disconnect_notify, + plugin); + plugin->nat_wait_conns = GNUNET_CONTAINER_multipeermap_create (16, + GNUNET_YES); + if (0 != bport) + LOG (GNUNET_ERROR_TYPE_INFO, + _("XT transport listening on port %llu\n"), + bport); + else + LOG (GNUNET_ERROR_TYPE_INFO, + _("XT transport not listening on any port (client only)\n")); + if ( (aport != bport) && + (0 != bport) ) + LOG (GNUNET_ERROR_TYPE_INFO, + _("XT transport advertises itself as being on port %llu\n"), + aport); + /* Initially set connections to 0 */ + GNUNET_STATISTICS_set (plugin->env->stats, + gettext_noop ("# XT sessions active"), + 0, + GNUNET_NO); + return api; + + die: + if (NULL != plugin->nat) + GNUNET_NAT_unregister (plugin->nat); + GNUNET_CONTAINER_multipeermap_destroy (plugin->sessionmap); + if (NULL != service) + LEGACY_SERVICE_stop (service); + GNUNET_free (plugin); + GNUNET_free_non_null (api); + return NULL; +} + + +/** + * Exit point from the plugin. + * + * @param cls the `struct GNUNET_TRANSPORT_PluginFunctions` + * @return NULL + */ +void * +libgnunet_plugin_transport_xt_done (void *cls) +{ + struct GNUNET_TRANSPORT_PluginFunctions *api = cls; + struct Plugin *plugin = api->cls; + struct TCPProbeContext *tcp_probe; + struct PrettyPrinterContext *cur; + struct PrettyPrinterContext *next; + + if (NULL == plugin) + { + GNUNET_free(api); + return NULL ; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Shutting down XT plugin\n"); + + /* Removing leftover sessions */ + GNUNET_CONTAINER_multipeermap_iterate (plugin->sessionmap, + &session_disconnect_it, + plugin); + /* Removing leftover NAT sessions */ + GNUNET_CONTAINER_multipeermap_iterate (plugin->nat_wait_conns, + &session_disconnect_it, + plugin); + + for (cur = plugin->ppc_dll_head; NULL != cur; cur = next) + { + next = cur->next; + GNUNET_CONTAINER_DLL_remove (plugin->ppc_dll_head, + plugin->ppc_dll_tail, + cur); + GNUNET_RESOLVER_request_cancel (cur->resolver_handle); + cur->asc (cur->asc_cls, + NULL, + GNUNET_OK); + GNUNET_free (cur); + } + + if (NULL != plugin->service) + LEGACY_SERVICE_stop (plugin->service); + else + GNUNET_SERVER_destroy (plugin->server); + GNUNET_free (plugin->handlers); + if (NULL != plugin->nat) + GNUNET_NAT_unregister (plugin->nat); + while (NULL != (tcp_probe = plugin->probe_head)) + { + GNUNET_CONTAINER_DLL_remove (plugin->probe_head, + plugin->probe_tail, + tcp_probe); + GNUNET_CONNECTION_destroy (tcp_probe->sock); + GNUNET_free (tcp_probe); + } + GNUNET_CONTAINER_multipeermap_destroy (plugin->nat_wait_conns); + GNUNET_CONTAINER_multipeermap_destroy (plugin->sessionmap); + GNUNET_break (0 == plugin->cur_connections); + GNUNET_free (plugin); + GNUNET_free (api); + return NULL; +} + +/* end of plugin_transport_xt.c */ -- cgit v1.2.3 From 28cee751620724a0da631dfa8b9c1123871e1e32 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 26 May 2018 20:18:59 +0200 Subject: build xt --- src/transport/Makefile.am | 13 +++++++++++++ src/transport/transport.conf.in | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) (limited to 'src/transport') diff --git a/src/transport/Makefile.am b/src/transport/Makefile.am index e8c1f5d4a..2b3f81567 100644 --- a/src/transport/Makefile.am +++ b/src/transport/Makefile.am @@ -280,6 +280,7 @@ gnunet_service_transport_CFLAGS = \ plugin_LTLIBRARIES = \ libgnunet_plugin_transport_tcp.la \ + libgnunet_plugin_transport_xt.la \ libgnunet_plugin_transport_udp.la \ $(UNIX_PLUGIN_LA) \ $(HTTP_CLIENT_PLUGIN_LA) \ @@ -306,6 +307,18 @@ libgnunet_plugin_transport_tcp_la_LIBADD = \ libgnunet_plugin_transport_tcp_la_LDFLAGS = \ $(GN_PLUGIN_LDFLAGS) +libgnunet_plugin_transport_xt_la_SOURCES = \ + plugin_transport_xt.c +libgnunet_plugin_transport_xt_la_LIBADD = \ + $(top_builddir)/src/hello/libgnunethello.la \ + $(top_builddir)/src/statistics/libgnunetstatistics.la \ + $(top_builddir)/src/peerinfo/libgnunetpeerinfo.la \ + $(top_builddir)/src/nat/libgnunetnatnew.la \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(LTLIBINTL) +libgnunet_plugin_transport_xt_la_LDFLAGS = \ + $(GN_PLUGIN_LDFLAGS) + libgnunet_plugin_transport_template_la_SOURCES = \ plugin_transport_template.c libgnunet_plugin_transport_template_la_LIBADD = \ diff --git a/src/transport/transport.conf.in b/src/transport/transport.conf.in index 2c99af000..a541080ce 100644 --- a/src/transport/transport.conf.in +++ b/src/transport/transport.conf.in @@ -68,6 +68,38 @@ MAX_CONNECTIONS = 128 TCP_STEALTH = NO + +[transport-xt] +# Use 0 to ONLY advertise as a peer behind NAT (no port binding) +PORT = 2087 + +# Obsolete option, to be replaced by HOLE_EXTERNAL (soon) +ADVERTISED_PORT = 2087 + +# If we have a manually punched NAT, what is the external IP and port? +# Can use DNS names for DynDNS-based detection of external IP. +# Can use IPv6 addresses ([fefc::]:PORT). +# Use "AUTO" for the hostname to automatically detect external IP. +# Do not set if NAT is not manually punched. +# HOLE_EXTERNAL = AUTO:2087 + +TESTING_IGNORE_KEYS = ACCEPT_FROM; + +# Maximum number of open TCP connections allowed +MAX_CONNECTIONS = 128 + +TIMEOUT = 5 s +# ACCEPT_FROM = +# ACCEPT_FROM6 = +# REJECT_FROM = +# REJECT_FROM6 = +# BINDTO = +MAX_CONNECTIONS = 128 + +# Enable TCP stealth? +TCP_STEALTH = NO + + [transport-udp] # Use PORT = 0 to autodetect a port available PORT = 2086 -- cgit v1.2.3 From c62b46170dfbd969f8cbadc4f3069a45832b804f Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 26 May 2018 20:44:29 +0200 Subject: disable LAN/loopback for AC --- src/transport/plugin_transport_xt.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/transport') diff --git a/src/transport/plugin_transport_xt.c b/src/transport/plugin_transport_xt.c index 9ed0f43c9..21ed19da5 100644 --- a/src/transport/plugin_transport_xt.c +++ b/src/transport/plugin_transport_xt.c @@ -1450,6 +1450,12 @@ tcp_nat_port_map_callback (void *cls, void *arg; size_t args; + if (GNUNET_NAT_AC_LOOPBACK == ac) + return; + if (GNUNET_NAT_AC_LAN == ac) + return; + if (GNUNET_NAT_AC_LAN_PRIVATE == ac) + return; LOG (GNUNET_ERROR_TYPE_INFO, "NAT notification to %s address `%s'\n", (GNUNET_YES == add_remove) ? "add" : "remove", -- cgit v1.2.3 From 0e70daa01fc872938ce4a23e84747a319d6909fd Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 28 May 2018 01:39:40 +0200 Subject: track hello passing --- src/peerinfo/peerinfo_api.c | 3 +++ src/transport/gnunet-service-transport_validation.c | 13 +++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) (limited to 'src/transport') diff --git a/src/peerinfo/peerinfo_api.c b/src/peerinfo/peerinfo_api.c index 8b47ed444..09ec04bf8 100644 --- a/src/peerinfo/peerinfo_api.c +++ b/src/peerinfo/peerinfo_api.c @@ -565,7 +565,10 @@ GNUNET_PEERINFO_add_peer (struct GNUNET_PEERINFO_Handle *h, struct GNUNET_PeerIdentity peer; if (NULL == h->mq) + { + GNUNET_break (0); return NULL; + } GNUNET_assert (GNUNET_OK == GNUNET_HELLO_get_id (hello, &peer)); diff --git a/src/transport/gnunet-service-transport_validation.c b/src/transport/gnunet-service-transport_validation.c index cd5aeb5e2..91acc18ed 100644 --- a/src/transport/gnunet-service-transport_validation.c +++ b/src/transport/gnunet-service-transport_validation.c @@ -1623,15 +1623,20 @@ GST_validation_handle_pong (const struct GNUNET_PeerIdentity *sender, GNUNET_TRANSPORT_VS_UPDATE); /* build HELLO to store in PEERINFO */ + GNUNET_STATISTICS_update (GST_stats, + gettext_noop ("# HELLOs given to peerinfo"), + 1, + GNUNET_NO); ve->copied = GNUNET_NO; hello = GNUNET_HELLO_create (&ve->address->peer.public_key, &add_valid_peer_address, ve, GNUNET_NO); - GNUNET_PEERINFO_add_peer (GST_peerinfo, - hello, - NULL, - NULL); + GNUNET_break (NULL != + GNUNET_PEERINFO_add_peer (GST_peerinfo, + hello, + NULL, + NULL)); GNUNET_free (hello); return GNUNET_OK; } -- cgit v1.2.3 From 1ac69f278b6cc4ca79f81f0b4dfc2f8771c88698 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 28 May 2018 16:56:32 +0200 Subject: adding xu transport --- src/include/gnunet_protocols.h | 6 + src/transport/Makefile.am | 14 + src/transport/plugin_transport_xu.c | 2492 +++++++++++++++++++++++++++++++++++ src/transport/transport.conf.in | 5 + 4 files changed, 2517 insertions(+) create mode 100644 src/transport/plugin_transport_xu.c (limited to 'src/transport') diff --git a/src/include/gnunet_protocols.h b/src/include/gnunet_protocols.h index c5e45d5c1..7040f2cbf 100644 --- a/src/include/gnunet_protocols.h +++ b/src/include/gnunet_protocols.h @@ -252,6 +252,12 @@ extern "C" #define GNUNET_MESSAGE_TYPE_DV_BOX 53 +/** + * Experimental message type. + */ +#define GNUNET_MESSAGE_TYPE_TRANSPORT_XU_MESSAGE 55 + + /******************************************************************************* * Transport-UDP message types ******************************************************************************/ diff --git a/src/transport/Makefile.am b/src/transport/Makefile.am index 2b3f81567..b04b54e7f 100644 --- a/src/transport/Makefile.am +++ b/src/transport/Makefile.am @@ -282,6 +282,7 @@ plugin_LTLIBRARIES = \ libgnunet_plugin_transport_tcp.la \ libgnunet_plugin_transport_xt.la \ libgnunet_plugin_transport_udp.la \ + libgnunet_plugin_transport_xu.la \ $(UNIX_PLUGIN_LA) \ $(HTTP_CLIENT_PLUGIN_LA) \ $(HTTPS_CLIENT_PLUGIN_LA) \ @@ -367,6 +368,19 @@ libgnunet_plugin_transport_udp_la_LIBADD = \ libgnunet_plugin_transport_udp_la_LDFLAGS = \ $(GN_PLUGIN_LDFLAGS) +libgnunet_plugin_transport_xu_la_SOURCES = \ + plugin_transport_xu.c plugin_transport_xu.h +libgnunet_plugin_transport_udp_la_LIBADD = \ + $(top_builddir)/src/hello/libgnunethello.la \ + $(top_builddir)/src/fragmentation/libgnunetfragmentation.la \ + $(top_builddir)/src/statistics/libgnunetstatistics.la \ + $(top_builddir)/src/peerinfo/libgnunetpeerinfo.la \ + $(top_builddir)/src/nat/libgnunetnatnew.la \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(LTLIBINTL) +libgnunet_plugin_transport_udp_la_LDFLAGS = \ + $(GN_PLUGIN_LDFLAGS) + libgnunet_plugin_transport_unix_la_SOURCES = \ plugin_transport_unix.c libgnunet_plugin_transport_unix_la_LIBADD = \ diff --git a/src/transport/plugin_transport_xu.c b/src/transport/plugin_transport_xu.c new file mode 100644 index 000000000..ea0d3f065 --- /dev/null +++ b/src/transport/plugin_transport_xu.c @@ -0,0 +1,2492 @@ +/* + This file is part of GNUnet + Copyright (C) 2010-2017 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 transport/plugin_transport_xu.c + * @brief Implementation of the XU transport protocol + * @author Christian Grothoff + * @author Nathan Evans + * @author Matthias Wachs + */ +#include "platform.h" +#include "plugin_transport_xu.h" +#include "gnunet_hello_lib.h" +#include "gnunet_util_lib.h" +#include "gnunet_fragmentation_lib.h" +#include "gnunet_nat_service.h" +#include "gnunet_protocols.h" +#include "gnunet_resolver_service.h" +#include "gnunet_signatures.h" +#include "gnunet_constants.h" +#include "gnunet_statistics_service.h" +#include "gnunet_transport_service.h" +#include "gnunet_transport_plugin.h" +#include "transport.h" + +#define LOG(kind,...) GNUNET_log_from (kind, "transport-xu", __VA_ARGS__) + +/** + * After how much inactivity should a XU session time out? + */ +#define XU_SESSION_TIME_OUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60) + + +/** + * XU Message-Packet header (after defragmentation). + */ +struct XUMessage +{ + /** + * Message header. + */ + struct GNUNET_MessageHeader header; + + /** + * Always zero for now. + */ + uint32_t reserved; + + /** + * What is the identity of the sender + */ + struct GNUNET_PeerIdentity sender; + +}; + + +/** + * Closure for #append_port(). + */ +struct PrettyPrinterContext +{ + /** + * DLL + */ + struct PrettyPrinterContext *next; + + /** + * DLL + */ + struct PrettyPrinterContext *prev; + + /** + * Our plugin. + */ + struct Plugin *plugin; + + /** + * Resolver handle + */ + struct GNUNET_RESOLVER_RequestHandle *resolver_handle; + + /** + * Function to call with the result. + */ + GNUNET_TRANSPORT_AddressStringCallback asc; + + /** + * Clsoure for @e asc. + */ + void *asc_cls; + + /** + * Timeout task + */ + struct GNUNET_SCHEDULER_Task *timeout_task; + + /** + * Is this an IPv6 address? + */ + int ipv6; + + /** + * Options + */ + uint32_t options; + + /** + * Port to add after the IP address. + */ + uint16_t port; + +}; + + +/** + * Session with another peer. + */ +struct GNUNET_ATS_Session +{ + /** + * Which peer is this session for? + */ + struct GNUNET_PeerIdentity target; + + /** + * Tokenizer for inbound messages. + */ + struct GNUNET_MessageStreamTokenizer *mst; + + /** + * Plugin this session belongs to. + */ + struct Plugin *plugin; + + /** + * Session timeout task + */ + struct GNUNET_SCHEDULER_Task *timeout_task; + + /** + * When does this session time out? + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * What time did we last transmit? + */ + struct GNUNET_TIME_Absolute last_transmit_time; + + /** + * expected delay for ACKs + */ + struct GNUNET_TIME_Relative last_expected_ack_delay; + + /** + * desired delay between XU messages + */ + struct GNUNET_TIME_Relative last_expected_msg_delay; + + /** + */ + struct GNUNET_TIME_Relative flow_delay_for_other_peer; + struct GNUNET_TIME_Relative flow_delay_from_other_peer; + + /** + * Our own address. + */ + struct GNUNET_HELLO_Address *address; + + /** + * Number of bytes waiting for transmission to this peer. + */ + unsigned long long bytes_in_queue; + + /** + * Number of messages waiting for transmission to this peer. + */ + unsigned int msgs_in_queue; + + /** + * Reference counter to indicate that this session is + * currently being used and must not be destroyed; + * setting @e in_destroy will destroy it as soon as + * possible. + */ + unsigned int rc; + + /** + * Network type of the address. + */ + enum GNUNET_ATS_Network_Type scope; + + /** + * Is this session about to be destroyed (sometimes we cannot + * destroy a session immediately as below us on the stack + * there might be code that still uses it; in this case, + * @e rc is non-zero). + */ + int in_destroy; +}; + + + +/** + * If a session monitor is attached, notify it about the new + * session state. + * + * @param plugin our plugin + * @param session session that changed state + * @param state new state of the session + */ +static void +notify_session_monitor (struct Plugin *plugin, + struct GNUNET_ATS_Session *session, + enum GNUNET_TRANSPORT_SessionState state) +{ + struct GNUNET_TRANSPORT_SessionInfo info; + + if (NULL == plugin->sic) + return; + if (GNUNET_YES == session->in_destroy) + return; /* already destroyed, just RC>0 left-over actions */ + memset (&info, + 0, + sizeof (info)); + info.state = state; + info.is_inbound = GNUNET_SYSERR; /* hard to say */ + info.num_msg_pending = session->msgs_in_queue; + info.num_bytes_pending = session->bytes_in_queue; + /* info.receive_delay remains zero as this is not supported by XU + (cannot selectively not receive from 'some' peer while continuing + to receive from others) */ + info.session_timeout = session->timeout; + info.address = session->address; + plugin->sic (plugin->sic_cls, + session, + &info); +} + + +/** + * Return information about the given session to the monitor callback. + * + * @param cls the `struct Plugin` with the monitor callback (`sic`) + * @param peer peer we send information about + * @param value our `struct GNUNET_ATS_Session` to send information about + * @return #GNUNET_OK (continue to iterate) + */ +static int +send_session_info_iter (void *cls, + const struct GNUNET_PeerIdentity *peer, + void *value) +{ + struct Plugin *plugin = cls; + struct GNUNET_ATS_Session *session = value; + + (void) peer; + notify_session_monitor (plugin, + session, + GNUNET_TRANSPORT_SS_INIT); + notify_session_monitor (plugin, + session, + GNUNET_TRANSPORT_SS_UP); + return GNUNET_OK; +} + + +/** + * Begin monitoring sessions of a plugin. There can only + * be one active monitor per plugin (i.e. if there are + * multiple monitors, the transport service needs to + * multiplex the generated events over all of them). + * + * @param cls closure of the plugin + * @param sic callback to invoke, NULL to disable monitor; + * plugin will being by iterating over all active + * sessions immediately and then enter monitor mode + * @param sic_cls closure for @a sic + */ +static void +xu_plugin_setup_monitor (void *cls, + GNUNET_TRANSPORT_SessionInfoCallback sic, + void *sic_cls) +{ + struct Plugin *plugin = cls; + + plugin->sic = sic; + plugin->sic_cls = sic_cls; + if (NULL != sic) + { + GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions, + &send_session_info_iter, + plugin); + /* signal end of first iteration */ + sic (sic_cls, + NULL, + NULL); + } +} + + +/* ****************** Little Helpers ****************** */ + + +/** + * Function to free last resources associated with a session. + * + * @param s session to free + */ +static void +free_session (struct GNUNET_ATS_Session *s) +{ + if (NULL != s->address) + { + GNUNET_HELLO_address_free (s->address); + s->address = NULL; + } + if (NULL != s->mst) + { + GNUNET_MST_destroy (s->mst); + s->mst = NULL; + } + GNUNET_free (s); +} + + +/** + * Function that is called to get the keepalive factor. + * #GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT is divided by this number to + * calculate the interval between keepalive packets. + * + * @param cls closure with the `struct Plugin` + * @return keepalive factor + */ +static unsigned int +xu_query_keepalive_factor (void *cls) +{ + (void) cls; + return 15; +} + + +/** + * Function obtain the network type for a session + * + * @param cls closure (`struct Plugin *`) + * @param session the session + * @return the network type + */ +static enum GNUNET_ATS_Network_Type +xu_plugin_get_network (void *cls, + struct GNUNET_ATS_Session *session) +{ + (void) cls; + return session->scope; +} + + +/** + * Function obtain the network type for an address. + * + * @param cls closure (`struct Plugin *`) + * @param address the address + * @return the network type + */ +static enum GNUNET_ATS_Network_Type +xu_plugin_get_network_for_address (void *cls, + const struct GNUNET_HELLO_Address *address) +{ + struct Plugin *plugin = cls; + size_t addrlen; + struct sockaddr_in a4; + struct sockaddr_in6 a6; + const struct IPv4XuAddress *u4; + const struct IPv6XuAddress *u6; + const void *sb; + size_t sbs; + + addrlen = address->address_length; + if (addrlen == sizeof(struct IPv6XuAddress)) + { + GNUNET_assert (NULL != address->address); /* make static analysis happy */ + u6 = address->address; + memset (&a6, 0, sizeof(a6)); +#if HAVE_SOCKADDR_IN_SIN_LEN + a6.sin6_len = sizeof (a6); +#endif + a6.sin6_family = AF_INET6; + a6.sin6_port = u6->u6_port; + GNUNET_memcpy (&a6.sin6_addr, &u6->ipv6_addr, sizeof(struct in6_addr)); + sb = &a6; + sbs = sizeof(a6); + } + else if (addrlen == sizeof(struct IPv4XuAddress)) + { + GNUNET_assert (NULL != address->address); /* make static analysis happy */ + u4 = address->address; + memset (&a4, 0, sizeof(a4)); +#if HAVE_SOCKADDR_IN_SIN_LEN + a4.sin_len = sizeof (a4); +#endif + a4.sin_family = AF_INET; + a4.sin_port = u4->u4_port; + a4.sin_addr.s_addr = u4->ipv4_addr; + sb = &a4; + sbs = sizeof(a4); + } + else + { + GNUNET_break (0); + return GNUNET_ATS_NET_UNSPECIFIED; + } + return plugin->env->get_address_type (plugin->env->cls, + sb, + sbs); +} + + +/* ******************* Event loop ******************** */ + +/** + * We have been notified that our readset has something to read. We don't + * know which socket needs to be read, so we have to check each one + * Then reschedule this function to be called again once more is available. + * + * @param cls the plugin handle + */ +static void +xu_plugin_select_v4 (void *cls); + + +/** + * We have been notified that our readset has something to read. We don't + * know which socket needs to be read, so we have to check each one + * Then reschedule this function to be called again once more is available. + * + * @param cls the plugin handle + */ +static void +xu_plugin_select_v6 (void *cls); + + +/** + * (re)schedule IPv4-select tasks for this plugin. + * + * @param plugin plugin to reschedule + */ +static void +schedule_select_v4 (struct Plugin *plugin) +{ + if ( (GNUNET_YES != plugin->enable_ipv4) || + (NULL == plugin->sockv4) ) + return; + if (NULL != plugin->select_task_v4) + GNUNET_SCHEDULER_cancel (plugin->select_task_v4); + plugin->select_task_v4 + = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + plugin->sockv4, + &xu_plugin_select_v4, + plugin); +} + + +/** + * (re)schedule IPv6-select tasks for this plugin. + * + * @param plugin plugin to reschedule + */ +static void +schedule_select_v6 (struct Plugin *plugin) +{ + if ( (GNUNET_YES != plugin->enable_ipv6) || + (NULL == plugin->sockv6) ) + return; + if (NULL != plugin->select_task_v6) + GNUNET_SCHEDULER_cancel (plugin->select_task_v6); + plugin->select_task_v6 + = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + plugin->sockv6, + &xu_plugin_select_v6, + plugin); +} + + +/* ******************* Address to string and back ***************** */ + + +/** + * Function called for a quick conversion of the binary address to + * a numeric address. Note that the caller must not free the + * address and that the next call to this function is allowed + * to override the address again. + * + * @param cls closure + * @param addr binary address (a `union XuAddress`) + * @param addrlen length of the @a addr + * @return string representing the same address + */ +const char * +xu_address_to_string (void *cls, + const void *addr, + size_t addrlen) +{ + static char rbuf[INET6_ADDRSTRLEN + 10]; + char buf[INET6_ADDRSTRLEN]; + const void *sb; + struct in_addr a4; + struct in6_addr a6; + const struct IPv4XuAddress *t4; + const struct IPv6XuAddress *t6; + int af; + uint16_t port; + uint32_t options; + + (void) cls; + if (NULL == addr) + { + GNUNET_break_op (0); + return NULL; + } + + if (addrlen == sizeof(struct IPv6XuAddress)) + { + t6 = addr; + af = AF_INET6; + options = ntohl (t6->options); + port = ntohs (t6->u6_port); + a6 = t6->ipv6_addr; + sb = &a6; + } + else if (addrlen == sizeof(struct IPv4XuAddress)) + { + t4 = addr; + af = AF_INET; + options = ntohl (t4->options); + port = ntohs (t4->u4_port); + a4.s_addr = t4->ipv4_addr; + sb = &a4; + } + else + { + GNUNET_break_op (0); + return NULL; + } + inet_ntop (af, + sb, + buf, + INET6_ADDRSTRLEN); + GNUNET_snprintf (rbuf, + sizeof(rbuf), + (af == AF_INET6) + ? "%s.%u.[%s]:%u" + : "%s.%u.%s:%u", + PLUGIN_NAME, + options, + buf, + port); + return rbuf; +} + + +/** + * Function called to convert a string address to a binary address. + * + * @param cls closure (`struct Plugin *`) + * @param addr string address + * @param addrlen length of the address + * @param buf location to store the buffer + * @param added location to store the number of bytes in the buffer. + * If the function returns #GNUNET_SYSERR, its contents are undefined. + * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure + */ +static int +xu_string_to_address (void *cls, + const char *addr, + uint16_t addrlen, + void **buf, + size_t *added) +{ + struct sockaddr_storage socket_address; + char *address; + char *plugin; + char *optionstr; + uint32_t options; + + (void) cls; + /* Format tcp.options.address:port */ + address = NULL; + plugin = NULL; + optionstr = NULL; + + if ((NULL == addr) || (0 == addrlen)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if ('\0' != addr[addrlen - 1]) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (strlen (addr) + 1 != (size_t) addrlen) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + plugin = GNUNET_strdup (addr); + optionstr = strchr (plugin, '.'); + if (NULL == optionstr) + { + GNUNET_break (0); + GNUNET_free (plugin); + return GNUNET_SYSERR; + } + optionstr[0] = '\0'; + optionstr++; + options = atol (optionstr); + address = strchr (optionstr, '.'); + if (NULL == address) + { + GNUNET_break (0); + GNUNET_free (plugin); + return GNUNET_SYSERR; + } + address[0] = '\0'; + address++; + + if (GNUNET_OK != + GNUNET_STRINGS_to_address_ip (address, + strlen (address), + &socket_address)) + { + GNUNET_break (0); + GNUNET_free (plugin); + return GNUNET_SYSERR; + } + GNUNET_free(plugin); + + switch (socket_address.ss_family) + { + case AF_INET: + { + struct IPv4XuAddress *u4; + const struct sockaddr_in *in4 = (const struct sockaddr_in *) &socket_address; + + u4 = GNUNET_new (struct IPv4XuAddress); + u4->options = htonl (options); + u4->ipv4_addr = in4->sin_addr.s_addr; + u4->u4_port = in4->sin_port; + *buf = u4; + *added = sizeof (struct IPv4XuAddress); + return GNUNET_OK; + } + case AF_INET6: + { + struct IPv6XuAddress *u6; + const struct sockaddr_in6 *in6 = (const struct sockaddr_in6 *) &socket_address; + + u6 = GNUNET_new (struct IPv6XuAddress); + u6->options = htonl (options); + u6->ipv6_addr = in6->sin6_addr; + u6->u6_port = in6->sin6_port; + *buf = u6; + *added = sizeof (struct IPv6XuAddress); + return GNUNET_OK; + } + default: + GNUNET_break (0); + return GNUNET_SYSERR; + } +} + + +/** + * Append our port and forward the result. + * + * @param cls a `struct PrettyPrinterContext *` + * @param hostname result from DNS resolver + */ +static void +append_port (void *cls, + const char *hostname) +{ + struct PrettyPrinterContext *ppc = cls; + struct Plugin *plugin = ppc->plugin; + char *ret; + + if (NULL == hostname) + { + /* Final call, done */ + GNUNET_CONTAINER_DLL_remove (plugin->ppc_dll_head, + plugin->ppc_dll_tail, + ppc); + ppc->resolver_handle = NULL; + ppc->asc (ppc->asc_cls, + NULL, + GNUNET_OK); + GNUNET_free (ppc); + return; + } + if (GNUNET_YES == ppc->ipv6) + GNUNET_asprintf (&ret, + "%s.%u.[%s]:%d", + PLUGIN_NAME, + ppc->options, + hostname, + ppc->port); + else + GNUNET_asprintf (&ret, + "%s.%u.%s:%d", + PLUGIN_NAME, + ppc->options, + hostname, + ppc->port); + ppc->asc (ppc->asc_cls, + ret, + GNUNET_OK); + GNUNET_free (ret); +} + + +/** + * Convert the transports address to a nice, human-readable format. + * + * @param cls closure with the `struct Plugin *` + * @param type name of the transport that generated the address + * @param addr one of the addresses of the host, NULL for the last address + * the specific address format depends on the transport; + * a `union XuAddress` + * @param addrlen length of the address + * @param numeric should (IP) addresses be displayed in numeric form? + * @param timeout after how long should we give up? + * @param asc function to call on each string + * @param asc_cls closure for @a asc + */ +static void +xu_plugin_address_pretty_printer (void *cls, + const char *type, + const void *addr, + size_t addrlen, + int numeric, + struct GNUNET_TIME_Relative timeout, + GNUNET_TRANSPORT_AddressStringCallback asc, + void *asc_cls) +{ + struct Plugin *plugin = cls; + struct PrettyPrinterContext *ppc; + const struct sockaddr *sb; + size_t sbs; + struct sockaddr_in a4; + struct sockaddr_in6 a6; + const struct IPv4XuAddress *u4; + const struct IPv6XuAddress *u6; + uint16_t port; + uint32_t options; + + (void) type; + if (addrlen == sizeof(struct IPv6XuAddress)) + { + u6 = addr; + memset (&a6, + 0, + sizeof (a6)); + a6.sin6_family = AF_INET6; +#if HAVE_SOCKADDR_IN_SIN_LEN + a6.sin6_len = sizeof (a6); +#endif + a6.sin6_port = u6->u6_port; + a6.sin6_addr = u6->ipv6_addr; + port = ntohs (u6->u6_port); + options = ntohl (u6->options); + sb = (const struct sockaddr *) &a6; + sbs = sizeof (a6); + } + else if (addrlen == sizeof (struct IPv4XuAddress)) + { + u4 = addr; + memset (&a4, + 0, + sizeof(a4)); + a4.sin_family = AF_INET; +#if HAVE_SOCKADDR_IN_SIN_LEN + a4.sin_len = sizeof (a4); +#endif + a4.sin_port = u4->u4_port; + a4.sin_addr.s_addr = u4->ipv4_addr; + port = ntohs (u4->u4_port); + options = ntohl (u4->options); + sb = (const struct sockaddr *) &a4; + sbs = sizeof(a4); + } + else + { + /* invalid address */ + GNUNET_break_op (0); + asc (asc_cls, + NULL, + GNUNET_SYSERR); + asc (asc_cls, + NULL, + GNUNET_OK); + return; + } + ppc = GNUNET_new (struct PrettyPrinterContext); + ppc->plugin = plugin; + ppc->asc = asc; + ppc->asc_cls = asc_cls; + ppc->port = port; + ppc->options = options; + if (addrlen == sizeof (struct IPv6XuAddress)) + ppc->ipv6 = GNUNET_YES; + else + ppc->ipv6 = GNUNET_NO; + GNUNET_CONTAINER_DLL_insert (plugin->ppc_dll_head, + plugin->ppc_dll_tail, + ppc); + ppc->resolver_handle + = GNUNET_RESOLVER_hostname_get (sb, + sbs, + ! numeric, + timeout, + &append_port, + ppc); +} + + +/** + * Check if the given port is plausible (must be either our listen + * port or our advertised port). If it is neither, we return + * #GNUNET_SYSERR. + * + * @param plugin global variables + * @param in_port port number to check + * @return #GNUNET_OK if port is either our open or advertised port + */ +static int +check_port (const struct Plugin *plugin, + uint16_t in_port) +{ + if ( (plugin->port == in_port) || + (plugin->aport == in_port) ) + return GNUNET_OK; + return GNUNET_SYSERR; +} + + +/** + * Function that will be called to check if a binary address for this + * plugin is well-formed and corresponds to an address for THIS peer + * (as per our configuration). Naturally, if absolutely necessary, + * plugins can be a bit conservative in their answer, but in general + * plugins should make sure that the address does not redirect + * traffic to a 3rd party that might try to man-in-the-middle our + * traffic. + * + * @param cls closure, should be our handle to the Plugin + * @param addr pointer to a `union XuAddress` + * @param addrlen length of @a addr + * @return #GNUNET_OK if this is a plausible address for this peer + * and transport, #GNUNET_SYSERR if not + */ +static int +xu_plugin_check_address (void *cls, + const void *addr, + size_t addrlen) +{ + struct Plugin *plugin = cls; + const struct IPv4XuAddress *v4; + const struct IPv6XuAddress *v6; + + if (sizeof(struct IPv4XuAddress) == addrlen) + { + struct sockaddr_in s4; + + v4 = (const struct IPv4XuAddress *) addr; + if (GNUNET_OK != check_port (plugin, + ntohs (v4->u4_port))) + return GNUNET_SYSERR; + memset (&s4, 0, sizeof (s4)); + s4.sin_family = AF_INET; +#if HAVE_SOCKADDR_IN_SIN_LEN + s4.sin_len = sizeof (s4); +#endif + s4.sin_port = v4->u4_port; + s4.sin_addr.s_addr = v4->ipv4_addr; + + if (GNUNET_OK != + GNUNET_NAT_test_address (plugin->nat, + &s4, + sizeof (struct sockaddr_in))) + return GNUNET_SYSERR; + } + else if (sizeof(struct IPv6XuAddress) == addrlen) + { + struct sockaddr_in6 s6; + + v6 = (const struct IPv6XuAddress *) addr; + if (IN6_IS_ADDR_LINKLOCAL (&v6->ipv6_addr)) + return GNUNET_OK; /* plausible, if unlikely... */ + memset (&s6, 0, sizeof (s6)); + s6.sin6_family = AF_INET6; +#if HAVE_SOCKADDR_IN_SIN_LEN + s6.sin6_len = sizeof (s6); +#endif + s6.sin6_port = v6->u6_port; + s6.sin6_addr = v6->ipv6_addr; + + if (GNUNET_OK != + GNUNET_NAT_test_address (plugin->nat, + &s6, + sizeof(struct sockaddr_in6))) + return GNUNET_SYSERR; + } + else + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Our external IP address/port mapping has changed. + * + * @param cls closure, the `struct Plugin` + * @param add_remove #GNUNET_YES to mean the new public IP address, + * #GNUNET_NO to mean the previous (now invalid) one + * @param ac address class the address belongs to + * @param addr either the previous or the new public IP address + * @param addrlen actual length of the @a addr + */ +static void +xu_nat_port_map_callback (void *cls, + int add_remove, + enum GNUNET_NAT_AddressClass ac, + const struct sockaddr *addr, + socklen_t addrlen) +{ + struct Plugin *plugin = cls; + struct GNUNET_HELLO_Address *address; + struct IPv4XuAddress u4; + struct IPv6XuAddress u6; + void *arg; + size_t args; + + if (GNUNET_NAT_AC_LOOPBACK == ac) + return; + if (GNUNET_NAT_AC_LAN == ac) + return; + if (GNUNET_NAT_AC_LAN_PRIVATE == ac) + return; + LOG (GNUNET_ERROR_TYPE_DEBUG, + (GNUNET_YES == add_remove) + ? "NAT notification to add address `%s'\n" + : "NAT notification to remove address `%s'\n", + GNUNET_a2s (addr, + addrlen)); + /* convert 'address' to our internal format */ + switch (addr->sa_family) + { + case AF_INET: + { + const struct sockaddr_in *i4; + + GNUNET_assert (sizeof(struct sockaddr_in) == addrlen); + i4 = (const struct sockaddr_in *) addr; + if (0 == ntohs (i4->sin_port)) + return; /* Port = 0 means unmapped, ignore these for XU. */ + memset (&u4, + 0, + sizeof(u4)); + u4.options = htonl (plugin->myoptions); + u4.ipv4_addr = i4->sin_addr.s_addr; + u4.u4_port = i4->sin_port; + arg = &u4; + args = sizeof (struct IPv4XuAddress); + break; + } + case AF_INET6: + { + const struct sockaddr_in6 *i6; + + GNUNET_assert (sizeof(struct sockaddr_in6) == addrlen); + i6 = (const struct sockaddr_in6 *) addr; + if (0 == ntohs (i6->sin6_port)) + return; /* Port = 0 means unmapped, ignore these for XU. */ + memset (&u6, + 0, + sizeof(u6)); + u6.options = htonl (plugin->myoptions); + u6.ipv6_addr = i6->sin6_addr; + u6.u6_port = i6->sin6_port; + arg = &u6; + args = sizeof (struct IPv6XuAddress); + break; + } + default: + GNUNET_break (0); + return; + } + /* modify our published address list */ + /* TODO: use 'ac' here in the future... */ + address = GNUNET_HELLO_address_allocate (plugin->env->my_identity, + PLUGIN_NAME, + arg, + args, + GNUNET_HELLO_ADDRESS_INFO_NONE); + plugin->env->notify_address (plugin->env->cls, + add_remove, + address); + GNUNET_HELLO_address_free (address); +} + + +/* ********************* Finding sessions ******************* */ + + +/** + * Closure for #session_cmp_it(). + */ +struct GNUNET_ATS_SessionCompareContext +{ + /** + * Set to session matching the address. + */ + struct GNUNET_ATS_Session *res; + + /** + * Address we are looking for. + */ + const struct GNUNET_HELLO_Address *address; +}; + + +/** + * Find a session with a matching address. + * + * @param cls the `struct GNUNET_ATS_SessionCompareContext *` + * @param key peer identity (unused) + * @param value the `struct GNUNET_ATS_Session *` + * @return #GNUNET_NO if we found the session, #GNUNET_OK if not + */ +static int +session_cmp_it (void *cls, + const struct GNUNET_PeerIdentity *key, + void *value) +{ + struct GNUNET_ATS_SessionCompareContext *cctx = cls; + struct GNUNET_ATS_Session *s = value; + + (void) key; + if (0 == GNUNET_HELLO_address_cmp (s->address, + cctx->address)) + { + GNUNET_assert (GNUNET_NO == s->in_destroy); + cctx->res = s; + return GNUNET_NO; + } + return GNUNET_OK; +} + + +/** + * Locate an existing session the transport service is using to + * send data to another peer. Performs some basic sanity checks + * on the address and then tries to locate a matching session. + * + * @param cls the plugin + * @param address the address we should locate the session by + * @return the session if it exists, or NULL if it is not found + */ +static struct GNUNET_ATS_Session * +xu_plugin_lookup_session (void *cls, + const struct GNUNET_HELLO_Address *address) +{ + struct Plugin *plugin = cls; + const struct IPv6XuAddress *xu_a6; + const struct IPv4XuAddress *xu_a4; + struct GNUNET_ATS_SessionCompareContext cctx; + + if (NULL == address->address) + { + GNUNET_break (0); + return NULL; + } + if (sizeof(struct IPv4XuAddress) == address->address_length) + { + if (NULL == plugin->sockv4) + return NULL; + xu_a4 = (const struct IPv4XuAddress *) address->address; + if (0 == xu_a4->u4_port) + { + GNUNET_break (0); + return NULL; + } + } + else if (sizeof(struct IPv6XuAddress) == address->address_length) + { + if (NULL == plugin->sockv6) + return NULL; + xu_a6 = (const struct IPv6XuAddress *) address->address; + if (0 == xu_a6->u6_port) + { + GNUNET_break (0); + return NULL; + } + } + else + { + GNUNET_break (0); + return NULL; + } + + /* check if session already exists */ + cctx.address = address; + cctx.res = NULL; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Looking for existing session for peer `%s' with address `%s'\n", + GNUNET_i2s (&address->peer), + xu_address_to_string (plugin, + address->address, + address->address_length)); + GNUNET_CONTAINER_multipeermap_get_multiple (plugin->sessions, + &address->peer, + &session_cmp_it, + &cctx); + if (NULL == cctx.res) + return NULL; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Found existing session %p\n", + cctx.res); + return cctx.res; +} + + +/* ********************** Timeout ****************** */ + + +/** + * Increment session timeout due to activity. + * + * @param s session to reschedule timeout activity for + */ +static void +reschedule_session_timeout (struct GNUNET_ATS_Session *s) +{ + if (GNUNET_YES == s->in_destroy) + return; + GNUNET_assert (NULL != s->timeout_task); + s->timeout = GNUNET_TIME_relative_to_absolute (XU_SESSION_TIME_OUT); +} + + + +/** + * Function that will be called whenever the transport service wants to + * notify the plugin that a session is still active and in use and + * therefore the session timeout for this session has to be updated + * + * @param cls closure with the `struct Plugin` + * @param peer which peer was the session for + * @param session which session is being updated + */ +static void +xu_plugin_update_session_timeout (void *cls, + const struct GNUNET_PeerIdentity *peer, + struct GNUNET_ATS_Session *session) +{ + struct Plugin *plugin = cls; + + if (GNUNET_YES != + GNUNET_CONTAINER_multipeermap_contains_value (plugin->sessions, + peer, + session)) + { + GNUNET_break (0); + return; + } + /* Reschedule session timeout */ + reschedule_session_timeout (session); +} + + +/* ************************* Sending ************************ */ + + +/** + * We failed to transmit a message via XU. Generate + * a descriptive error message. + * + * @param plugin our plugin + * @param sa target address we were trying to reach + * @param slen number of bytes in @a sa + * @param error the errno value returned from the sendto() call + */ +static void +analyze_send_error (struct Plugin *plugin, + const struct sockaddr *sa, + socklen_t slen, + int error) +{ + enum GNUNET_ATS_Network_Type type; + + type = plugin->env->get_address_type (plugin->env->cls, + sa, + slen); + if ( ( (GNUNET_ATS_NET_LAN == type) || + (GNUNET_ATS_NET_WAN == type) ) && + ( (ENETUNREACH == errno) || + (ENETDOWN == errno) ) ) + { + if (slen == sizeof (struct sockaddr_in)) + { + /* IPv4: "Network unreachable" or "Network down" + * + * This indicates we do not have connectivity + */ + LOG (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK, + _("XU could not transmit message to `%s': " + "Network seems down, please check your network configuration\n"), + GNUNET_a2s (sa, + slen)); + } + if (slen == sizeof (struct sockaddr_in6)) + { + /* IPv6: "Network unreachable" or "Network down" + * + * This indicates that this system is IPv6 enabled, but does not + * have a valid global IPv6 address assigned or we do not have + * connectivity + */ + LOG (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK, + _("XU could not transmit IPv6 message! " + "Please check your network configuration and disable IPv6 if your " + "connection does not have a global IPv6 address\n")); + } + } + else + { + LOG (GNUNET_ERROR_TYPE_WARNING, + "XU could not transmit message to `%s': `%s'\n", + GNUNET_a2s (sa, + slen), + STRERROR (error)); + } +} + + + + +/** + * Function that can be used by the transport service to transmit a + * message using the plugin. Note that in the case of a peer + * disconnecting, the continuation MUST be called prior to the + * disconnect notification itself. This function will be called with + * this peer's HELLO message to initiate a fresh connection to another + * peer. + * + * @param cls closure + * @param s which session must be used + * @param msgbuf the message to transmit + * @param msgbuf_size number of bytes in @a msgbuf + * @param priority how important is the message (most plugins will + * ignore message priority and just FIFO) + * @param to how long to wait at most for the transmission (does not + * require plugins to discard the message after the timeout, + * just advisory for the desired delay; most plugins will ignore + * this as well) + * @param cont continuation to call once the message has + * been transmitted (or if the transport is ready + * for the next transmission call; or if the + * peer disconnected...); can be NULL + * @param cont_cls closure for @a cont + * @return number of bytes used (on the physical network, with overheads); + * -1 on hard errors (i.e. address invalid); 0 is a legal value + * and does NOT mean that the message was not transmitted (DV) + */ +static ssize_t +xu_plugin_send (void *cls, + struct GNUNET_ATS_Session *s, + const char *msgbuf, + size_t msgbuf_size, + unsigned int priority, + struct GNUNET_TIME_Relative to, + GNUNET_TRANSPORT_TransmitContinuation cont, + void *cont_cls) +{ + struct Plugin *plugin = cls; + size_t xumlen = msgbuf_size + sizeof(struct XUMessage); + struct XUMessage *xu; + char mbuf[xumlen] GNUNET_ALIGN; + ssize_t sent; + socklen_t slen; + const struct sockaddr *a; + const struct IPv4XuAddress *u4; + struct sockaddr_in a4; + const struct IPv6XuAddress *u6; + struct sockaddr_in6 a6; + struct GNUNET_NETWORK_Handle *sock; + + (void) priority; + (void) to; + if ( (sizeof(struct IPv6XuAddress) == s->address->address_length) && + (NULL == plugin->sockv6) ) + return GNUNET_SYSERR; + if ( (sizeof(struct IPv4XuAddress) == s->address->address_length) && + (NULL == plugin->sockv4) ) + return GNUNET_SYSERR; + if (xumlen >= GNUNET_MAX_MESSAGE_SIZE) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_YES != + GNUNET_CONTAINER_multipeermap_contains_value (plugin->sessions, + &s->target, + s)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "XU transmits %u-byte message to `%s' using address `%s'\n", + xumlen, + GNUNET_i2s (&s->target), + xu_address_to_string (plugin, + s->address->address, + s->address->address_length)); + xu = (struct XUMessage *) mbuf; + xu->header.size = htons (xumlen); + xu->header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_XU_MESSAGE); + xu->reserved = htonl (0); + xu->sender = *plugin->env->my_identity; + GNUNET_memcpy (&xu[1], + msgbuf, + msgbuf_size); + + if (sizeof (struct IPv4XuAddress) == s->address->address_length) + { + u4 = s->address->address; + memset (&a4, + 0, + sizeof(a4)); + a4.sin_family = AF_INET; +#if HAVE_SOCKADDR_IN_SIN_LEN + a4.sin_len = sizeof (a4); +#endif + a4.sin_port = u4->u4_port; + a4.sin_addr.s_addr = u4->ipv4_addr; + a = (const struct sockaddr *) &a4; + slen = sizeof (a4); + sock = plugin->sockv4; + } + else if (sizeof (struct IPv6XuAddress) == s->address->address_length) + { + u6 = s->address->address; + memset (&a6, + 0, + sizeof(a6)); + a6.sin6_family = AF_INET6; +#if HAVE_SOCKADDR_IN_SIN_LEN + a6.sin6_len = sizeof (a6); +#endif + a6.sin6_port = u6->u6_port; + a6.sin6_addr = u6->ipv6_addr; + a = (const struct sockaddr *) &a6; + slen = sizeof (a6); + sock = plugin->sockv6; + } + else + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + sent = GNUNET_NETWORK_socket_sendto (sock, + mbuf, + xumlen, + a, + slen); + s->last_transmit_time + = GNUNET_TIME_absolute_max (GNUNET_TIME_absolute_get (), + s->last_transmit_time); + + if (GNUNET_SYSERR == sent) + { + /* Failure */ + analyze_send_error (plugin, + a, + slen, + errno); + GNUNET_STATISTICS_update (plugin->env->stats, + "# XU, total, bytes, sent, failure", + sent, + GNUNET_NO); + GNUNET_STATISTICS_update (plugin->env->stats, + "# XU, total, messages, sent, failure", + 1, + GNUNET_NO); + return GNUNET_SYSERR; + } + /* Success */ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "XU transmitted %u-byte message to `%s' `%s' (%d: %s)\n", + (unsigned int) (msgbuf_size), + GNUNET_i2s (&s->target), + GNUNET_a2s (a, + slen), + (int ) sent, + (sent < 0) ? STRERROR (errno) : "ok"); + GNUNET_STATISTICS_update (plugin->env->stats, + "# XU, total, bytes, sent, success", + sent, + GNUNET_NO); + GNUNET_STATISTICS_update (plugin->env->stats, + "# XU, total, messages, sent, success", + 1, + GNUNET_NO); + cont (cont_cls, + &s->target, + GNUNET_OK, + msgbuf_size, + xumlen); + notify_session_monitor (s->plugin, + s, + GNUNET_TRANSPORT_SS_UPDATE); + return xumlen; +} + + +/* ********************** Receiving ********************** */ + + +/** + * Functions with this signature are called whenever we need to close + * a session due to a disconnect or failure to establish a connection. + * + * @param cls closure with the `struct Plugin` + * @param s session to close down + * @return #GNUNET_OK on success + */ +static int +xu_disconnect_session (void *cls, + struct GNUNET_ATS_Session *s) +{ + struct Plugin *plugin = cls; + + GNUNET_assert (GNUNET_YES != s->in_destroy); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Session %p to peer `%s' at address %s ended\n", + s, + GNUNET_i2s (&s->target), + xu_address_to_string (plugin, + s->address->address, + s->address->address_length)); + if (NULL != s->timeout_task) + { + GNUNET_SCHEDULER_cancel (s->timeout_task); + s->timeout_task = NULL; + } + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multipeermap_remove (plugin->sessions, + &s->target, + s)); + s->in_destroy = GNUNET_YES; + notify_session_monitor (s->plugin, + s, + GNUNET_TRANSPORT_SS_DONE); + plugin->env->session_end (plugin->env->cls, + s->address, + s); + GNUNET_STATISTICS_set (plugin->env->stats, + "# XU sessions active", + GNUNET_CONTAINER_multipeermap_size (plugin->sessions), + GNUNET_NO); + if (0 == s->rc) + free_session (s); + return GNUNET_OK; +} + + +/** + * Message tokenizer has broken up an incomming message. Pass it on + * to the service. + * + * @param cls the `struct GNUNET_ATS_Session *` + * @param hdr the actual message + * @return #GNUNET_OK (always) + */ +static int +process_inbound_tokenized_messages (void *cls, + const struct GNUNET_MessageHeader *hdr) +{ + struct GNUNET_ATS_Session *session = cls; + struct Plugin *plugin = session->plugin; + + if (GNUNET_YES == session->in_destroy) + return GNUNET_OK; + reschedule_session_timeout (session); + session->flow_delay_for_other_peer + = plugin->env->receive (plugin->env->cls, + session->address, + session, + hdr); + return GNUNET_OK; +} + + +/** + * Destroy a session, plugin is being unloaded. + * + * @param cls the `struct Plugin` + * @param key hash of public key of target peer + * @param value a `struct PeerSession *` to clean up + * @return #GNUNET_OK (continue to iterate) + */ +static int +disconnect_and_free_it (void *cls, + const struct GNUNET_PeerIdentity *key, + void *value) +{ + struct Plugin *plugin = cls; + + (void) key; + xu_disconnect_session (plugin, + value); + return GNUNET_OK; +} + + +/** + * Disconnect from a remote node. Clean up session if we have one for + * this peer. + * + * @param cls closure for this call (should be handle to Plugin) + * @param target the peeridentity of the peer to disconnect + * @return #GNUNET_OK on success, #GNUNET_SYSERR if the operation failed + */ +static void +xu_disconnect (void *cls, + const struct GNUNET_PeerIdentity *target) +{ + struct Plugin *plugin = cls; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Disconnecting from peer `%s'\n", + GNUNET_i2s (target)); + GNUNET_CONTAINER_multipeermap_get_multiple (plugin->sessions, + target, + &disconnect_and_free_it, + plugin); +} + + +/** + * Session was idle, so disconnect it. + * + * @param cls the `struct GNUNET_ATS_Session` to time out + */ +static void +session_timeout (void *cls) +{ + struct GNUNET_ATS_Session *s = cls; + struct Plugin *plugin = s->plugin; + struct GNUNET_TIME_Relative left; + + s->timeout_task = NULL; + left = GNUNET_TIME_absolute_get_remaining (s->timeout); + if (left.rel_value_us > 0) + { + /* not actually our turn yet, but let's at least update + the monitor, it may think we're about to die ... */ + notify_session_monitor (s->plugin, + s, + GNUNET_TRANSPORT_SS_UPDATE); + s->timeout_task = GNUNET_SCHEDULER_add_delayed (left, + &session_timeout, + s); + return; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Session %p was idle for %s, disconnecting\n", + s, + GNUNET_STRINGS_relative_time_to_string (XU_SESSION_TIME_OUT, + GNUNET_YES)); + /* call session destroy function */ + xu_disconnect_session (plugin, + s); +} + + +/** + * Allocate a new session for the given endpoint address. + * Note that this function does not inform the service + * of the new session, this is the responsibility of the + * caller (if needed). + * + * @param cls the `struct Plugin` + * @param address address of the other peer to use + * @param network_type network type the address belongs to + * @return NULL on error, otherwise session handle + */ +static struct GNUNET_ATS_Session * +xu_plugin_create_session (void *cls, + const struct GNUNET_HELLO_Address *address, + enum GNUNET_ATS_Network_Type network_type) +{ + struct Plugin *plugin = 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; + s->last_transmit_time = GNUNET_TIME_absolute_get (); + s->last_expected_ack_delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + 250); + s->last_expected_msg_delay = GNUNET_TIME_UNIT_MILLISECONDS; + s->flow_delay_from_other_peer = GNUNET_TIME_UNIT_ZERO; + s->flow_delay_for_other_peer = GNUNET_TIME_UNIT_ZERO; + s->timeout = GNUNET_TIME_relative_to_absolute (XU_SESSION_TIME_OUT); + s->timeout_task = GNUNET_SCHEDULER_add_delayed (XU_SESSION_TIME_OUT, + &session_timeout, + s); + s->scope = network_type; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Creating new session %p for peer `%s' address `%s'\n", + s, + GNUNET_i2s (&address->peer), + xu_address_to_string (plugin, + address->address, + address->address_length)); + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multipeermap_put (plugin->sessions, + &s->target, + s, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE)); + GNUNET_STATISTICS_set (plugin->env->stats, + "# XU sessions active", + GNUNET_CONTAINER_multipeermap_size (plugin->sessions), + GNUNET_NO); + notify_session_monitor (plugin, + s, + GNUNET_TRANSPORT_SS_INIT); + return s; +} + + +/** + * Creates a new outbound session the transport service will use to + * send data to the peer. + * + * @param cls the `struct Plugin *` + * @param address the address + * @return the session or NULL of max connections exceeded + */ +static struct GNUNET_ATS_Session * +xu_plugin_get_session (void *cls, + const struct GNUNET_HELLO_Address *address) +{ + struct Plugin *plugin = cls; + struct GNUNET_ATS_Session *s; + enum GNUNET_ATS_Network_Type network_type = GNUNET_ATS_NET_UNSPECIFIED; + const struct IPv4XuAddress *xu_v4; + const struct IPv6XuAddress *xu_v6; + + if (NULL == address) + { + GNUNET_break (0); + return NULL; + } + if ( (address->address_length != sizeof(struct IPv4XuAddress)) && + (address->address_length != sizeof(struct IPv6XuAddress)) ) + { + GNUNET_break_op (0); + return NULL; + } + if (NULL != (s = xu_plugin_lookup_session (cls, + address))) + return s; + + /* need to create new session */ + if (sizeof (struct IPv4XuAddress) == address->address_length) + { + struct sockaddr_in v4; + + xu_v4 = (const struct IPv4XuAddress *) address->address; + memset (&v4, '\0', sizeof (v4)); + v4.sin_family = AF_INET; +#if HAVE_SOCKADDR_IN_SIN_LEN + v4.sin_len = sizeof (struct sockaddr_in); +#endif + v4.sin_port = xu_v4->u4_port; + v4.sin_addr.s_addr = xu_v4->ipv4_addr; + network_type = plugin->env->get_address_type (plugin->env->cls, + (const struct sockaddr *) &v4, + sizeof (v4)); + } + if (sizeof (struct IPv6XuAddress) == address->address_length) + { + struct sockaddr_in6 v6; + + xu_v6 = (const struct IPv6XuAddress *) address->address; + memset (&v6, '\0', sizeof (v6)); + v6.sin6_family = AF_INET6; +#if HAVE_SOCKADDR_IN_SIN_LEN + v6.sin6_len = sizeof (struct sockaddr_in6); +#endif + v6.sin6_port = xu_v6->u6_port; + v6.sin6_addr = xu_v6->ipv6_addr; + network_type = plugin->env->get_address_type (plugin->env->cls, + (const struct sockaddr *) &v6, + sizeof (v6)); + } + GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != network_type); + return xu_plugin_create_session (cls, + address, + network_type); +} + + +/** + * We've received a XU Message. Process it (pass contents to main service). + * + * @param plugin plugin context + * @param msg the message + * @param xu_addr sender address + * @param xu_addr_len number of bytes in @a xu_addr + * @param network_type network type the address belongs to + */ +static void +process_xu_message (struct Plugin *plugin, + const struct XUMessage *msg, + const union XuAddress *xu_addr, + size_t xu_addr_len, + enum GNUNET_ATS_Network_Type network_type) +{ + struct GNUNET_ATS_Session *s; + struct GNUNET_HELLO_Address *address; + + GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != network_type); + if (0 != ntohl (msg->reserved)) + { + GNUNET_break_op(0); + return; + } + if (ntohs (msg->header.size) + < sizeof(struct GNUNET_MessageHeader) + sizeof(struct XUMessage)) + { + GNUNET_break_op(0); + return; + } + + address = GNUNET_HELLO_address_allocate (&msg->sender, + PLUGIN_NAME, + xu_addr, + xu_addr_len, + GNUNET_HELLO_ADDRESS_INFO_NONE); + if (NULL == + (s = xu_plugin_lookup_session (plugin, + address))) + { + s = xu_plugin_create_session (plugin, + address, + network_type); + plugin->env->session_start (plugin->env->cls, + address, + s, + s->scope); + notify_session_monitor (plugin, + s, + GNUNET_TRANSPORT_SS_UP); + } + GNUNET_free (address); + + s->rc++; + GNUNET_MST_from_buffer (s->mst, + (const char *) &msg[1], + ntohs (msg->header.size) - sizeof(struct XUMessage), + GNUNET_YES, + GNUNET_NO); + s->rc--; + if ( (0 == s->rc) && + (GNUNET_YES == s->in_destroy) ) + free_session (s); +} + + +/** + * Read and process a message from the given socket. + * + * @param plugin the overall plugin + * @param rsock socket to read from + */ +static void +xu_select_read (struct Plugin *plugin, + struct GNUNET_NETWORK_Handle *rsock) +{ + socklen_t fromlen; + struct sockaddr_storage addr; + char buf[65536] GNUNET_ALIGN; + ssize_t size; + const struct GNUNET_MessageHeader *msg; + struct IPv4XuAddress v4; + struct IPv6XuAddress v6; + const struct sockaddr *sa; + const struct sockaddr_in *sa4; + const struct sockaddr_in6 *sa6; + const union XuAddress *int_addr; + size_t int_addr_len; + enum GNUNET_ATS_Network_Type network_type; + + fromlen = sizeof (addr); + memset (&addr, + 0, + sizeof(addr)); + size = GNUNET_NETWORK_socket_recvfrom (rsock, + buf, + sizeof (buf), + (struct sockaddr *) &addr, + &fromlen); + sa = (const struct sockaddr *) &addr; +#if MINGW + /* On SOCK_DGRAM XU sockets recvfrom might fail with a + * WSAECONNRESET error to indicate that previous sendto() (yes, sendto!) + * on this socket has failed. + * Quote from MSDN: + * WSAECONNRESET - The virtual circuit was reset by the remote side + * executing a hard or abortive close. The application should close + * the socket; it is no longer usable. On a XU-datagram socket this + * error indicates a previous send operation resulted in an ICMP Port + * Unreachable message. + */ + if ( (-1 == size) && + (ECONNRESET == errno) ) + return; +#endif + if (-1 == size) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "XU failed to receive data: %s\n", + STRERROR (errno)); + /* Connection failure or something. Not a protocol violation. */ + return; + } + + /* Check if this is a STUN packet */ + if (GNUNET_NO != + GNUNET_NAT_stun_handle_packet (plugin->nat, + (const struct sockaddr *) &addr, + fromlen, + buf, + size)) + return; /* was STUN, do not process further */ + + if (((size_t) size) < sizeof(struct GNUNET_MessageHeader)) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + "XU got %u bytes from %s, which is not enough for a GNUnet message header\n", + (unsigned int ) size, + GNUNET_a2s (sa, + fromlen)); + /* _MAY_ be a connection failure (got partial message) */ + /* But it _MAY_ also be that the other side uses non-GNUnet protocol. */ + GNUNET_break_op (0); + return; + } + + msg = (const struct GNUNET_MessageHeader *) buf; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "XU received %u-byte message from `%s' type %u\n", + (unsigned int) size, + GNUNET_a2s (sa, + fromlen), + ntohs (msg->type)); + if (size != ntohs (msg->size)) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + "XU malformed message (size %u) header from %s\n", + (unsigned int) size, + GNUNET_a2s (sa, + fromlen)); + GNUNET_break_op (0); + return; + } + GNUNET_STATISTICS_update (plugin->env->stats, + "# XU, total bytes received", + size, + GNUNET_NO); + network_type = plugin->env->get_address_type (plugin->env->cls, + sa, + fromlen); + switch (sa->sa_family) + { + case AF_INET: + sa4 = (const struct sockaddr_in *) &addr; + v4.options = 0; + v4.ipv4_addr = sa4->sin_addr.s_addr; + v4.u4_port = sa4->sin_port; + int_addr = (union XuAddress *) &v4; + int_addr_len = sizeof (v4); + break; + case AF_INET6: + sa6 = (const struct sockaddr_in6 *) &addr; + v6.options = 0; + v6.ipv6_addr = sa6->sin6_addr; + v6.u6_port = sa6->sin6_port; + int_addr = (union XuAddress *) &v6; + int_addr_len = sizeof (v6); + break; + default: + GNUNET_break (0); + return; + } + + switch (ntohs (msg->type)) + { + case GNUNET_MESSAGE_TYPE_TRANSPORT_XU_MESSAGE: + if (ntohs (msg->size) < sizeof(struct XUMessage)) + { + GNUNET_break_op(0); + return; + } + process_xu_message (plugin, + (const struct XUMessage *) msg, + int_addr, + int_addr_len, + network_type); + return; + default: + GNUNET_break_op(0); + return; + } +} + + +/* ***************** Event loop (part 2) *************** */ + + +/** + * We have been notified that our readset has something to read. We don't + * know which socket needs to be read, so we have to check each one + * Then reschedule this function to be called again once more is available. + * + * @param cls the plugin handle + */ +static void +xu_plugin_select_v4 (void *cls) +{ + struct Plugin *plugin = cls; + const struct GNUNET_SCHEDULER_TaskContext *tc; + + plugin->select_task_v4 = NULL; + if (NULL == plugin->sockv4) + return; + tc = GNUNET_SCHEDULER_get_task_context (); + if ( (0 != (tc->reason & GNUNET_SCHEDULER_REASON_READ_READY)) && + (GNUNET_NETWORK_fdset_isset (tc->read_ready, + plugin->sockv4)) ) + xu_select_read (plugin, + plugin->sockv4); + schedule_select_v4 (plugin); +} + + +/** + * We have been notified that our readset has something to read. We don't + * know which socket needs to be read, so we have to check each one + * Then reschedule this function to be called again once more is available. + * + * @param cls the plugin handle + */ +static void +xu_plugin_select_v6 (void *cls) +{ + struct Plugin *plugin = cls; + const struct GNUNET_SCHEDULER_TaskContext *tc; + + plugin->select_task_v6 = NULL; + if (NULL == plugin->sockv6) + return; + tc = GNUNET_SCHEDULER_get_task_context (); + if ( (0 != (tc->reason & GNUNET_SCHEDULER_REASON_READ_READY)) && + (GNUNET_NETWORK_fdset_isset (tc->read_ready, + plugin->sockv6)) ) + xu_select_read (plugin, + plugin->sockv6); + schedule_select_v6 (plugin); +} + + +/* ******************* Initialization *************** */ + + +/** + * Setup the XU sockets (for IPv4 and IPv6) for the plugin. + * + * @param plugin the plugin to initialize + * @param bind_v6 IPv6 address to bind to (can be NULL, for 'any') + * @param bind_v4 IPv4 address to bind to (can be NULL, for 'any') + * @return number of sockets that were successfully bound + */ +static unsigned int +setup_sockets (struct Plugin *plugin, + const struct sockaddr_in6 *bind_v6, + const struct sockaddr_in *bind_v4) +{ + int tries; + unsigned int sockets_created = 0; + struct sockaddr_in6 server_addrv6; + struct sockaddr_in server_addrv4; + const struct sockaddr *server_addr; + const struct sockaddr *addrs[2]; + socklen_t addrlens[2]; + socklen_t addrlen; + int eno; + + /* Create IPv6 socket */ + eno = EINVAL; + if (GNUNET_YES == plugin->enable_ipv6) + { + plugin->sockv6 = GNUNET_NETWORK_socket_create (PF_INET6, + SOCK_DGRAM, + 0); + if (NULL == plugin->sockv6) + { + LOG (GNUNET_ERROR_TYPE_INFO, + _("Disabling IPv6 since it is not supported on this system!\n")); + plugin->enable_ipv6 = GNUNET_NO; + } + else + { + memset (&server_addrv6, + 0, + sizeof(struct sockaddr_in6)); +#if HAVE_SOCKADDR_IN_SIN_LEN + server_addrv6.sin6_len = sizeof (struct sockaddr_in6); +#endif + server_addrv6.sin6_family = AF_INET6; + if (NULL != bind_v6) + server_addrv6.sin6_addr = bind_v6->sin6_addr; + else + server_addrv6.sin6_addr = in6addr_any; + + if (0 == plugin->port) /* autodetect */ + server_addrv6.sin6_port + = htons (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, + 33537) + + 32000); + else + server_addrv6.sin6_port = htons (plugin->port); + addrlen = sizeof (struct sockaddr_in6); + server_addr = (const struct sockaddr *) &server_addrv6; + + tries = 0; + while (tries < 10) + { + LOG(GNUNET_ERROR_TYPE_DEBUG, + "Binding to IPv6 `%s'\n", + GNUNET_a2s (server_addr, + addrlen)); + /* binding */ + if (GNUNET_OK == + GNUNET_NETWORK_socket_bind (plugin->sockv6, + server_addr, + addrlen)) + break; + eno = errno; + if (0 != plugin->port) + { + tries = 10; /* fail immediately */ + break; /* bind failed on specific port */ + } + /* autodetect */ + server_addrv6.sin6_port + = htons (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, + 33537) + + 32000); + tries++; + } + if (tries >= 10) + { + GNUNET_NETWORK_socket_close (plugin->sockv6); + plugin->enable_ipv6 = GNUNET_NO; + plugin->sockv6 = NULL; + } + else + { + plugin->port = ntohs (server_addrv6.sin6_port); + } + if (NULL != plugin->sockv6) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "IPv6 XU socket created listinging at %s\n", + GNUNET_a2s (server_addr, + addrlen)); + addrs[sockets_created] = server_addr; + addrlens[sockets_created] = addrlen; + sockets_created++; + } + else + { + LOG (GNUNET_ERROR_TYPE_WARNING, + _("Failed to bind XU socket to %s: %s\n"), + GNUNET_a2s (server_addr, + addrlen), + STRERROR (eno)); + } + } + } + + /* Create IPv4 socket */ + eno = EINVAL; + plugin->sockv4 = GNUNET_NETWORK_socket_create (PF_INET, + SOCK_DGRAM, + 0); + if (NULL == plugin->sockv4) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "socket"); + LOG (GNUNET_ERROR_TYPE_INFO, + _("Disabling IPv4 since it is not supported on this system!\n")); + plugin->enable_ipv4 = GNUNET_NO; + } + else + { + memset (&server_addrv4, + 0, + sizeof(struct sockaddr_in)); +#if HAVE_SOCKADDR_IN_SIN_LEN + server_addrv4.sin_len = sizeof (struct sockaddr_in); +#endif + server_addrv4.sin_family = AF_INET; + if (NULL != bind_v4) + server_addrv4.sin_addr = bind_v4->sin_addr; + else + server_addrv4.sin_addr.s_addr = INADDR_ANY; + + if (0 == plugin->port) + /* autodetect */ + server_addrv4.sin_port + = htons (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, + 33537) + + 32000); + else + server_addrv4.sin_port = htons (plugin->port); + + addrlen = sizeof (struct sockaddr_in); + server_addr = (const struct sockaddr *) &server_addrv4; + + tries = 0; + while (tries < 10) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Binding to IPv4 `%s'\n", + GNUNET_a2s (server_addr, + addrlen)); + + /* binding */ + if (GNUNET_OK == + GNUNET_NETWORK_socket_bind (plugin->sockv4, + server_addr, + addrlen)) + break; + eno = errno; + if (0 != plugin->port) + { + tries = 10; /* fail */ + break; /* bind failed on specific port */ + } + + /* autodetect */ + server_addrv4.sin_port + = htons (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, + 33537) + + 32000); + tries++; + } + if (tries >= 10) + { + GNUNET_NETWORK_socket_close (plugin->sockv4); + plugin->enable_ipv4 = GNUNET_NO; + plugin->sockv4 = NULL; + } + else + { + plugin->port = ntohs (server_addrv4.sin_port); + } + + if (NULL != plugin->sockv4) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "IPv4 socket created on port %s\n", + GNUNET_a2s (server_addr, + addrlen)); + addrs[sockets_created] = server_addr; + addrlens[sockets_created] = addrlen; + sockets_created++; + } + else + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Failed to bind XU socket to %s: %s\n"), + GNUNET_a2s (server_addr, + addrlen), + STRERROR (eno)); + } + } + + if (0 == sockets_created) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + _("Failed to open XU sockets\n")); + return 0; /* No sockets created, return */ + } + schedule_select_v4 (plugin); + schedule_select_v6 (plugin); + plugin->nat = GNUNET_NAT_register (plugin->env->cfg, + "transport-xu", + IPPROTO_UDP, + sockets_created, + addrs, + addrlens, + &xu_nat_port_map_callback, + NULL, + plugin); + return sockets_created; +} + + +/** + * The exported method. Makes the core api available via a global and + * returns the xu transport API. + * + * @param cls our `struct GNUNET_TRANSPORT_PluginEnvironment` + * @return our `struct GNUNET_TRANSPORT_PluginFunctions` + */ +void * +libgnunet_plugin_transport_xu_init (void *cls) +{ + struct GNUNET_TRANSPORT_PluginEnvironment *env = cls; + struct GNUNET_TRANSPORT_PluginFunctions *api; + struct Plugin *p; + unsigned long long port; + unsigned long long aport; + int enable_v6; + char *bind4_address; + char *bind6_address; + struct sockaddr_in server_addrv4; + struct sockaddr_in6 server_addrv6; + unsigned int res; + int have_bind4; + int have_bind6; + + if (NULL == env->receive) + { + /* run in 'stub' mode (i.e. as part of gnunet-peerinfo), don't fully + initialze the plugin or the API */ + api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions); + api->cls = NULL; + api->address_pretty_printer = &xu_plugin_address_pretty_printer; + api->address_to_string = &xu_address_to_string; + api->string_to_address = &xu_string_to_address; + return api; + } + + /* Get port number: port == 0 : autodetect a port, + * > 0 : use this port, not given : 2086 default */ + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (env->cfg, + "transport-xu", + "PORT", + &port)) + port = 2086; + if (port > 65535) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "transport-xu", + "PORT", + _("must be in [0,65535]")); + return NULL; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (env->cfg, + "transport-xu", + "ADVERTISED_PORT", + &aport)) + aport = port; + if (aport > 65535) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "transport-xu", + "ADVERTISED_PORT", + _("must be in [0,65535]")); + return NULL; + } + + if (GNUNET_YES == + GNUNET_CONFIGURATION_get_value_yesno (env->cfg, + "nat", + "DISABLEV6")) + enable_v6 = GNUNET_NO; + else + enable_v6 = GNUNET_YES; + + have_bind4 = GNUNET_NO; + memset (&server_addrv4, + 0, + sizeof (server_addrv4)); + if (GNUNET_YES == + GNUNET_CONFIGURATION_get_value_string (env->cfg, + "transport-xu", + "BINDTO", + &bind4_address)) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Binding XU plugin to specific address: `%s'\n", + bind4_address); + if (1 != inet_pton (AF_INET, + bind4_address, + &server_addrv4.sin_addr)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "transport-xu", + "BINDTO", + _("must be valid IPv4 address")); + GNUNET_free (bind4_address); + return NULL; + } + have_bind4 = GNUNET_YES; + } + GNUNET_free_non_null (bind4_address); + have_bind6 = GNUNET_NO; + memset (&server_addrv6, + 0, + sizeof (server_addrv6)); + if (GNUNET_YES == + GNUNET_CONFIGURATION_get_value_string (env->cfg, + "transport-xu", + "BINDTO6", + &bind6_address)) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Binding xu plugin to specific address: `%s'\n", + bind6_address); + if (1 != inet_pton (AF_INET6, + bind6_address, + &server_addrv6.sin6_addr)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "transport-xu", + "BINDTO6", + _("must be valid IPv6 address")); + GNUNET_free (bind6_address); + return NULL; + } + have_bind6 = GNUNET_YES; + } + GNUNET_free_non_null (bind6_address); + + p = GNUNET_new (struct Plugin); + p->port = port; + p->aport = aport; + p->enable_ipv6 = enable_v6; + p->enable_ipv4 = GNUNET_YES; /* default */ + p->env = env; + p->sessions = GNUNET_CONTAINER_multipeermap_create (16, + GNUNET_NO); + res = setup_sockets (p, + (GNUNET_YES == have_bind6) ? &server_addrv6 : NULL, + (GNUNET_YES == have_bind4) ? &server_addrv4 : NULL); + if ( (0 == res) || + ( (NULL == p->sockv4) && + (NULL == p->sockv6) ) ) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Failed to create XU network sockets\n")); + GNUNET_CONTAINER_multipeermap_destroy (p->sessions); + if (NULL != p->nat) + GNUNET_NAT_unregister (p->nat); + GNUNET_free (p); + return NULL; + } + + api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions); + api->cls = p; + api->disconnect_session = &xu_disconnect_session; + api->query_keepalive_factor = &xu_query_keepalive_factor; + api->disconnect_peer = &xu_disconnect; + api->address_pretty_printer = &xu_plugin_address_pretty_printer; + api->address_to_string = &xu_address_to_string; + api->string_to_address = &xu_string_to_address; + api->check_address = &xu_plugin_check_address; + api->get_session = &xu_plugin_get_session; + api->send = &xu_plugin_send; + api->get_network = &xu_plugin_get_network; + api->get_network_for_address = &xu_plugin_get_network_for_address; + api->update_session_timeout = &xu_plugin_update_session_timeout; + api->setup_monitor = &xu_plugin_setup_monitor; + return api; +} + + +/** + * The exported method. Makes the core api available via a global and + * returns the xu transport API. + * + * @param cls our `struct GNUNET_TRANSPORT_PluginEnvironment` + * @return NULL + */ +void * +libgnunet_plugin_transport_xu_done (void *cls) +{ + struct GNUNET_TRANSPORT_PluginFunctions *api = cls; + struct Plugin *plugin = api->cls; + struct PrettyPrinterContext *cur; + + if (NULL == plugin) + { + GNUNET_free (api); + return NULL; + } + if (NULL != plugin->select_task_v4) + { + GNUNET_SCHEDULER_cancel (plugin->select_task_v4); + plugin->select_task_v4 = NULL; + } + if (NULL != plugin->select_task_v6) + { + GNUNET_SCHEDULER_cancel (plugin->select_task_v6); + plugin->select_task_v6 = NULL; + } + if (NULL != plugin->sockv4) + { + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (plugin->sockv4)); + plugin->sockv4 = NULL; + } + if (NULL != plugin->sockv6) + { + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (plugin->sockv6)); + plugin->sockv6 = NULL; + } + if (NULL != plugin->nat) + { + GNUNET_NAT_unregister (plugin->nat); + plugin->nat = NULL; + } + GNUNET_CONTAINER_multipeermap_destroy (plugin->sessions); + + while (NULL != (cur = plugin->ppc_dll_head)) + { + GNUNET_break (0); + GNUNET_CONTAINER_DLL_remove (plugin->ppc_dll_head, + plugin->ppc_dll_tail, + cur); + GNUNET_RESOLVER_request_cancel (cur->resolver_handle); + if (NULL != cur->timeout_task) + { + GNUNET_SCHEDULER_cancel (cur->timeout_task); + cur->timeout_task = NULL; + } + GNUNET_free (cur); + } + GNUNET_free (plugin); + GNUNET_free (api); + return NULL; +} + +/* end of plugin_transport_xu.c */ diff --git a/src/transport/transport.conf.in b/src/transport/transport.conf.in index a541080ce..1f56a2fa7 100644 --- a/src/transport/transport.conf.in +++ b/src/transport/transport.conf.in @@ -122,6 +122,11 @@ TESTING_IGNORE_KEYS = ACCEPT_FROM; # HOLE_EXTERNAL = AUTO:2086 +[transport-xu] +# Use PORT = 0 to autodetect a port available +PORT = 2087 + + [transport-http_client] MAX_CONNECTIONS = 128 TESTING_IGNORE_KEYS = ACCEPT_FROM; -- cgit v1.2.3 From f5a18b7466f342ac9624adcdb65f104aef8ecb5e Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 30 May 2018 18:46:55 +0200 Subject: disable xu transport, header missing --- src/transport/Makefile.am | 1 - 1 file changed, 1 deletion(-) (limited to 'src/transport') diff --git a/src/transport/Makefile.am b/src/transport/Makefile.am index b04b54e7f..286f03c4d 100644 --- a/src/transport/Makefile.am +++ b/src/transport/Makefile.am @@ -282,7 +282,6 @@ plugin_LTLIBRARIES = \ libgnunet_plugin_transport_tcp.la \ libgnunet_plugin_transport_xt.la \ libgnunet_plugin_transport_udp.la \ - libgnunet_plugin_transport_xu.la \ $(UNIX_PLUGIN_LA) \ $(HTTP_CLIENT_PLUGIN_LA) \ $(HTTPS_CLIENT_PLUGIN_LA) \ -- cgit v1.2.3 From 41cbe10b783a0741c75566232886f262cd779fbb Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 4 Jun 2018 19:00:10 +0200 Subject: add function for getopt uint16_t arguments --- src/gns/gnunet-bcd.c | 22 +-- src/gns/gnunet-gns-proxy.c | 16 +-- src/include/gnunet_getopt_lib.h | 17 +++ src/transport/Makefile.am | 7 +- src/transport/plugin_transport_xu.h | 273 ++++++++++++++++++++++++++++++++++++ src/util/getopt_helpers.c | 76 ++++++++++ 6 files changed, 391 insertions(+), 20 deletions(-) create mode 100644 src/transport/plugin_transport_xu.h (limited to 'src/transport') diff --git a/src/gns/gnunet-bcd.c b/src/gns/gnunet-bcd.c index 0746d5c57..d7fd1a812 100644 --- a/src/gns/gnunet-bcd.c +++ b/src/gns/gnunet-bcd.c @@ -76,7 +76,7 @@ static char *resfile; /** * Port number. */ -static unsigned int port = 8888; +static uint16_t port = 8888; struct Entry @@ -351,7 +351,7 @@ prepare_daemon (struct MHD_Daemon *daemon_handle) static int server_start () { - if ((0 == port) || (port > UINT16_MAX)) + if (0 == port) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Invalid port number %u. Exiting.\n"), @@ -362,7 +362,7 @@ server_start () _("Businesscard HTTP server starts on %u\n"), port); daemon_handle = MHD_start_daemon (MHD_USE_DUAL_STACK | MHD_USE_DEBUG, - (uint16_t) port, + port, NULL /* accept_policy_callback */, NULL, &access_handler_callback, NULL, MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 512, @@ -374,7 +374,7 @@ server_start () { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Could not start businesscard HTTP server on port %u\n"), - (unsigned short) port); + (unsigned int) port); return GNUNET_SYSERR; } http_task = prepare_daemon (daemon_handle); @@ -516,17 +516,17 @@ main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { - GNUNET_GETOPT_option_uint ('p', - "port", - "PORT", - gettext_noop ("Run HTTP serve on port PORT (default is 8888)"), - &port), - + GNUNET_GETOPT_option_uint16 ('p', + "port", + "PORT", + gettext_noop ("Run HTTP serve on port PORT (default is 8888)"), + &port), GNUNET_GETOPT_OPTION_END }; int ret; - if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv)) + if (GNUNET_OK != + GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv)) return 2; GNUNET_log_setup ("gnunet-bcd", "WARNING", NULL); ret = diff --git a/src/gns/gnunet-gns-proxy.c b/src/gns/gnunet-gns-proxy.c index 02ebcf0f1..0d7d83b4b 100644 --- a/src/gns/gnunet-gns-proxy.c +++ b/src/gns/gnunet-gns-proxy.c @@ -636,7 +636,7 @@ struct Socks5Request /** * The port the proxy is running on (default 7777) */ -static unsigned long long port = GNUNET_GNS_PROXY_PORT; +static uint16_t port = GNUNET_GNS_PROXY_PORT; /** * The CA file (pem) to use for the proxy CA @@ -3399,8 +3399,8 @@ run (void *cls, return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Proxy listens on port %llu\n", - port); + "Proxy listens on port %u\n", + (unsigned int) port); /* start MHD daemon for HTTP */ hd = GNUNET_new (struct MhdHttpList); @@ -3437,11 +3437,11 @@ int main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { - GNUNET_GETOPT_option_ulong ('p', - "port", - NULL, - gettext_noop ("listen on specified port (default: 7777)"), - &port), + GNUNET_GETOPT_option_uint16 ('p', + "port", + NULL, + gettext_noop ("listen on specified port (default: 7777)"), + &port), GNUNET_GETOPT_option_string ('a', "authority", NULL, diff --git a/src/include/gnunet_getopt_lib.h b/src/include/gnunet_getopt_lib.h index e38925f14..88c7825d0 100644 --- a/src/include/gnunet_getopt_lib.h +++ b/src/include/gnunet_getopt_lib.h @@ -286,6 +286,23 @@ GNUNET_GETOPT_option_uint (char shortName, unsigned int *val); +/** + * Allow user to specify an uint16_t. + * + * @param shortName short name of the option + * @param name long name of the option + * @param argumentHelp help text for the option argument + * @param description long help text for the option + * @param[out] val set to the value specified at the command line + */ +struct GNUNET_GETOPT_CommandLineOption +GNUNET_GETOPT_option_uint16 (char shortName, + const char *name, + const char *argumentHelp, + const char *description, + uint16_t *val); + + /** * Allow user to specify an `unsigned long long`. * diff --git a/src/transport/Makefile.am b/src/transport/Makefile.am index 286f03c4d..8697d0941 100644 --- a/src/transport/Makefile.am +++ b/src/transport/Makefile.am @@ -280,7 +280,6 @@ gnunet_service_transport_CFLAGS = \ plugin_LTLIBRARIES = \ libgnunet_plugin_transport_tcp.la \ - libgnunet_plugin_transport_xt.la \ libgnunet_plugin_transport_udp.la \ $(UNIX_PLUGIN_LA) \ $(HTTP_CLIENT_PLUGIN_LA) \ @@ -290,6 +289,12 @@ plugin_LTLIBRARIES = \ $(WLAN_PLUGIN_LA) \ $(BT_PLUGIN_LA) +if HAVE_EXPERIMENTAL +plugin_LTLIBRARIES += \ + libgnunet_plugin_transport_xt.la \ + libgnunet_plugin_transport_xu.la +endif + # Note: real plugins of course need to be added # to the plugin_LTLIBRARIES above noinst_LTLIBRARIES = \ diff --git a/src/transport/plugin_transport_xu.h b/src/transport/plugin_transport_xu.h new file mode 100644 index 000000000..1884f92e8 --- /dev/null +++ b/src/transport/plugin_transport_xu.h @@ -0,0 +1,273 @@ +/* + This file is part of GNUnet + Copyright (C) 2010-2014 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 transport/plugin_transport_xu.h + * @brief Implementation of the XU transport protocol + * @author Christian Grothoff + * @author Nathan Evans + * @author Matthias Wachs + */ +#ifndef PLUGIN_TRANSPORT_XU_H +#define PLUGIN_TRANSPORT_XU_H + +#include "platform.h" +#include "gnunet_hello_lib.h" +#include "gnunet_util_lib.h" +#include "gnunet_fragmentation_lib.h" +#include "gnunet_protocols.h" +#include "gnunet_resolver_service.h" +#include "gnunet_signatures.h" +#include "gnunet_constants.h" +#include "gnunet_statistics_service.h" +#include "gnunet_transport_service.h" +#include "gnunet_transport_plugin.h" +#include "transport.h" + +#define LOG(kind,...) GNUNET_log_from (kind, "transport-xu", __VA_ARGS__) + +#define PLUGIN_NAME "xu" + +#define DEBUG_XU GNUNET_NO + +#define DEBUG_XU_BROADCASTING GNUNET_NO + +/** + * MTU for fragmentation subsystem. Should be conservative since + * all communicating peers MUST work with this MTU. + */ +#define XU_MTU 1400 + + +GNUNET_NETWORK_STRUCT_BEGIN +/** + * Network format for IPv4 addresses. + */ +struct IPv4XuAddress +{ + /** + * Optional options and flags for this address + */ + uint32_t options GNUNET_PACKED; + + /** + * IPv4 address, in network byte order. + */ + uint32_t ipv4_addr GNUNET_PACKED; + + /** + * Port number, in network byte order. + */ + uint16_t u4_port GNUNET_PACKED; +}; + + +/** + * Network format for IPv6 addresses. + */ +struct IPv6XuAddress +{ + /** + * Optional options and flags for this address + */ + uint32_t options GNUNET_PACKED; + + /** + * IPv6 address. + */ + struct in6_addr ipv6_addr GNUNET_PACKED; + + /** + * Port number, in network byte order. + */ + uint16_t u6_port GNUNET_PACKED; +}; +GNUNET_NETWORK_STRUCT_END + +/** + * Either an IPv4 or IPv6 XU address. Note that without a "length", + * one cannot tell which one of the two types this address represents. + */ +union XuAddress +{ + /** + * IPv4 case. + */ + struct IPv4XuAddress v4; + + /** + * IPv6 case. + */ + struct IPv6XuAddress v6; +}; + + +/** + * Information we track for each message in the queue. + */ +struct XU_MessageWrapper; + + +/** + * Closure for #append_port(). + */ +struct PrettyPrinterContext; + + +/** + * Encapsulation of all of the state of the plugin. + */ +struct Plugin +{ + + /** + * Our environment. + */ + struct GNUNET_TRANSPORT_PluginEnvironment *env; + + /** + * Session of peers with whom we are currently connected, + * map of peer identity to `struct GNUNET_ATS_Session *`. + */ + struct GNUNET_CONTAINER_MultiPeerMap *sessions; + + /** + * ID of select task for IPv4 + */ + struct GNUNET_SCHEDULER_Task *select_task_v4; + + /** + * ID of select task for IPv6 + */ + struct GNUNET_SCHEDULER_Task *select_task_v6; + + /** + * Address we were told to bind to exclusively (IPv4). + */ + char *bind4_address; + + /** + * Address we were told to bind to exclusively (IPv6). + */ + char *bind6_address; + + /** + * Handle to NAT traversal support. + */ + struct GNUNET_NAT_Handle *nat; + + /** + * Handle to NAT traversal support. + */ + struct GNUNET_NAT_STUN_Handle *stun; + + /** + * The read socket for IPv4 + */ + struct GNUNET_NETWORK_Handle *sockv4; + + /** + * The read socket for IPv6 + */ + struct GNUNET_NETWORK_Handle *sockv6; + + /** + * Running pretty printers: head + */ + struct PrettyPrinterContext *ppc_dll_head; + + /** + * Running pretty printers: tail + */ + struct PrettyPrinterContext *ppc_dll_tail; + + /** + * Function to call about session status changes. + */ + GNUNET_TRANSPORT_SessionInfoCallback sic; + + /** + * Closure for @e sic. + */ + void *sic_cls; + + /** + * IPv6 multicast address + */ + struct sockaddr_in6 ipv6_multicast_address; + + /** + * Broadcast interval + */ + struct GNUNET_TIME_Relative broadcast_interval; + + /** + * Bytes currently in buffer + */ + int64_t bytes_in_buffer; + + /** + * Address options + */ + uint32_t myoptions; + + /** + * Is IPv6 enabled: #GNUNET_YES or #GNUNET_NO + */ + int enable_ipv6; + + /** + * Is IPv4 enabled: #GNUNET_YES or #GNUNET_NO + */ + int enable_ipv4; + + /** + * Port we listen on. + */ + uint16_t port; + + /** + * Port we advertise on. + */ + uint16_t aport; + +}; + + +/** + * Function called for a quick conversion of the binary address to + * a numeric address. Note that the caller must not free the + * address and that the next call to this function is allowed + * to override the address again. + * + * @param cls closure + * @param addr binary address (a `union XuAddress`) + * @param addrlen length of the @a addr + * @return string representing the same address + */ +const char * +xu_address_to_string (void *cls, + const void *addr, + size_t addrlen); + + +/*#ifndef PLUGIN_TRANSPORT_XU_H*/ +#endif +/* end of plugin_transport_xu.h */ diff --git a/src/util/getopt_helpers.c b/src/util/getopt_helpers.c index c836c9055..f9341f528 100644 --- a/src/util/getopt_helpers.c +++ b/src/util/getopt_helpers.c @@ -793,6 +793,82 @@ GNUNET_GETOPT_option_uint (char shortName, } + +/** + * Set an option of type 'uint16_t' from the command line. + * A pointer to this function should be passed as part of the + * 'struct GNUNET_GETOPT_CommandLineOption' array to initialize options + * of this type. It should be followed by a pointer to a value of + * type 'uint16_t'. + * + * @param ctx command line processing context + * @param scls additional closure (will point to the 'unsigned int') + * @param option name of the option + * @param value actual value of the option as a string. + * @return #GNUNET_OK if parsing the value worked + */ +static int +set_uint16 (struct GNUNET_GETOPT_CommandLineProcessorContext *ctx, + void *scls, + const char *option, + const char *value) +{ + uint16_t *val = scls; + unsigned int v; + + (void) ctx; + if (1 != SSCANF (value, + "%u", + v)) + { + FPRINTF (stderr, + _("You must pass a number to the `%s' option.\n"), + option); + return GNUNET_SYSERR; + } + if (v > UINT16_MAX) + { + FPRINTF (stderr, + _("You must pass a number below %u to the `%s' option.\n"), + (unsigned int) UINT16_MAX, + option); + return GNUNET_SYSERR; + } + *val = (uint16_t) v; + return GNUNET_OK; +} + + +/** + * Allow user to specify an uint16_t. + * + * @param shortName short name of the option + * @param name long name of the option + * @param argumentHelp help text for the option argument + * @param description long help text for the option + * @param[out] val set to the value specified at the command line + */ +struct GNUNET_GETOPT_CommandLineOption +GNUNET_GETOPT_option_uint16 (char shortName, + const char *name, + const char *argumentHelp, + const char *description, + uint16_t *val) +{ + struct GNUNET_GETOPT_CommandLineOption clo = { + .shortName = shortName, + .name = name, + .argumentHelp = argumentHelp, + .description = description, + .require_argument = 1, + .processor = &set_uint16, + .scls = (void *) val + }; + + return clo; +} + + /** * Closure for #set_base32(). */ -- cgit v1.2.3