/*
This file is part of GNUnet
GNUnet is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation; either version 2, or (at your
option) any later version.
GNUnet is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNUnet; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
/**
* @file transports/tcp6.c
* @brief Implementation of the TCP6 transport service over IPv6
* @author Christian Grothoff
*/
#include "gnunet_util.h"
#include "gnunet_transport.h"
#include "platform.h"
#define DEBUG_TCP6 NO
/**
* after how much time of the core not being associated with a tcp6
* connection anymore do we close it?
*/
#define TCP6_TIMEOUT 30 * cronSECONDS
/**
* @brief Host-Address in a TCP6 network.
*/
typedef struct {
/**
* claimed IP of the sender, network byte order
*/
IP6addr ip;
/**
* claimed port of the sender, network byte order
*/
unsigned short port;
/**
* reserved (set to 0 for signature verification)
*/
unsigned short reserved;
} Host6Address;
/**
* @brief TCP6 Message-Packet header.
*/
typedef struct {
/**
* size of the message, in bytes, including this header;
* max 65536-header (network byte order)
*/
unsigned short size;
/**
* reserved, must be 0 (network byte order)
*/
unsigned short isEncrypted;
/**
* CRC checksum of the packet (network byte order)
*/
int checkSum;
/**
* This struct is followed by MESSAGE_PARTs - until size is reached
* There is no "end of message".
*/
p2p_HEADER parts[0];
} TCP6MessagePack;
/**
* Initial handshake message. Note that the beginning
* must match the CS_HEADER since we are using tcp6io.
*/
typedef struct {
/**
* size of the handshake message, in nbo, value is 24
*/
unsigned short size;
/**
* "message type", TCP6 version number, always 0.
*/
unsigned short version;
/**
* Identity of the node connecting (TCP6 client)
*/
HostIdentity clientIdentity;
} TCP6Welcome;
/**
* @brief TCP6 Transport Session handle.
*/
typedef struct {
/**
* the tcp6 socket
*/
int sock;
/**
* number of users of this session
*/
int users;
/**
* Last time this connection was used
*/
cron_t lastUse;
/**
* mutex for synchronized access to 'users'
*/
Mutex lock;
/**
* To whom are we talking to (set to our identity
* if we are still waiting for the welcome message)
*/
HostIdentity sender;
/**
* Are we still expecting the welcome? (YES/NO)
*/
int expectingWelcome;
/**
* Current read position in the buffer.
*/
unsigned int pos;
/**
* Current size of the buffer.
*/
unsigned int size;
/**
* The read buffer.
*/
char * rbuff;
/**
* Position in the write buffer
*/
unsigned int wpos;
/**
* The write buffer.
*/
char * wbuff;
} TCP6Session;
/* *********** globals ************* */
/**
* apis (our advertised API and the core api )
*/
static CoreAPIForTransport * coreAPI;
static TransportAPI tcp6API;
/**
* one thread for listening for new connections,
* and for reading on all open sockets
*/
static PTHREAD_T listenThread;
/**
* sock is the tcp6 socket that we listen on for new inbound
* connections.
*/
static int tcp6_sock;
/**
* tcp6_pipe is used to signal the thread that is
* blocked in a select call that the set of sockets to listen
* to has changed.
*/
static int tcp6_pipe[2];
/**
* Array of currently active TCP6 sessions.
*/
static TSession ** tsessions = NULL;
static int tsessionCount;
static int tsessionArrayLength;
/**
* handles for statistics
*/
static int stat_octets_total_tcp6_in;
static int stat_octets_total_tcp6_out;
/* configuration */
static CIDR6Network * filteredNetworks_;
/**
* Lock for access to mutable state of the module,
* that is the configuration and the tsessions array.
* Note that we ONLY need to synchronize access to
* the tsessions array when adding or removing sessions,
* since removing is done only by one thread and we just
* need to avoid another thread adding an element at the
* same point in time. We do not need to synchronize at
* every access point since adding new elements does not
* prevent the select thread from operating and removing
* is done by the only therad that reads from the array.
*/
static Mutex tcp6lock;
/**
* Semaphore used by the server-thread to signal that
* the server has been started -- and later again to
* signal that the server has been stopped.
*/
static Semaphore * serverSignal = NULL;
static int tcp6_shutdown = YES;
/* ******************** helper functions *********************** */
/**
* Check if we are allowed to connect to the given IP.
*/
static int isBlacklisted(IP6addr * ip) {
int ret;
MUTEX_LOCK(&tcp6lock);
ret = checkIP6Listed(filteredNetworks_,
ip);
MUTEX_UNLOCK(&tcp6lock);
return ret;
}
/**
* Write to the pipe to wake up the select thread (the set of
* files to watch has changed).
*/
static void signalSelect() {
char i = 0;
int ret;
LOG(LOG_DEBUG,
"Signaling select.\n");
ret = WRITE(tcp6_pipe[1],
&i,
sizeof(char));
if (ret != sizeof(char))
LOG_STRERROR(LOG_ERROR, "write");
}
/**
* Disconnect from a remote node. May only be called
* on sessions that were aquired by the caller first.
* For the core, aquiration means to call associate or
* connect. The number of disconnects must match the
* number of calls to connect+associate.
*
* @param tsession the session that is closed
* @return OK on success, SYSERR if the operation failed
*/
static int tcp6Disconnect(TSession * tsession) {
if (tsession->internal != NULL) {
TCP6Session * tcp6session = tsession->internal;
MUTEX_LOCK(&tcp6session->lock);
tcp6session->users--;
if (tcp6session->users > 0) {
MUTEX_UNLOCK(&tcp6session->lock);
return OK;
}
MUTEX_UNLOCK(&tcp6session->lock);
MUTEX_DESTROY(&tcp6session->lock);
FREE(tcp6session->rbuff);
FREENONNULL(tcp6session->wbuff);
FREE(tcp6session);
FREE(tsession);
}
return OK;
}
/**
* Remove a session, either the other side closed the connection
* or we have otherwise reason to believe that it should better
* be killed. Destroy session closes the session as far as the
* TCP6 layer is concerned, but since the core may still have
* references to it, tcp6Disconnect may not instantly free all
* the associated resources.
*
* destroySession may only be called if the tcp6lock is already
* held.
*
* @param i index to the session handle
*/
static void destroySession(int i) {
TCP6Session * tcp6Session;
tcp6Session = tsessions[i]->internal;
if (-1 != tcp6Session->sock)
if (0 != SHUTDOWN(tcp6Session->sock, SHUT_RDWR))
LOG(LOG_EVERYTHING,
"Error shutting down socket %d: %s\n",
tcp6Session->sock,
STRERROR(errno));
CLOSE(tcp6Session->sock);
tcp6Session->sock = -1;
tcp6Disconnect(tsessions[i]);
tsessions[i] = tsessions[--tsessionCount];
tsessions[tsessionCount] = NULL;
}
/**
* Get the GNUnet UDP port from the configuration,
* or from /etc/services if it is not specified in
* the config file.
*/
static unsigned short getGNUnetTCP6Port() {
struct servent * pse; /* pointer to service information entry */
unsigned short port;
port = (unsigned short) getConfigurationInt("TCP6",
"PORT");
if (port == 0) { /* try lookup in services */
if ((pse = getservbyname("gnunet", "tcp6")))
port = htons(pse->s_port);
}
return port;
}
/**
* A (core) Session is to be associated with a transport session. The
* transport service may want to know in order to call back on the
* core if the connection is being closed. Associate can also be
* called to test if it would be possible to associate the session
* later, in this case the argument session is NULL. This can be used
* to test if the connection must be closed by the core or if the core
* can assume that it is going to be self-managed (if associate
* returns OK and session was NULL, the transport layer is responsible
* for eventually freeing resources associated with the tesession). If
* session is not NULL, the core takes responsbility for eventually
* calling disconnect.
*
* @param tsession the session handle passed along
* from the call to receive that was made by the transport
* layer
* @return OK if the session could be associated,
* SYSERR if not.
*/
static int tcp6Associate(TSession * tsession) {
TCP6Session * tcp6Session;
GNUNET_ASSERT(tsession != NULL);
tcp6Session = (TCP6Session*) tsession->internal;
MUTEX_LOCK(&tcp6Session->lock);
tcp6Session->users++;
MUTEX_UNLOCK(&tcp6Session->lock);
return OK;
}
/**
* The socket of session i has data waiting, process!
*
* This function may only be called if the tcp6lock is
* already held by the caller.
*/
static int readAndProcess(int i) {
TSession * tsession;
TCP6Session * tcp6Session;
unsigned int len;
int ret, success;
TCP6MessagePack * pack;
MessagePack * mp;
tsession = tsessions[i];
if (SYSERR == tcp6Associate(tsession))
return SYSERR;
tcp6Session = tsession->internal;
try_again_2:
success = RECV_NONBLOCKING(tcp6Session->sock,
&tcp6Session->rbuff[tcp6Session->pos],
tcp6Session->size - tcp6Session->pos,
&ret);
cronTime(&tcp6Session->lastUse);
if (ret == 0) {
tcp6Disconnect(tsession);
#if DEBUG_TCP6
LOG(LOG_DEBUG,
"READ on socket %d returned 0 bytes, closing connection\n",
tcp6Session->sock);
#endif
return SYSERR; /* other side closed connection */
}
if (success == NO) {
gnunet_util_sleep(20);
goto try_again_2;
}
if (ret < 0) {
if ( (errno == EINTR) ||
(errno == EAGAIN) ) {
#if DEBUG_TCP6
LOG(LOG_DEBUG,
"READ on socket %d returned %s, closing connection\n",
tcp6Session->sock,
STRERROR(errno));
#endif
tcp6Disconnect(tsession);
return OK;
}
#if DEBUG_TCP6
LOG(LOG_INFO,
"Read failed on peer tcp6 connection (%d), closing (%s).\n",
ret,
STRERROR(errno));
#endif
tcp6Disconnect(tsession);
return SYSERR;
}
incrementBytesReceived(ret);
statChange(stat_octets_total_tcp6_in,
ret);
tcp6Session->pos += ret;
len = ntohs(((TCP6MessagePack*)&tcp6Session->rbuff[0])->size);
if (len > tcp6Session->size) /* if MTU larger than expected, grow! */
GROW(tcp6Session->rbuff,
tcp6Session->size,
len);
#if DEBUG_TCP6
LOG(LOG_DEBUG,
"Read %d bytes on socket %d, expecting %d for full message\n",
tcp6Session->pos,
tcp6Session->sock,
len);
#endif
if ( (tcp6Session->pos < 2) ||
(tcp6Session->pos < len) ) {
tcp6Disconnect(tsession);
return OK;
}
/* complete message received, let's check what it is */
if (YES == tcp6Session->expectingWelcome) {
TCP6Welcome * welcome;
#if DEBUG_TCP6
EncName hex;
#endif
welcome = (TCP6Welcome*) &tcp6Session->rbuff[0];
if ( (ntohs(welcome->version) != 0) ||
(ntohs(welcome->size) != sizeof(TCP6Welcome)) ) {
LOG(LOG_WARNING,
_("Expected welcome message on tcp connection, got garbage. Closing.\n"));
tcp6Disconnect(tsession);
return SYSERR;
}
tcp6Session->expectingWelcome = NO;
memcpy(&tcp6Session->sender,
&welcome->clientIdentity,
sizeof(HostIdentity));
#if DEBUG_TCP6
IFLOG(LOG_DEBUG,
hash2enc(&tcp6Session->sender.hashPubKey,
&enc));
LOG(LOG_DEBUG,
"tcp6 welcome message from %s received\n",
&enc);
#endif
memmove(&tcp6Session->rbuff[0],
&tcp6Session->rbuff[sizeof(TCP6Welcome)],
tcp6Session->pos - sizeof(TCP6Welcome));
tcp6Session->pos -= sizeof(TCP6Welcome);
len = ntohs(((TCP6MessagePack*)&tcp6Session->rbuff[0])->size);
}
if ( (tcp6Session->pos < 2) ||
(tcp6Session->pos < len) ) {
tcp6Disconnect(tsession);
return OK;
}
pack = (TCP6MessagePack*)&tcp6Session->rbuff[0];
/* send msg to core! */
if (len <= sizeof(TCP6MessagePack)) {
LOG(LOG_WARNING,
_("Received malformed message from tcp6-peer connection. Closing connection.\n"));
tcp6Disconnect(tsession);
return SYSERR;
}
mp = MALLOC(sizeof(MessagePack));
mp->msg = MALLOC(len);
memcpy(mp->msg,
&pack->parts[0],
len - sizeof(TCP6MessagePack));
memcpy(&mp->sender,
&tcp6Session->sender,
sizeof(HostIdentity));
mp->crc = ntohl(pack->checkSum);
mp->isEncrypted = ntohs(pack->isEncrypted);
mp->size = len - sizeof(TCP6MessagePack);
mp->tsession = tsession;
#if DEBUG_TCP6
LOG(LOG_DEBUG,
"tcp6 transport received %d bytes, forwarding to core\n",
mp->size);
#endif
coreAPI->receive(mp);
if (tcp6Session->pos < len) {
BREAK();
tcp6Disconnect(tsession);
return SYSERR;
}
/* finally, shrink buffer adequately */
memmove(&tcp6Session->rbuff[0],
&tcp6Session->rbuff[len],
tcp6Session->pos - len);
tcp6Session->pos -= len;
tcp6Disconnect(tsession);
return OK;
}
/**
* Add a new session to the array watched by the select thread. Grows
* the array if needed. If the caller wants to do anything useful
* with the return value, it must have the lock on tcp6lock before
* calling. It is ok to call this function without holding tcp6lock if
* the return value is ignored.
*/
static int addTSession(TSession * tsession) {
int i;
MUTEX_LOCK(&tcp6lock);
if (tsessionCount == tsessionArrayLength)
GROW(tsessions,
tsessionArrayLength,
tsessionArrayLength * 2);
i = tsessionCount;
tsessions[tsessionCount++] = tsession;
MUTEX_UNLOCK(&tcp6lock);
return i;
}
/**
* Create a new session for an inbound connection on the given
* socket. Adds the session to the array of sessions watched
* by the select thread.
*/
static void createNewSession(int sock) {
TSession * tsession;
TCP6Session * tcp6Session;
tcp6Session = MALLOC(sizeof(TCP6Session));
tcp6Session->pos = 0;
tcp6Session->size = tcp6API.mtu + sizeof(TCP6MessagePack);
tcp6Session->rbuff = MALLOC(tcp6Session->size);
tcp6Session->wpos = 0;
tcp6Session->wbuff = NULL;
tcp6Session->sock = sock;
/* fill in placeholder identity to mark that we
are waiting for the welcome message */
memcpy(&tcp6Session->sender,
coreAPI->myIdentity,
sizeof(HostIdentity));
tcp6Session->expectingWelcome = YES;
MUTEX_CREATE_RECURSIVE(&tcp6Session->lock);
tcp6Session->users = 1; /* us only, core has not seen this tsession! */
cronTime(&tcp6Session->lastUse);
tsession = MALLOC(sizeof(TSession));
tsession->ttype = TCP6_PROTOCOL_NUMBER;
tsession->internal = tcp6Session;
addTSession(tsession);
}
/**
* Main method for the thread listening on the tcp6 socket and all tcp6
* connections. Whenever a message is received, it is forwarded to the
* core. This thread waits for activity on any of the TCP6 connections
* and processes deferred (async) writes and buffers reads until an
* entire message has been received.
*/
static void * tcp6ListenMain() {
struct sockaddr_in6 clientAddr;
fd_set readSet;
fd_set errorSet;
fd_set writeSet;
struct stat buf;
int lenOfIncomingAddr;
int i;
int max;
int ret;
if (tcp6_sock != -1)
if (0 != LISTEN(tcp6_sock, 5))
LOG_STRERROR(LOG_ERROR, "listen");
SEMAPHORE_UP(serverSignal); /* we are there! */
MUTEX_LOCK(&tcp6lock);
while (tcp6_shutdown == NO) {
FD_ZERO(&readSet);
FD_ZERO(&errorSet);
FD_ZERO(&writeSet);
if (tcp6_sock != -1) {
if (isSocketValid(tcp6_sock)) {
FD_SET(tcp6_sock, &readSet);
FD_SET(tcp6_sock, &writeSet);
FD_SET(tcp6_sock, &errorSet);
} else {
LOG_STRERROR(LOG_ERROR, "isSocketValid");
tcp6_sock = -1; /* prevent us from error'ing all the time */
}
} else
LOG(LOG_DEBUG,
"TCP6 server socket not open!\n");
if (tcp6_pipe[0] != -1) {
if (-1 != FSTAT(tcp6_pipe[0], &buf)) {
FD_SET(tcp6_pipe[0], &readSet);
} else {
LOG_STRERROR(LOG_ERROR, "fstat");
tcp6_pipe[0] = -1; /* prevent us from error'ing all the time */
}
}
max = tcp6_pipe[0];
if (tcp6_sock > tcp6_pipe[0])
max = tcp6_sock;
for (i=0;iinternal;
int sock = tcp6Session->sock;
if (sock != -1) {
if (isSocketValid(sock)) {
FD_SET(sock, &readSet);
FD_SET(sock, &errorSet);
if (tcp6Session->wpos > 0)
FD_SET(sock, &writeSet); /* do we have a pending write request? */
} else {
LOG_STRERROR(LOG_ERROR, "isSocketValid");
destroySession(i);
}
} else {
BREAK();
destroySession(i);
}
if (sock > max)
max = sock;
}
LOG(LOG_DEBUG,
"Blocking on select!\n");
MUTEX_UNLOCK(&tcp6lock);
ret = SELECT(max+1, &readSet, &writeSet, &errorSet, NULL);
MUTEX_LOCK(&tcp6lock);
LOG(LOG_DEBUG,
"Select returned!\n");
if ( (ret == -1) &&
( (errno == EAGAIN) || (errno == EINTR) ) )
continue;
if (ret == -1) {
if (errno == EBADF) {
LOG_STRERROR(LOG_ERROR, "select");
} else {
DIE_STRERROR("select");
}
}
if (tcp6_sock != -1) {
if (FD_ISSET(tcp6_sock, &readSet)) {
int sock;
LOG(LOG_DEBUG,
"accepting inbound connection\n");
lenOfIncomingAddr = sizeof(clientAddr);
sock = ACCEPT(tcp6_sock,
(struct sockaddr *)&clientAddr,
&lenOfIncomingAddr);
if (sock != -1) {
/* verify clientAddr for eligibility here (ipcheck-style,
user should be able to specify who is allowed to connect,
otherwise we just close and reject the communication! */
GNUNET_ASSERT(sizeof(struct in6_addr) == sizeof(IP6addr));
if (YES == isBlacklisted((IP6addr*)&clientAddr.sin6_addr)) {
char * tmp = MALLOC(INET6_ADDRSTRLEN);
LOG(LOG_INFO,
_("Rejected blacklisted connection from address %s.\n"),
inet_ntop(AF_INET6,
&clientAddr,
tmp,
INET6_ADDRSTRLEN));
FREE(tmp);
SHUTDOWN(sock, 2);
CLOSE(sock);
} else
createNewSession(sock);
} else {
LOG_STRERROR(LOG_INFO, "accept");
}
}
}
if (FD_ISSET(tcp6_pipe[0], &readSet)) {
/* allow reading multiple signals in one go in case we get many
in one shot... */
#define MAXSIG_BUF 128
char buf[MAXSIG_BUF];
/* just a signal to refresh sets, eat and continue */
if (0 >= READ(tcp6_pipe[0],
&buf[0],
MAXSIG_BUF)) {
LOG_STRERROR(LOG_WARNING, "read");
}
}
for (i=0;iinternal;
int sock = tcp6Session->sock;
if (FD_ISSET(sock, &readSet)) {
if (SYSERR == readAndProcess(i)) {
destroySession(i);
i--;
continue;
}
}
if (FD_ISSET(sock, &writeSet)) {
int ret, success;
try_again_1:
success = SEND_NONBLOCKING(sock,
tcp6Session->wbuff,
tcp6Session->wpos,
&ret);
if (success == SYSERR) {
LOG_STRERROR(LOG_WARNING, "send");
destroySession(i);
i--;
continue;
} else if (success == NO) {
/* this should only happen under Win9x because
of a bug in the socket implementation (KB177346).
Let's sleep and try again. */
gnunet_util_sleep(20);
goto try_again_1;
}
if (ret == 0) {
/* send only returns 0 on error (other side closed connection),
* so close the session */
destroySession(i);
i--;
continue;
}
if ((unsigned int)ret == tcp6Session->wpos) {
FREENONNULL(tcp6Session->wbuff);
tcp6Session->wbuff = NULL;
tcp6Session->wpos = 0;
} else {
memmove(tcp6Session->wbuff,
&tcp6Session->wbuff[ret],
tcp6Session->wpos - ret);
tcp6Session->wpos -= ret;
}
}
if (FD_ISSET(sock, &errorSet)) {
destroySession(i);
i--;
continue;
}
if ( ( tcp6Session->users == 1) &&
(cronTime(NULL) > tcp6Session->lastUse + TCP6_TIMEOUT) ) {
destroySession(i);
i--;
continue;
}
}
}
/* shutdown... */
if (tcp6_sock != -1) {
CLOSE(tcp6_sock);
tcp6_sock = -1;
}
/* close all sessions */
while (tsessionCount > 0)
destroySession(0);
MUTEX_UNLOCK(&tcp6lock);
SEMAPHORE_UP(serverSignal); /* we are there! */
return NULL;
} /* end of tcp6 listen main */
/**
* Send a message (already encapsulated if needed) via the
* tcp6 socket (or enqueue if sending now would block).
*
* @param tcp6Session the session to use for sending
* @param mp the message to send
* @param ssize the size of the message
* @return OK if message send or queued, SYSERR if queue is full and
* message was dropped.
*/
static int tcp6DirectSend(TCP6Session * tcp6Session,
void * mp,
unsigned int ssize) {
int ok;
int ret, success;
if (tcp6Session->sock == -1) {
#if DEBUG_TCP6
LOG(LOG_INFO,
"tcp6DirectSend called, but socket is closed\n");
#endif
return SYSERR;
}
if (ssize == 0) {
BREAK();
return SYSERR;
}
if (ssize > tcp6API.mtu + sizeof(TCP6MessagePack)) {
BREAK();
return SYSERR;
}
ok = SYSERR;
MUTEX_LOCK(&tcp6lock);
if (tcp6Session->wpos > 0) {
ret = 0;
} else {
sucess = SEND_NONBLOCKING(tcp6Session->sock,
mp,
ssize,
&ret);
}
if (success == SYSERR) {
LOG_STRERROR(LOG_INFO, "send");
MUTEX_UNLOCK(&tcp6lock);
return SYSERR;
} else if (success == NO)
ret = 0;
if ((unsigned int) ret <= ssize) { /* some bytes send or blocked */
if ((unsigned int)ret < ssize) {
if (tcp6Session->wbuff == NULL) {
tcp6Session->wbuff = MALLOC(tcp6API.mtu + sizeof(TCP6MessagePack));
tcp6Session->wpos = 0;
}
if ((unsigned int) (ssize - ret) >
tcp6API.mtu + sizeof(TCP6MessagePack) - tcp6Session->wpos) {
ssize = 0;
ok = SYSERR; /* buffer full, drop */
} else {
memcpy(&tcp6Session->wbuff[tcp6Session->wpos],
mp,
ssize - ret);
tcp6Session->wpos += ssize - ret;
if (tcp6Session->wpos == ssize - ret)
signalSelect(); /* select set changed! */
ok = OK; /* all buffered */
}
} else
ok = OK; /* all written */
} else {
LOG_STRERROR(LOG_WARNING, "send");
ssize = 0;
ok = SYSERR; /* write failed for real */
}
MUTEX_UNLOCK(&tcp6lock);
cronTime(&tcp6Session->lastUse);
incrementBytesSent(ssize);
statChange(stat_octets_total_tcp6_out,
ssize);
return ok;
}
/**
* Send a message (already encapsulated if needed) via the
* tcp6 socket. Block if required.
*
* @param tcp6Session the session to use for sending
* @param mp the message to send
* @param ssize the size of the message
* @return OK if message send or queued, SYSERR if queue is full and
* message was dropped.
*/
static int tcp6DirectSendReliable(TCP6Session * tcp6Session,
void * mp,
unsigned int ssize) {
int ok;
if (tcp6Session->sock == -1) {
#if DEBUG_TCP6
LOG(LOG_INFO,
"tcp6DirectSendReliable called, but socket is closed\n");
#endif
return SYSERR;
}
if (ssize == 0) {
BREAK();
return SYSERR;
}
if (ssize > tcp6API.mtu + sizeof(TCP6MessagePack)) {
BREAK();
return SYSERR;
}
MUTEX_LOCK(&tcp6lock);
if (tcp6Session->wpos > 0) {
unsigned int old = tcp6Session->wpos;
/* reliable: grow send-buffer above limit! */
GROW(tcp6Session->wbuff,
tcp6Session->wpos,
tcp6Session->wpos + ssize);
memcpy(&tcp6Session->wbuff[old],
mp,
ssize);
ok = OK;
} else {
ok = tcp6DirectSend(tcp6Session,
mp,
ssize);
}
MUTEX_UNLOCK(&tcp6lock);
return ok;
}
/**
* Send a message to the specified remote node.
*
* @param tsession the HELO_Message identifying the remote node
* @param msg the message
* @param size the size of the message
* @param isEncrypted is the message encrypted (YES/NO)
* @param crc CRC32 of the plaintext
* @return SYSERR on error, OK on success
*/
static int tcp6SendReliable(TSession * tsession,
const void * msg,
const unsigned int size,
int isEncrypted,
const int crc) {
TCP6MessagePack * mp;
int ok;
int ssize;
if (tcp6_shutdown == YES)
return SYSERR;
if (size == 0) {
BREAK();
return SYSERR;
}
if (size > tcp6API.mtu) {
BREAK();
return SYSERR;
}
if (((TCP6Session*)tsession->internal)->sock == -1)
return SYSERR; /* other side closed connection */
mp = MALLOC(sizeof(TCP6MessagePack) + size);
mp->checkSum = htonl(crc);
mp->isEncrypted = htons(isEncrypted);
memcpy(&mp->parts[0],
msg,
size);
ssize = size + sizeof(TCP6MessagePack);
mp->size = htons(ssize);
ok = tcp6DirectSendReliable(tsession->internal,
mp,
ssize);
FREE(mp);
return ok;
}
/**
* Verify that a HELO-Message is correct (a node
* is reachable at that address). Since the reply
* will be asynchronous, a method must be called on
* success.
* @param helo the HELO message to verify
* (the signature/crc have been verified before)
* @return OK on success, SYSERR on error
*/
static int verifyHelo(const HELO_Message * helo) {
Host6Address * haddr;
haddr = (Host6Address*) &((HELO_Message_GENERIC*)helo)->senderAddress[0];
if ( (ntohs(helo->senderAddressSize) != sizeof(Host6Address)) ||
(ntohs(helo->header.size) != HELO_Message_size(helo)) ||
(ntohs(helo->header.requestType) != p2p_PROTO_HELO) ||
(ntohs(helo->protocol) != TCP6_PROTOCOL_NUMBER) ||
(YES == isBlacklisted(&haddr->ip)) )
return SYSERR; /* obviously invalid */
else
return OK;
}
/**
* Create a HELO-Message for the current node. The HELO is
* created without signature and without a timestamp. The
* GNUnet core will sign the message and add an expiration time.
*
* @param helo address where to store the pointer to the HELO
* message
* @return OK on success, SYSERR on error
*/
static int createHELO(HELO_Message ** helo) {
HELO_Message * msg;
Host6Address * haddr;
unsigned short port;
port = getGNUnetTCP6Port();
if (0 == port) {
LOG(LOG_DEBUG,
"TCP6 port is 0, will only send using TCP6\n");
return SYSERR; /* TCP6 transport is configured SEND-only! */
}
msg = (HELO_Message *) MALLOC(sizeof(HELO_Message) + sizeof(Host6Address));
haddr = (Host6Address*) &((HELO_Message_GENERIC*)msg)->senderAddress[0];
if (SYSERR == getPublicIP6Address(&haddr->ip)) {
FREE(msg);
LOG(LOG_WARNING,
_("Could not determine my public IPv6 address.\n"));
return SYSERR;
}
haddr->port = htons(port);
haddr->reserved = htons(0);
msg->senderAddressSize = htons(sizeof(Host6Address));
msg->protocol = htons(TCP6_PROTOCOL_NUMBER);
msg->MTU = htonl(tcp6API.mtu);
*helo = msg;
return OK;
}
/**
* Establish a connection to a remote node.
*
* @param helo the HELO-Message for the target node
* @param tsessionPtr the session handle that is set
* @return OK on success, SYSERR if the operation failed
*/
static int tcp6Connect(HELO_Message * helo,
TSession ** tsessionPtr) {
int i;
Host6Address * haddr;
TCP6Welcome welcome;
int sock;
TSession * tsession;
TCP6Session * tcp6Session;
char * hostname;
struct addrinfo hints, *res, *res0;
int rtn;
#if DEBUG_TCP6
char * tmp;
#endif
if (tcp6_shutdown == YES)
return SYSERR;
haddr = (Host6Address*) &((HELO_Message_GENERIC*)helo)->senderAddress[0];
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_INET6;
hints.ai_socktype = SOCK_STREAM;
hostname = MALLOC(INET6_ADDRSTRLEN);
inet_ntop(AF_INET6,
haddr,
hostname,
INET6_ADDRSTRLEN);
rtn = getaddrinfo(hostname, NULL, &hints, &res0);
FREE(hostname);
if (rtn != 0) {
LOG(LOG_WARNING,
_("'%s': unknown service: %s\n"),
__FUNCTION__,
gai_strerror(rtn));
return SYSERR;
}
#if DEBUG_TCP6
tmp = MALLOC(INET6_ADDRSTRLEN);
LOG(LOG_DEBUG,
"creating TCP6 connection to %s:%d\n",
inet_ntop(AF_INET6,
haddr,
tmp,
INET6_ADDRSTRLEN),
ntohs(haddr->port));
FREE(tmp);
#endif
sock = -1;
for (res=res0; res; res=res->ai_next) {
if (res->ai_family != PF_INET6)
continue;
sock = SOCKET(res->ai_family,
res->ai_socktype,
res->ai_protocol);
if (sock < 0)
continue;
if (0 != setBlocking(sock, NO)) {
CLOSE(sock);
LOG_STRERROR(LOG_FAILURE, "setBlocking");
return SYSERR;
}
((struct sockaddr_in6*)(res->ai_addr))->sin6_port
= haddr->port;
if ( (CONNECT(sock,
res->ai_addr,
res->ai_addrlen) < 0) &&
(errno != EINPROGRESS) ) {
LOG_STRERROR(LOG_WARNING, "connect");
CLOSE(sock);
sock = -1;
continue;
}
break;
}
freeaddrinfo(res0);
if (sock == -1) {
LOG_STRERROR(LOG_FAILURE, "socket");
return SYSERR;
}
if (0 != setBlocking(sock, NO)) {
LOG_STRERROR(LOG_FAILURE, "setBlocking");
CLOSE(sock);
return SYSERR;
}
tcp6Session = MALLOC(sizeof(TCP6Session));
tcp6Session->sock = sock;
tcp6Session->wpos = 0;
tcp6Session->wbuff = NULL;
tcp6Session->size = tcp6API.mtu + sizeof(TCP6MessagePack);
tcp6Session->rbuff = MALLOC(tcp6Session->size);
tsession = MALLOC(sizeof(TSession));
tsession->internal = tcp6Session;
tsession->ttype = tcp6API.protocolNumber;
MUTEX_CREATE_RECURSIVE(&tcp6Session->lock);
tcp6Session->users = 2; /* caller + us */
tcp6Session->pos = 0;
cronTime(&tcp6Session->lastUse);
memcpy(&tcp6Session->sender,
&helo->senderIdentity,
sizeof(HostIdentity));
tcp6Session->expectingWelcome = NO;
MUTEX_LOCK(&tcp6lock);
i = addTSession(tsession);
/* send our node identity to the other side to fully establish the
connection! */
welcome.size = htons(sizeof(TCP6Welcome));
welcome.version = htons(0);
memcpy(&welcome.clientIdentity,
coreAPI->myIdentity,
sizeof(HostIdentity));
if (SYSERR == tcp6DirectSend(tcp6Session,
&welcome,
sizeof(TCP6Welcome))) {
destroySession(i);
tcp6Disconnect(tsession);
MUTEX_UNLOCK(&tcp6lock);
return SYSERR;
}
MUTEX_UNLOCK(&tcp6lock);
signalSelect();
*tsessionPtr = tsession;
FREE(helo);
return OK;
}
/**
* Send a message to the specified remote node.
*
* @param tsession the HELO_Message identifying the remote node
* @param msg the message
* @param size the size of the message
* @param isEncrypted is the message encrypted (YES/NO)
* @param crc CRC32 of the plaintext
* @return SYSERR on error, OK on success
*/
static int tcp6Send(TSession * tsession,
const void * msg,
const unsigned int size,
int isEncrypted,
const int crc) {
TCP6MessagePack * mp;
int ok;
int ssize;
if (tcp6_shutdown == YES)
return SYSERR;
if (size == 0) {
BREAK();
return SYSERR;
}
if (size > tcp6API.mtu) {
BREAK();
return SYSERR;
}
if (((TCP6Session*)tsession->internal)->sock == -1)
return SYSERR; /* other side closed connection */
mp = MALLOC(sizeof(TCP6MessagePack) + size);
mp->checkSum = htonl(crc);
mp->isEncrypted = htons(isEncrypted);
memcpy(&mp->parts[0],
msg,
size);
ssize = size + sizeof(TCP6MessagePack);
mp->size = htons(ssize);
ok = tcp6DirectSend(tsession->internal,
mp,
ssize);
FREE(mp);
return ok;
}
/**
* Start the server process to receive inbound traffic.
* @return OK on success, SYSERR if the operation failed
*/
static int startTransportServer(void) {
struct sockaddr_in6 serverAddr;
const int on = 1;
unsigned short port;
int flags;
GNUNET_ASSERT(serverSignal == NULL);
serverSignal = SEMAPHORE_NEW(0);
tcp6_shutdown = NO;
if (0 != PIPE(tcp6_pipe)) {
LOG_STRERROR(LOG_ERROR, "pipe");
return SYSERR;
}
flags = fcntl(tcp6_pipe[1], F_GETFL, 0);
fcntl(tcp6_pipe[1], F_SETFL, flags | O_NONBLOCK);
port = getGNUnetTCP6Port();
if (port != 0) { /* if port == 0, this is a read-only
business! */
tcp6_sock = SOCKET(PF_INET6,
SOCK_STREAM,
0);
if (tcp6_sock < 0)
DIE_STRERROR("socket");
if ( SETSOCKOPT(tcp6_sock,
SOL_SOCKET,
SO_REUSEADDR,
&on,
sizeof(on)) < 0 )
DIE_STRERROR("setsockopt");
memset((char *) &serverAddr,
0,
sizeof(serverAddr));
serverAddr.sin6_family = AF_INET6;
serverAddr.sin6_flowinfo = 0;
serverAddr.sin6_addr = in6addr_any;
serverAddr.sin6_port = htons(getGNUnetTCP6Port());
#if DEBUG_TCP6
LOG(LOG_INFO,
"starting tcp6 peer server on port %d\n",
ntohs(serverAddr.sin6_port));
#endif
if (BIND(tcp6_sock,
(struct sockaddr *) &serverAddr,
sizeof(serverAddr)) < 0) {
LOG_STRERROR(LOG_ERROR, "bind");
LOG(LOG_ERROR,
_("Failed to start transport service on port %d.\n"),
getGNUnetTCPPort());
CLOSE(tcp6_sock);
tcp6_sock = -1;
SEMAPHORE_FREE(serverSignal);
serverSignal = NULL;
return SYSERR;
}
} else
tcp6_sock = -1;
if (0 == PTHREAD_CREATE(&listenThread,
(PThreadMain) &tcp6ListenMain,
NULL,
2048)) {
SEMAPHORE_DOWN(serverSignal); /* wait for server to be up */
} else {
LOG_STRERROR(LOG_FAILURE, "pthread_create");
CLOSE(tcp6_sock);
SEMAPHORE_FREE(serverSignal);
serverSignal = NULL;
return SYSERR;
}
return OK;
}
/**
* Shutdown the server process (stop receiving inbound
* traffic). Maybe restarted later!
*/
static int stopTransportServer() {
void * unused;
int haveThread;
tcp6_shutdown = YES;
signalSelect();
if (serverSignal != NULL) {
haveThread = YES;
SEMAPHORE_DOWN(serverSignal);
SEMAPHORE_FREE(serverSignal);
} else
haveThread = NO;
serverSignal = NULL;
CLOSE(tcp6_pipe[1]);
CLOSE(tcp6_pipe[0]);
if (tcp6_sock != -1) {
CLOSE(tcp6_sock);
tcp6_sock = -1;
}
if (haveThread == YES)
PTHREAD_JOIN(&listenThread, &unused);
return OK;
}
/**
* Reload the configuration. Should never fail (keep old
* configuration on error, syslog errors!)
*/
static void reloadConfiguration(void) {
char * ch;
MUTEX_LOCK(&tcp6lock);
FREENONNULL(filteredNetworks_);
ch = getConfigurationString("TCP6",
"BLACKLIST");
if (ch == NULL)
filteredNetworks_ = parseRoutes6("");
else {
filteredNetworks_ = parseRoutes6(ch);
FREE(ch);
}
MUTEX_UNLOCK(&tcp6lock);
}
/**
* Convert TCP6 address to a string.
*/
static char * addressToString(const HELO_Message * helo) {
char * ret;
char * tmp;
Host6Address * haddr;
haddr = (Host6Address*) &((HELO_Message_GENERIC*)helo)->senderAddress[0];
ret = MALLOC(INET6_ADDRSTRLEN+16);
tmp = MALLOC(INET6_ADDRSTRLEN);
SNPRINTF(ret,
INET6_ADDRSTRLEN+16,
"%s:%d (TCP6)",
inet_ntop(AF_INET6,
haddr,
tmp,
INET6_ADDRSTRLEN),
ntohs(haddr->port));
FREE(tmp);
return ret;
}
/* ******************** public API ******************** */
/**
* The exported method. Makes the core api available
* via a global and returns the udp transport API.
*/
TransportAPI * inittransport_tcp6(CoreAPIForTransport * core) {
int mtu;
MUTEX_CREATE_RECURSIVE(&tcp6lock);
reloadConfiguration();
tsessionCount = 0;
tsessionArrayLength = 32;
tsessions = MALLOC(sizeof(TSession*) * tsessionArrayLength);
coreAPI = core;
stat_octets_total_tcp6_in
= statHandle(_("# bytes received via tcp6"));
stat_octets_total_tcp6_out
= statHandle(_("# bytes sent via tcp6"));
mtu = getConfigurationInt("TCP6",
"MTU");
if (mtu == 0)
mtu = 1440;
if (mtu < 1200)
LOG(LOG_ERROR,
_("MTU for '%s' is probably too low (fragmentation not implemented!)\n"),
"TCP6");
tcp6API.protocolNumber = TCP6_PROTOCOL_NUMBER;
tcp6API.mtu = mtu - sizeof(TCP6MessagePack);
tcp6API.cost = 19950; /* about equal to udp6 */
tcp6API.verifyHelo = &verifyHelo;
tcp6API.createHELO = &createHELO;
tcp6API.connect = &tcp6Connect;
tcp6API.associate = &tcp6Associate;
tcp6API.send = &tcp6Send;
tcp6API.sendReliable = &tcp6SendReliable;
tcp6API.disconnect = &tcp6Disconnect;
tcp6API.startTransportServer = &startTransportServer;
tcp6API.stopTransportServer = &stopTransportServer;
tcp6API.reloadConfiguration = &reloadConfiguration;
tcp6API.addressToString = &addressToString;
return &tcp6API;
}
void donetransport_tcp6() {
int i;
for (i=0;i