/*
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/tcp.c
* @brief Implementation of the TCP transport service
* @author Christian Grothoff
*/
#include "gnunet_util.h"
#include "gnunet_transport.h"
#include "platform.h"
#define DEBUG_TCP NO
/**
* after how much time of the core not being associated with a tcp
* connection anymore do we close it?
*/
#define TCP_TIMEOUT 30 * cronSECONDS
/**
* Host-Address in a TCP network.
*/
typedef struct {
/**
* claimed IP of the sender, network byte order
*/
IPaddr ip;
/**
* claimed port of the sender, network byte order
*/
unsigned short port;
/**
* reserved (set to 0 for signature verification)
*/
unsigned short reserved;
} HostAddress;
/**
* TCP 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];
} TCPMessagePack;
/**
* Initial handshake message. Note that the beginning
* must match the CS_HEADER since we are using tcpio.
*/
typedef struct {
/**
* size of the handshake message, in nbo, value is 24
*/
unsigned short size;
/**
* "message type", TCP version number, always 0.
*/
unsigned short version;
/**
* Identity of the node connecting (TCP client)
*/
HostIdentity clientIdentity;
} TCPWelcome;
/**
* Transport Session handle.
*/
typedef struct {
/**
* the tcp 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;
} TCPSession;
/* *********** globals ************* */
/**
* apis (our advertised API and the core api )
*/
static CoreAPIForTransport * coreAPI;
static TransportAPI tcpAPI;
/**
* one thread for listening for new connections,
* and for reading on all open sockets
*/
static PTHREAD_T listenThread;
/**
* sock is the tcp socket that we listen on for new inbound
* connections.
*/
static int tcp_sock;
/**
* tcp_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 tcp_pipe[2];
/**
* Array of currently active TCP sessions.
*/
static TSession ** tsessions = NULL;
static int tsessionCount;
static int tsessionArrayLength;
/**
* handles for statistics
*/
static int stat_octets_total_tcp_in;
static int stat_octets_total_tcp_out;
/* configuration */
static CIDRNetwork * 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 tcplock;
/**
* 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 tcp_shutdown = YES;
/* ******************** helper functions *********************** */
/**
* Check if we are allowed to connect to the given IP.
*/
static int isBlacklisted(IPaddr ip) {
int ret;
MUTEX_LOCK(&tcplock);
ret = checkIPListed(filteredNetworks_,
ip);
MUTEX_UNLOCK(&tcplock);
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;
ret = WRITE(tcp_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 tcpDisconnect(TSession * tsession) {
if (tsession->internal != NULL) {
TCPSession * tcpsession = tsession->internal;
MUTEX_LOCK(&tcpsession->lock);
tcpsession->users--;
if (tcpsession->users > 0) {
MUTEX_UNLOCK(&tcpsession->lock);
return OK;
}
MUTEX_UNLOCK(&tcpsession->lock);
MUTEX_DESTROY(&tcpsession->lock);
FREE(tcpsession->rbuff);
FREENONNULL(tcpsession->wbuff);
FREE(tcpsession);
}
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
* TCP layer is concerned, but since the core may still have
* references to it, tcpDisconnect may not instantly free all
* the associated resources.
*
* destroySession may only be called if the tcplock is already
* held.
*
* @param i index to the session handle
*/
static void destroySession(int i) {
TCPSession * tcpSession;
tcpSession = tsessions[i]->internal;
if (tcpSession->sock != -1)
if (0 != SHUTDOWN(tcpSession->sock, SHUT_RDWR))
LOG_STRERROR(LOG_EVERYTHING, "shutdown");
CLOSE(tcpSession->sock);
tcpSession->sock = -1;
tcpDisconnect(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 getGNUnetTCPPort() {
struct servent * pse; /* pointer to service information entry */
unsigned short port;
port = (unsigned short) getConfigurationInt("TCP",
"PORT");
if (port == 0) { /* try lookup in services */
if ((pse = getservbyname("gnunet", "tcp")))
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 tcpAssociate(TSession * tsession) {
TCPSession * tcpSession;
if (tsession == NULL) {
BREAK();
return SYSERR;
}
tcpSession = (TCPSession*) tsession->internal;
MUTEX_LOCK(&tcpSession->lock);
tcpSession->users++;
MUTEX_UNLOCK(&tcpSession->lock);
return OK;
}
/**
* The socket of session i has data waiting, process!
*
* This function may only be called if the tcplock is
* already held by the caller.
*/
static int readAndProcess(int i) {
TSession * tsession;
TCPSession * tcpSession;
unsigned int len;
int ret;
TCPMessagePack * pack;
MessagePack * mp;
tsession = tsessions[i];
if (SYSERR == tcpAssociate(tsession))
return SYSERR;
tcpSession = tsession->internal;
ret = READ(tcpSession->sock,
&tcpSession->rbuff[tcpSession->pos],
tcpSession->size - tcpSession->pos);
cronTime(&tcpSession->lastUse);
if (ret == 0) {
tcpDisconnect(tsession);
#if DEBUG_TCP
LOG(LOG_DEBUG,
"READ on socket %d returned 0 bytes, closing connection\n",
tcpSession->sock);
#endif
return SYSERR; /* other side closed connection */
}
if (ret < 0) {
if ( (errno == EINTR) ||
(errno == EAGAIN) ) {
#if DEBUG_TCP
LOG_STRERROR(LOG_DEBUG, "read");
#endif
tcpDisconnect(tsession);
return OK;
}
#if DEBUG_TCP
LOG_STRERROR(LOG_INFO, "read");
#endif
tcpDisconnect(tsession);
return SYSERR;
}
incrementBytesReceived(ret);
statChange(stat_octets_total_tcp_in,
ret);
tcpSession->pos += ret;
len = ntohs(((TCPMessagePack*)&tcpSession->rbuff[0])->size);
if (len > tcpSession->size) /* if MTU larger than expected, grow! */
GROW(tcpSession->rbuff,
tcpSession->size,
len);
#if DEBUG_TCP
LOG(LOG_DEBUG,
"Read %d bytes on socket %d, expecting %d for full message\n",
tcpSession->pos,
tcpSession->sock,
len);
#endif
if ( (tcpSession->pos < 2) ||
(tcpSession->pos < len) ) {
tcpDisconnect(tsession);
return OK;
}
/* complete message received, let's check what it is */
if (YES == tcpSession->expectingWelcome) {
TCPWelcome * welcome;
#if DEBUG_TCP
EncName enc;
#endif
welcome = (TCPWelcome*) &tcpSession->rbuff[0];
if ( (ntohs(welcome->version) != 0) ||
(ntohs(welcome->size) != sizeof(TCPWelcome)) ) {
LOG(LOG_WARNING,
_("Expected welcome message on tcp connection, got garbage. Closing.\n"));
tcpDisconnect(tsession);
return SYSERR;
}
tcpSession->expectingWelcome = NO;
memcpy(&tcpSession->sender,
&welcome->clientIdentity,
sizeof(HostIdentity));
#if DEBUG_TCP
IFLOG(LOG_DEBUG,
hash2enc(&tcpSession->sender.hashPubKey,
&enc));
LOG(LOG_DEBUG,
"tcp welcome message from %s received\n",
&enc);
#endif
memmove(&tcpSession->rbuff[0],
&tcpSession->rbuff[sizeof(TCPWelcome)],
tcpSession->pos - sizeof(TCPWelcome));
tcpSession->pos -= sizeof(TCPWelcome);
len = ntohs(((TCPMessagePack*)&tcpSession->rbuff[0])->size);
}
if ( (tcpSession->pos < 2) ||
(tcpSession->pos < len) ) {
tcpDisconnect(tsession);
return OK;
}
pack = (TCPMessagePack*)&tcpSession->rbuff[0];
/* send msg to core! */
if ((unsigned int)len <= sizeof(TCPMessagePack)) {
LOG(LOG_WARNING,
_("Received malformed message from tcp-peer connection. Closing.\n"));
tcpDisconnect(tsession);
return SYSERR;
}
mp = MALLOC(sizeof(MessagePack));
mp->msg = MALLOC(len);
memcpy(mp->msg,
&pack->parts[0],
len - sizeof(TCPMessagePack));
memcpy(&mp->sender,
&tcpSession->sender,
sizeof(HostIdentity));
mp->crc = ntohl(pack->checkSum);
mp->isEncrypted = ntohs(pack->isEncrypted);
mp->size = len - sizeof(TCPMessagePack);
mp->tsession = tsession;
#if DEBUG_TCP
LOG(LOG_DEBUG,
"tcp transport received %d bytes, forwarding to core\n",
mp->size);
#endif
coreAPI->receive(mp);
if (tcpSession->pos < len) {
BREAK();
tcpDisconnect(tsession);
return SYSERR;
}
/* finally, shrink buffer adequately */
memmove(&tcpSession->rbuff[0],
&tcpSession->rbuff[len],
tcpSession->pos - len);
tcpSession->pos -= len;
tcpDisconnect(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 tcplock before
* calling. It is ok to call this function without holding tcplock if
* the return value is ignored.
*/
static int addTSession(TSession * tsession) {
int i;
MUTEX_LOCK(&tcplock);
if (tsessionCount == tsessionArrayLength)
GROW(tsessions,
tsessionArrayLength,
tsessionArrayLength * 2);
i = tsessionCount;
tsessions[tsessionCount++] = tsession;
MUTEX_UNLOCK(&tcplock);
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;
TCPSession * tcpSession;
tcpSession = MALLOC(sizeof(TCPSession));
tcpSession->pos = 0;
tcpSession->size = tcpAPI.mtu + sizeof(TCPMessagePack);
tcpSession->rbuff = MALLOC(tcpSession->size);
tcpSession->wpos = 0;
tcpSession->wbuff = NULL;
tcpSession->sock = sock;
/* fill in placeholder identity to mark that we
are waiting for the welcome message */
memcpy(&tcpSession->sender,
coreAPI->myIdentity,
sizeof(HostIdentity));
tcpSession->expectingWelcome = YES;
MUTEX_CREATE_RECURSIVE(&tcpSession->lock);
tcpSession->users = 1; /* us only, core has not seen this tsession! */
cronTime(&tcpSession->lastUse);
tsession = MALLOC(sizeof(TSession));
tsession->ttype = TCP_PROTOCOL_NUMBER;
tsession->internal = tcpSession;
addTSession(tsession);
}
/**
* Main method for the thread listening on the tcp socket and all tcp
* connections. Whenever a message is received, it is forwarded to the
* core. This thread waits for activity on any of the TCP connections
* and processes deferred (async) writes and buffers reads until an
* entire message has been received.
*/
static void * tcpListenMain() {
struct sockaddr_in clientAddr;
fd_set readSet;
fd_set errorSet;
fd_set writeSet;
struct stat buf;
int lenOfIncomingAddr;
int i;
int max;
int ret;
if (tcp_sock != -1)
LISTEN(tcp_sock, 5);
SEMAPHORE_UP(serverSignal); /* we are there! */
MUTEX_LOCK(&tcplock);
while (tcp_shutdown == NO) {
FD_ZERO(&readSet);
FD_ZERO(&errorSet);
FD_ZERO(&writeSet);
if (tcp_sock != -1) {
if (isSocketValid(tcp_sock)) {
FD_SET(tcp_sock, &readSet);
} else {
LOG_STRERROR(LOG_ERROR, "isSocketValid");
tcp_sock = -1; /* prevent us from error'ing all the time */
}
}
if (tcp_pipe[0] != -1) {
if (-1 != FSTAT(tcp_pipe[0], &buf)) {
FD_SET(tcp_pipe[0], &readSet);
} else {
LOG_STRERROR(LOG_ERROR, "fstat");
tcp_pipe[0] = -1; /* prevent us from error'ing all the time */
}
}
max = tcp_pipe[0];
if (tcp_sock > tcp_pipe[0])
max = tcp_sock;
for (i=0;iinternal;
int sock = tcpSession->sock;
if (sock != -1) {
if (isSocketValid(sock)) {
FD_SET(sock, &readSet);
FD_SET(sock, &errorSet);
if (tcpSession->wpos > 0)
FD_SET(sock, &writeSet); /* do we have a pending write request? */
} else {
LOG_STRERROR(LOG_ERROR, "isSocketValid");
destroySession(i);
}
} else {
BREAK(); /* sock in tsessions array should never be -1 */
destroySession(i);
}
if (sock > max)
max = sock;
}
MUTEX_UNLOCK(&tcplock);
ret = SELECT(max+1, &readSet, &writeSet, &errorSet, NULL);
MUTEX_LOCK(&tcplock);
if ( (ret == -1) &&
( (errno == EAGAIN) || (errno == EINTR) ) )
continue;
if (ret == -1) {
if (errno == EBADF) {
LOG_STRERROR(LOG_ERROR, "select");
} else {
DIE_STRERROR("select");
}
}
if (tcp_sock != -1) {
if (FD_ISSET(tcp_sock, &readSet)) {
int sock;
lenOfIncomingAddr = sizeof(clientAddr);
sock = ACCEPT(tcp_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! */
IPaddr ipaddr;
GNUNET_ASSERT(sizeof(struct in_addr) == sizeof(IPaddr));
memcpy(&ipaddr,
&clientAddr.sin_addr,
sizeof(struct in_addr));
if (YES == isBlacklisted(ipaddr)) {
LOG(LOG_INFO,
_("Rejected blacklisted connection from %u.%u.%u.%u.\n"),
PRIP(ntohl(*(int*)&clientAddr.sin_addr)));
CLOSE(sock);
} else {
#if DEBUG_TCP
LOG(LOG_INFO,
"Accepted connection from %u.%u.%u.%u.\n",
PRIP(ntohl(*(int*)&clientAddr.sin_addr)));
#endif
createNewSession(sock);
}
} else {
LOG_STRERROR(LOG_INFO, "accept");
}
}
}
if (FD_ISSET(tcp_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(tcp_pipe[0],
&buf[0],
MAXSIG_BUF)) {
LOG_STRERROR(LOG_WARNING, "read");
}
}
for (i=0;iinternal;
int sock = tcpSession->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,
tcpSession->wbuff,
tcpSession->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 == tcpSession->wpos) {
FREENONNULL(tcpSession->wbuff);
tcpSession->wbuff = NULL;
tcpSession->wpos = 0;
} else {
memmove(tcpSession->wbuff,
&tcpSession->wbuff[ret],
tcpSession->wpos - ret);
tcpSession->wpos -= ret;
}
}
if (FD_ISSET(sock, &errorSet)) {
destroySession(i);
i--;
continue;
}
if ( ( tcpSession->users == 1) &&
(cronTime(NULL) > tcpSession->lastUse + TCP_TIMEOUT) ) {
destroySession(i);
i--;
continue;
}
}
}
/* shutdown... */
if (tcp_sock != -1) {
CLOSE(tcp_sock);
tcp_sock = -1;
}
/* close all sessions */
while (tsessionCount > 0)
destroySession(0);
MUTEX_UNLOCK(&tcplock);
SEMAPHORE_UP(serverSignal); /* we are there! */
return NULL;
} /* end of tcp listen main */
/**
* Send a message (already encapsulated if needed) via the
* tcp socket (or enqueue if sending now would block).
*
* @param tcpSession 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 tcpDirectSend(TCPSession * tcpSession,
void * mp,
unsigned int ssize) {
int ok;
int ret, success;
if (tcpSession->sock == -1) {
#if DEBUG_TCP
LOG(LOG_INFO,
"tcpDirectSend called, but socket is closed\n");
#endif
return SYSERR;
}
if (ssize == 0) {
BREAK(); /* size 0 not allowed */
return SYSERR;
}
if (ssize > tcpAPI.mtu + sizeof(TCPMessagePack)) {
BREAK(); /* size > mtu */
return SYSERR;
}
ok = SYSERR;
MUTEX_LOCK(&tcplock);
if (tcpSession->wpos > 0) {
/* select already pending... */
ret = 0;
success = NO;
} else {
success = SEND_NONBLOCKING(tcpSession->sock,
mp,
ssize,
&ret);
}
if (success == SYSERR) {
LOG_STRERROR(LOG_INFO, "send");
MUTEX_UNLOCK(&tcplock);
return SYSERR;
} else if (success == NO)
ret = 0;
if ((unsigned int)ret <= ssize) { /* some bytes send or blocked */
if ((unsigned int)ret < ssize) {
if (tcpSession->wbuff == NULL) {
tcpSession->wbuff = MALLOC(tcpAPI.mtu + sizeof(TCPMessagePack));
tcpSession->wpos = 0;
}
if (ssize + tcpSession->wpos > tcpAPI.mtu + sizeof(TCPMessagePack) + ret) {
ssize = 0;
ok = SYSERR; /* buffer full, drop */
} else {
memcpy(&tcpSession->wbuff[tcpSession->wpos],
mp,
ssize - ret);
tcpSession->wpos += ssize - ret;
if (tcpSession->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(&tcplock);
cronTime(&tcpSession->lastUse);
incrementBytesSent(ssize);
statChange(stat_octets_total_tcp_out,
ssize);
return ok;
}
/**
* Send a message (already encapsulated if needed) via the
* tcp socket. Block if required.
*
* @param tcpSession 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 tcpDirectSendReliable(TCPSession * tcpSession,
void * mp,
unsigned int ssize) {
int ok;
if (tcpSession->sock == -1) {
#if DEBUG_TCP
LOG(LOG_INFO,
"tcpDirectSendReliable called, but socket is closed\n");
#endif
return SYSERR;
}
if (ssize == 0) {
BREAK();
return SYSERR;
}
if (ssize > tcpAPI.mtu + sizeof(TCPMessagePack)) {
BREAK();
return SYSERR;
}
MUTEX_LOCK(&tcplock);
if (tcpSession->wpos > 0) {
unsigned int old = tcpSession->wpos;
/* reliable: grow send-buffer above limit! */
GROW(tcpSession->wbuff,
tcpSession->wpos,
tcpSession->wpos + ssize);
memcpy(&tcpSession->wbuff[old],
mp,
ssize);
ok = OK;
} else {
ok = tcpDirectSend(tcpSession,
mp,
ssize);
}
MUTEX_UNLOCK(&tcplock);
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) {
HostAddress * haddr;
haddr = (HostAddress*) &((HELO_Message_GENERIC*)helo)->senderAddress[0];
if ( (ntohs(helo->senderAddressSize) != sizeof(HostAddress)) ||
(ntohs(helo->header.size) != HELO_Message_size(helo)) ||
(ntohs(helo->header.requestType) != p2p_PROTO_HELO) ||
(ntohs(helo->protocol) != TCP_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;
HostAddress * haddr;
unsigned short port;
port = getGNUnetTCPPort();
if (0 == port) {
LOG(LOG_DEBUG,
"TCP port is 0, will only send using TCP.\n");
return SYSERR; /* TCP transport is configured SEND-only! */
}
msg = (HELO_Message *) MALLOC(sizeof(HELO_Message) + sizeof(HostAddress));
haddr = (HostAddress*) &((HELO_Message_GENERIC*)msg)->senderAddress[0];
if (SYSERR == getPublicIPAddress(&haddr->ip)) {
FREE(msg);
LOG(LOG_WARNING,
_("Could not determine my public IP address.\n"));
return SYSERR;
}
haddr->port = htons(port);
haddr->reserved = htons(0);
msg->senderAddressSize = htons(sizeof(HostAddress));
msg->protocol = htons(TCP_PROTOCOL_NUMBER);
msg->MTU = htonl(tcpAPI.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 tcpConnect(HELO_Message * helo,
TSession ** tsessionPtr) {
int i;
HostAddress * haddr;
TCPWelcome welcome;
int sock;
TSession * tsession;
TCPSession * tcpSession;
struct sockaddr_in soaddr;
#if DEBUG_TCP
EncName enc;
#endif
if (tcp_shutdown == YES)
return SYSERR;
haddr = (HostAddress*) &((HELO_Message_GENERIC*)helo)->senderAddress[0];
#if DEBUG_TCP
hash2enc(&coreAPI->myIdentity->hashPubKey,
&enc);
LOG(LOG_DEBUG,
"Creating TCP connection to %u.%u.%u.%u:%u from %s.\n",
PRIP(ntohl(*(int*)&haddr->ip.addr)),
ntohs(haddr->port),
&enc);
#endif
sock = SOCKET(PF_INET,
SOCK_STREAM,
6); /* 6: TCP */
if (sock == -1) {
LOG_STRERROR(LOG_FAILURE, "socket");
return SYSERR;
}
if (0 != setBlocking(sock, NO)) {
CLOSE(sock);
LOG_STRERROR(LOG_FAILURE, "setBlocking");
return SYSERR;
}
memset(&soaddr,
0,
sizeof(soaddr));
soaddr.sin_family = AF_INET;
GNUNET_ASSERT(sizeof(struct in_addr) == sizeof(IPaddr));
memcpy(&soaddr.sin_addr,
&haddr->ip,
sizeof(IPaddr));
soaddr.sin_port = haddr->port;
i = CONNECT(sock,
(struct sockaddr*)&soaddr,
sizeof(soaddr));
if ( (i < 0) &&
(errno != EINPROGRESS) ) {
LOG(LOG_ERROR,
_("Cannot connect to %u.%u.%u.%u:%u: %s\n"),
PRIP(ntohl(*(int*)&haddr->ip)),
ntohs(haddr->port),
STRERROR(errno));
CLOSE(sock);
return SYSERR;
}
tcpSession = MALLOC(sizeof(TCPSession));
tcpSession->sock = sock;
tcpSession->wpos = 0;
tcpSession->wbuff = NULL;
tcpSession->size = tcpAPI.mtu + sizeof(TCPMessagePack);
tcpSession->rbuff = MALLOC(tcpSession->size);
tsession = MALLOC(sizeof(TSession));
tsession->internal = tcpSession;
tsession->ttype = tcpAPI.protocolNumber;
MUTEX_CREATE_RECURSIVE(&tcpSession->lock);
tcpSession->users = 2; /* caller + us */
tcpSession->pos = 0;
cronTime(&tcpSession->lastUse);
memcpy(&tcpSession->sender,
&helo->senderIdentity,
sizeof(HostIdentity));
tcpSession->expectingWelcome = NO;
MUTEX_LOCK(&tcplock);
i = addTSession(tsession);
/* send our node identity to the other side to fully establish the
connection! */
welcome.size = htons(sizeof(TCPWelcome));
welcome.version = htons(0);
memcpy(&welcome.clientIdentity,
coreAPI->myIdentity,
sizeof(HostIdentity));
if (SYSERR == tcpDirectSend(tcpSession,
&welcome,
sizeof(TCPWelcome))) {
destroySession(i);
tcpDisconnect(tsession);
MUTEX_UNLOCK(&tcplock);
return SYSERR;
}
MUTEX_UNLOCK(&tcplock);
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 tcpSend(TSession * tsession,
const void * msg,
const unsigned int size,
int isEncrypted,
const int crc) {
TCPMessagePack * mp;
int ok;
int ssize;
if (tcp_shutdown == YES)
return SYSERR;
if ( (size == 0) || (size > tcpAPI.mtu) ) {
BREAK();
return SYSERR;
}
if (((TCPSession*)tsession->internal)->sock == -1)
return SYSERR; /* other side closed connection */
mp = MALLOC(sizeof(TCPMessagePack) + size);
mp->checkSum = htonl(crc);
mp->isEncrypted = htons(isEncrypted);
memcpy(&mp->parts[0],
msg,
size);
ssize = size + sizeof(TCPMessagePack);
mp->size = htons(ssize);
ok = tcpDirectSend(tsession->internal,
mp,
ssize);
FREE(mp);
return ok;
}
/**
* Send a message to the specified remote node with
* increased reliability (i.e. grow TCP send buffer
* above one frame if needed).
*
* @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 tcpSendReliable(TSession * tsession,
const void * msg,
const unsigned int size,
int isEncrypted,
const int crc) {
TCPMessagePack * mp;
int ok;
int ssize;
if (tcp_shutdown == YES)
return SYSERR;
if ( (size == 0) || (size > tcpAPI.mtu) ) {
BREAK();
return SYSERR;
}
if (((TCPSession*)tsession->internal)->sock == -1)
return SYSERR; /* other side closed connection */
mp = MALLOC(sizeof(TCPMessagePack) + size);
mp->checkSum = htonl(crc);
mp->isEncrypted = htons(isEncrypted);
memcpy(&mp->parts[0],
msg,
size);
ssize = size + sizeof(TCPMessagePack);
mp->size = htons(ssize);
ok = tcpDirectSendReliable(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_in serverAddr;
const int on = 1;
unsigned short port;
if (serverSignal != NULL) {
BREAK();
return SYSERR;
}
if (0 != PIPE(tcp_pipe)) {
LOG_STRERROR(LOG_ERROR, "pipe");
return SYSERR;
}
setBlocking(tcp_pipe[1], NO);
serverSignal = SEMAPHORE_NEW(0);
tcp_shutdown = NO;
port = getGNUnetTCPPort();
if (port != 0) { /* if port == 0, this is a read-only
business! */
tcp_sock = SOCKET(PF_INET, SOCK_STREAM, 0);
if (tcp_sock < 0)
DIE_STRERROR("socket");
if (SETSOCKOPT(tcp_sock,
SOL_SOCKET,
SO_REUSEADDR,
&on, sizeof(on)) < 0 )
DIE_STRERROR("setsockopt");
memset((char *) &serverAddr,
0,
sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(getGNUnetTCPPort());
#if DEBUG_TCP
LOG(LOG_INFO,
"starting tcp peer server on port %d\n",
ntohs(serverAddr.sin_port));
#endif
if (BIND(tcp_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(tcp_sock);
tcp_sock = -1;
SEMAPHORE_FREE(serverSignal);
serverSignal = NULL;
return SYSERR;
}
} else
tcp_sock = -1;
if (0 == PTHREAD_CREATE(&listenThread,
(PThreadMain) &tcpListenMain,
NULL,
2048)) {
SEMAPHORE_DOWN(serverSignal); /* wait for server to be up */
} else {
LOG_STRERROR(LOG_ERROR,
"pthread_create");
CLOSE(tcp_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;
tcp_shutdown = YES;
signalSelect();
if (serverSignal != NULL) {
haveThread = YES;
SEMAPHORE_DOWN(serverSignal);
SEMAPHORE_FREE(serverSignal);
} else
haveThread = NO;
serverSignal = NULL;
CLOSE(tcp_pipe[1]);
CLOSE(tcp_pipe[0]);
if (tcp_sock != -1) {
CLOSE(tcp_sock);
tcp_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(&tcplock);
FREENONNULL(filteredNetworks_);
ch = getConfigurationString("TCP",
"BLACKLIST");
if (ch == NULL)
filteredNetworks_ = parseRoutes("");
else {
filteredNetworks_ = parseRoutes(ch);
FREE(ch);
}
MUTEX_UNLOCK(&tcplock);
}
/**
* Convert TCP address to a string.
*/
static char * addressToString(const HELO_Message * helo) {
char * ret;
HostAddress * haddr;
size_t n;
haddr = (HostAddress*) &((HELO_Message_GENERIC*)helo)->senderAddress[0];
n = 4*4+6+6;
ret = MALLOC(n);
SNPRINTF(ret,
n,
"%u.%u.%u.%u:%u (TCP)",
PRIP(ntohl(*(int*)&haddr->ip.addr)),
ntohs(haddr->port));
return ret;
}
/* ******************** public API ******************** */
/**
* The exported method. Makes the core api available
* via a global and returns the udp transport API.
*/
TransportAPI * inittransport_tcp(CoreAPIForTransport * core) {
int mtu;
MUTEX_CREATE_RECURSIVE(&tcplock);
reloadConfiguration();
tsessionCount = 0;
tsessionArrayLength = 0;
GROW(tsessions,
tsessionArrayLength,
32);
coreAPI = core;
stat_octets_total_tcp_in
= statHandle(_("# bytes received via tcp"));
stat_octets_total_tcp_out
= statHandle(_("# bytes sent via tcp"));
mtu = getConfigurationInt("TCP",
"MTU");
if (mtu == 0)
mtu = 1460;
if (mtu < 1200)
LOG(LOG_ERROR,
_("MTU for '%s' is probably too low (fragmentation not implemented!)\n"),
"TCP");
tcpAPI.protocolNumber = TCP_PROTOCOL_NUMBER;
tcpAPI.mtu = mtu - sizeof(TCPMessagePack);
tcpAPI.cost = 20000; /* about equal to udp */
tcpAPI.verifyHelo = &verifyHelo;
tcpAPI.createHELO = &createHELO;
tcpAPI.connect = &tcpConnect;
tcpAPI.associate = &tcpAssociate;
tcpAPI.send = &tcpSend;
tcpAPI.sendReliable = &tcpSendReliable;
tcpAPI.disconnect = &tcpDisconnect;
tcpAPI.startTransportServer = &startTransportServer;
tcpAPI.stopTransportServer = &stopTransportServer;
tcpAPI.reloadConfiguration = &reloadConfiguration;
tcpAPI.addressToString = &addressToString;
return &tcpAPI;
}
void donetransport_tcp() {
int i;
for (i=tsessionCount-1;i>=0;i--)
destroySession(i);
GROW(tsessions,
tsessionArrayLength,
0);
tsessions = NULL;
tsessionArrayLength = 0;
FREENONNULL(filteredNetworks_);
MUTEX_DESTROY(&tcplock);
}
/* end of tcp.c */