xt transport for experiments:
authorChristian Grothoff <christian@grothoff.org>
Sat, 26 May 2018 14:06:07 +0000 (16:06 +0200)
committerChristian Grothoff <christian@grothoff.org>
Sat, 26 May 2018 14:06:07 +0000 (16:06 +0200)
src/transport/plugin_transport_xt.c [new file with mode: 0644]

diff --git a/src/transport/plugin_transport_xt.c b/src/transport/plugin_transport_xt.c
new file mode 100644 (file)
index 0000000..9ed0f43
--- /dev/null
@@ -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,
+                                               &notify_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 */