//
//sock.c
//
//Functions for handling sockets.
//
//
//-UserX 2001/11/07
#include "misc/compat.h"
#include <string.h>
//#include "isproxy.h"
#include "net/sock.h"
#include "base/mem.h"
#include "net/noderef.h"
#include "base/logger.h"
#include "crypt/random.h"
#include "pipe/dummy.h"
#ifndef _WINDOZE_
#include <errno.h>
#endif
//#ifdef _WINDOZE_
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
//#endif
/**
Functions for handling \Ref{SockHandle}s. <P>
Normally SockHandle's are maniuplated else where (\Ref{sockserv})
but to manual/direct use of \Ref{SockHandle} can be done in this manner.<P>
<B>Opening out bound connections</B><BR>
<CODE>
SockHandle *sh; <BR>
NodeRef *nr; <BR>
int er; <BR>
char *s; <BR>
<BR>
nr = noderefMake(); <BR>
nr->host = "myhost"; <BR>
nr->port = 1234; <BR>
<BR>
sh = \Ref{sockMake}(); <BR>
er = sockSetNodeRef(sh, nr); <BR>
noderefFree(nr); <BR>
if(er != SOCK_ERR_NONE) { <BR>
return; //insert error action <BR>
} <BR>
<BR>
er = sockOpen(sh); <BR>
if(er != SOCK_ERR_NONE) { <BR>
return; //insert error action <BR>
} <BR>
<BR>
sh->IOPipe->OutBuffer = dblockAppenString(sh->IOPipe->OutBuffer, "Hello World!"); <BR>
sockWrite(sh); <BR>
<BR>
sockRead(sh); <BR>
s = dblockToString(sh->IOPipe->InBuffer); <BR>
<BR>
sockClose(sh); <BR>
sockFree(sh); <BR>
<BR>
</CODE>
<B>Inbound connections:</B><BR>
<CODE>
er = sockSetLocalNodeRef(sh, nr); <BR>
er = sockListen(sh); <BR>
nsh = sockAccept(sh); <BR>
</CODE>
//todo: Better support for detecting inbound connections. <BR>
//todo: Allow options for non-blocking sockets.<BR>
//todo: Look into what needs to be done for supporting other transports.<BR>
@author UserX
@name sock
*/
//@{
struct sockaddr_in blanksockaddr_in = BLANKSOCKADDR_IN;
struct sockaddr_in localsockaddr_in = LOCALSOCKADDR_IN;
SockHandle blanksockhandle = {
BLANKPIPE_SOCK,
//NULL, // in datablock
//NULL, // out datablock
BLANKSOCKADDR_IN, //remote sockaddress
LOCALSOCKADDR_IN, //local sockaddress
//BLANKSOCKADDR_IN, //remote sockaddress
//LOCALSOCKADDR_IN, //local sockaddress
(SOCKET) SOCKET_ERROR, //socket
NULL, //noderef
NULL, //localnoderef
SOCK_STATUS_NEW, //status
SOCK_DIR_UNKNOWN, //connection direction
0, //socket error
0 //reg count
};
/**
Initializes sock functions. Doesn't actually do anything now.
Will probably be deleted later.
*/
void SocketAtStart() {
LOGDEBUG ("SocketAtStart called.");
return;
}
/**
Initializes an existing SockHandle.
@param sh The SockHandle to initialize.
*/
void sockInit(SockHandle *sh) {
LOGDEBUG (stringJoin("sockInit:", ptrToString(sh)));
if(sh == NULL) {
return;
}
sh->RegCount = 1;
sh->status = SOCK_STATUS_NEW;
pipeInit(&sh->IOPipe);
pipeInitFunctions(&sh->IOPipe,
NULL, NULL,
(PipeFuncAttach) sockPipeAttach, (PipeFuncDetach) sockPipeDetach,
(PipeFuncClose) sockPipeClose, NULL);
//sh->IOPipe.attachFunction = sockPipeAttach;
//sh->IOPipe.detachFunction = sockPipeDetach;
//sh->IOPipe.closeFunction = sockPipeClose;
sh->IOPipe.status = PSTATUS_NORMAL;
}
/**
Creates and initializes a new SockHandle.
@return Pointer to the new SockHandle.
*/
SockHandle *sockMake() {
SockHandle *sh;
LOGMEMDUMP();
sh = memCopy(&blanksockhandle, sizeof(SockHandle), "Sock", NULL);
LOGDEBUG (stringJoin("sockMake:", ptrToString(sh)));
sockInit(sh);
return sh;
}
/**
Creates a new SockHandle and saves the pointer to the specified address.
@param sh A pointer to where the pointer to a SockHandle will be stored.
*/
void sockMakeAt(SockHandle **sh) {
LOGDEBUG (stringJoin("sockMakeAt:", ptrToString(sh)));
if(sh == NULL) {
return;
}
*sh = sockMake();
}
/**
Increase the reference count to this sock.
@param sh Pointer to the SockHandle.
*/
void sockRegister(SockHandle *sh) {
LOGDEBUG (stringJoin("sockRegister:", ptrToString(sh)));
++sh->RegCount;
return;
}
/**
Decrements the reference count to this sock and releases it when it reaches zero.
@param sh Pointer to the SockHandle to release.
*/
void sockFree(SockHandle *sh) {
LOGDEBUG (stringJoin("sockFree:", ptrToString(sh)));
LOGDEBUG (stringJoin("- reference count:", intToString(sh->RegCount)));
if(--sh->RegCount != 0) {
return;
}
noderefFree(sh->noderef);
noderefFree(sh->localnoderef);
sockClose(sh);
pipeFree(&sh->IOPipe);
memFree(sh);
LOGMEMDUMP();
}
/**
Internal - Gets the error code of the last socket operation.
@param so The socket handle.
*/
int socketGetError(SOCKET so) {
int err = SOCKET_ERROR;
#ifdef _WINDOZE_
err = WSAGetLastError();
#else
err = errno;
#endif
LOGDEBUG (stringJoinMany(
"sockerGetError: socket(",
intToString(so),
") error(",
intToString(err),
")",
NULL));
return err;
}
/**
Internal - Gets and saves the last socket error, also returns the error code.
@param sh Pointer to the SockHandle.
@return The error code.
*/
int sockRecordError(SockHandle *sh) {
LOGDEBUG (stringJoin("sockRecordError:", ptrToString(sh)));
if(sh == NULL) {
return SOCKET_ERROR;
}
return sh->socketerror = socketGetError(sh->Socket);
}
/**
Logs an error related to a SockHandle.
@param sh Pointer to the SockHandle.
@param msg Releasable string, descriptive context of the error.
*/
void sockPrintError(SockHandle *sh, char *msg) {
LOGERROR(stringJoinMany(
"Socket(",
intToString(sh->Socket),
"), Error (",
intToString(sh->socketerror),
"):",
msg,
NULL
));
}
/**
Internal - Converts a NodeRef to a network socket address.
@param nr Pointer to the NodeRef to lookup.
@param sai Pointer to the address structure.
@return Zero on success, non-zero error code on failure.
*/
int nodeRefToSockAddr(NodeRef *nr, struct sockaddr_in *sai) {
if (sai == NULL) {
return SOCK_ERR_NOSOCKHANDLE;
}
if (nr == NULL) {
return SOCK_ERR_BADNODEREF;
}
sai->sin_port = htons((unsigned short)nr->PortNum);
if(sai->sin_port == 0 && nr->PortNum != 0) {
//noderefFree(nr);
return SOCK_ERR_BADPORT;
}
sai->sin_addr.s_addr = inet_addr(nr->HostName); //try #.#.#.# first
if(sai->sin_addr.s_addr == INADDR_NONE) {
struct hostent *nameresult = gethostbyname(nr->HostName); // try a DNS lookup
if (nameresult == NULL) {
LOGERROR(stringJoinMany("nodeRefToSockAddr: bad host name: host(",
stringCopy(nr->HostName),
"), port(", intToString(nr->PortNum) ,")",
_("Failed to lookup host name."),
NULL));
return SOCK_ERR_BADHOST;
}
memcpy(&sai->sin_addr, nameresult->h_addr, nameresult->h_length);
}
return SOCK_ERR_NONE;
}
/**
Set the remote NodeRef, does a look up. A copy of the NodeRef is made.
@param sh The SockHandle.
@param nr The NodeRef to copy.
@return Zero if successful, or non-zero error code.
*/
int sockSetNodeRef(SockHandle *sh, NodeRef *nr) {
struct sockaddr_in tmpin = BLANKSOCKADDR_IN;
int er;
if (sh == NULL) {
return SOCK_ERR_NOSOCKHANDLE;
}
if (sh->noderef != NULL) {
noderefFree(sh->noderef);
sh->noderef = NULL;
}
if(nr == NULL) {
return SOCK_ERR_BADNODEREF;
}
nr = noderefCopy(nr);
//nodeRefFree(nr);
er = nodeRefToSockAddr(nr, &tmpin);
if(er != SOCK_ERR_NONE) {
noderefFree(nr);
return er;
}
sh->SockAddress.sin_port = tmpin.sin_port;
sh->SockAddress.sin_addr.s_addr = tmpin.sin_addr.s_addr;
sh->noderef = nr;
return SOCK_ERR_NONE;
}
/**
Set the local NodeRef, does a look up. A copy of the NodeRef is made.
@param sh Pointer to the SockHandle.
@param nr Pointer to the NodeRef to copy.
@return Zero if successful, or non-zero error code.
*/
int sockSetLocalNodeRef(SockHandle *sh, NodeRef *nr) {
struct sockaddr_in tmpin = BLANKSOCKADDR_IN;
int er;
if (sh == NULL) {
return SOCK_ERR_NOSOCKHANDLE;
}
if (sh->localnoderef != NULL) {
noderefFree(sh->localnoderef);
sh->localnoderef = NULL;
}
if(nr == NULL) {
return SOCK_ERR_BADNODEREF;
}
nr = noderefCopy(nr);
//nodeRefFree(nr);
er = nodeRefToSockAddr(nr, &tmpin);
if(er != SOCK_ERR_NONE) {
noderefFree(nr);
return er;
}
sh->LocalSockAddress.sin_port = tmpin.sin_port;
sh->LocalSockAddress.sin_addr.s_addr = tmpin.sin_addr.s_addr;
sh->localnoderef = nr;
return SOCK_ERR_NONE;
}
/**
Internal - Sets the remote network socket address. Does not update any NodeRef
@param sh Pointer to the SockHandle.
@param sain Pointer to the network address to set.
@return Zero if successful, or non-zero error code.
*/
int sockSetSockAddress(SockHandle *sh, struct sockaddr_in *sain) {
if(sh == NULL) {
return SOCK_ERR_NOSOCKHANDLE;
}
if(sain == NULL) {
return SOCK_ERR_NOSOCKHANDLE;
}
memcpy(&sh->SockAddress, sain, sizeof(struct sockaddr_in));
return SOCK_ERR_NONE;
}
/**
Internal - Sets the local network socket address. Does not update any NodeRef
@param sh Pointer to the SockHandle.
@param sain Pointer to the network address to set.
@return Zero if successful, or non-zero error code.
*/
int sockSetLocalSockAddress(SockHandle *sh, struct sockaddr_in *sain) {
if(sh == NULL) {
return SOCK_ERR_NOSOCKHANDLE;
}
if(sain == NULL) {
return SOCK_ERR_NOSOCKHANDLE;
}
memcpy(&sh->SockAddress, sain, sizeof(struct sockaddr_in));
return SOCK_ERR_NONE;
}
/**
Opens an outbound connection.
@param sh Pointer to the SockHandle to use.
@return Zero if successful, or non-zero error code.
*/
int sockOpen(SockHandle *sh) {
int er;
LOGDEBUG (stringJoin("sockOpen:", ptrToString(sh)));
if(sh == NULL) {
return SOCK_ERR_NOSOCKHANDLE;
}
sh->direction = SOCK_DIR_OUT;
//todo: check status
//todo: check return error codes
#ifdef __FreeBSD__
sh->Socket = socket(PF_INET, SOCK_STREAM, 0);
#else
sh->Socket = socket(AF_INET, SOCK_STREAM, 0);
#endif
LOGDEBUG(stringJoin("- socket:", intToString(sh->Socket)));
if(sh->Socket == SOCKET_ERROR) {
return SOCK_ERR_SOCKETERROR;
}
er = bind(sh->Socket, (const struct sockaddr *) &sh->LocalSockAddress, sizeof(struct sockaddr_in));
if(er == SOCKET_ERROR) {
sockRecordError(sh);
sockPrintError(sh, "Failed to bind new connection.");
sockClose(sh);
return SOCK_ERR_BINDFAILED;
}
er = connect(sh->Socket, (const struct sockaddr *) &sh->SockAddress, sizeof(struct sockaddr_in));
//sh
if(er == SOCKET_ERROR) {
sockRecordError(sh);
sockPrintError(sh, "Failed to open new connection.");
LOGERROR(stringJoinMany("sockOpen: open failed: host(", stringCopy(sh->noderef->HostName),
"), port(", intToString(sh->noderef->PortNum) ,")",
_("Failed to open an outbound connection."),
NULL));
sockClose(sh);
return SOCK_ERR_NOCONNECT;
}
sh->status = SOCK_STATUS_OPEN;
sh->IOPipe.status |= PSTATUS_OPENED;
randomAddEntropyClock(2);
return SOCK_ERR_NONE;
}
/**
Closes a connection.
@param sh Pointer to the SockHandle to use.
@return Zero if successful, or non-zero error code.
*/
int sockClose(SockHandle *sh) {
LOGDEBUG (stringJoin("sockClose:", ptrToString(sh)));
if(sh == NULL) {
return SOCK_ERR_NOSOCKHANDLE;
}
//todo: check status
//todo: check return error codes
if(sh->Socket != SOCKET_ERROR) {
closesocket(sh->Socket);
}
sh->direction = SOCK_DIR_UNKNOWN;
sh->status = SOCK_STATUS_CLOSED;
sh->IOPipe.status |= PSTATUS_CLOSED;
sh->Socket = (SOCKET) SOCKET_ERROR;
randomAddEntropyClock(1);
return SOCK_ERR_NONE;
}
/**
Starts a SockHandle listening for connections.
@param sh Pointer to the SockHandle to use.
@return Zero if successful, or non-zero error code.
*/
int sockListen(SockHandle *sh) {
int er;
LOGDEBUG (stringJoin("sockListen:", ptrToString(sh)));
if(sh == NULL) {
return SOCK_ERR_NOSOCKHANDLE;
}
sh->direction = SOCK_DIR_IN;
//todo: check status
//todo: check return error codes
#ifdef __FreeBSD__
sh->Socket = socket(PF_INET, SOCK_STREAM, 0);
#else
sh->Socket = socket(AF_INET, SOCK_STREAM, 0);
#endif
if(sh->Socket == SOCKET_ERROR) {
return SOCK_ERR_SOCKETERROR;
}
er = bind(sh->Socket, (struct sockaddr *) &sh->LocalSockAddress, sizeof(struct sockaddr_in));
if(er == SOCKET_ERROR) {
sockRecordError(sh);
sockPrintError(sh, "Failed to bind listening socket.");
LOGERROR(stringJoinMany("sockListen: bind failed: host(", stringCopy(sh->localnoderef->HostName),
"), port(", intToString(sh->localnoderef->PortNum) ,")",
_("Failed to bind listening socket."),
NULL));
sockClose(sh);
return SOCK_ERR_BINDFAILED;
}
er = listen(sh->Socket, SOMAXCONN);
if(er == SOCKET_ERROR) {
sockRecordError(sh);
sockPrintError(sh, "Failed to establish listening socket.");
LOGERROR(stringJoinMany("sockListen: listen failed: host(", stringCopy(sh->localnoderef->HostName),
"), port(", intToString(sh->localnoderef->PortNum) ,")",
_("Failed to establish a listening socket."),
NULL));
sockClose(sh);
return SOCK_ERR_LISTENFAILED;
}
randomAddEntropyClock(1);
sh->status = SOCK_STATUS_LISTENING;
LOGDEBUG(stringJoin("- socket:", intToString(sh->Socket)));
return SOCK_ERR_NONE;
}
/**
Accepts an incoming connection from a listening SockHandle.
@param sh Pointer to the SockHandle to use.
@return A pointer to the SockHandle of the new connection.
*/
SockHandle *sockAccept(SockHandle *sh) {
SockHandle *nsh;
int i = sizeof(struct sockaddr_in);
LOGDEBUG(stringJoin("sockAccept:", ptrToString(sh)));
if(sh == NULL) {
return NULL;
}
//todo: check status
nsh = sockMake();
LOGDEBUG(stringJoin("- SockHandle:", ptrToString(nsh)));
nsh->direction = SOCK_DIR_IN;
nsh->Socket = accept(sh->Socket, (struct sockaddr *) &sh->SockAddress, &i);
LOGDEBUG(stringJoin("- socket:", intToString(sh->Socket)));
if(nsh->Socket == SOCKET_ERROR) {
sockFree(nsh);
return NULL;
}
memcpy(&nsh->LocalSockAddress, &sh->LocalSockAddress, sizeof(struct sockaddr_in));
nsh->status = SOCK_STATUS_OPEN;
nsh->IOPipe.status |= PSTATUS_OPENED;
nsh->localnoderef = noderefCopy(sh->localnoderef);
randomAddEntropyClock(2);
return nsh;
}
/**
Receives notice that a pipe has been attached to this SockHandle.
@param sh Pointer to the SockHandle in question.
@param newpipe Pointer to the Pipe being attached.
*/
void sockPipeAttach(SockHandle *sh, Pipe *newpipe) {
LOGDEBUG(stringJoin("sockPipeAttach:", ptrToString(sh)));
sockRegister(sh);
}
/**
Receives notice that a pipe has been detached from this SockHandle.
@param sh Pointer to the SockHandle in question.
*/
void sockPipeDetach(SockHandle *sh) {
LOGDEBUG(stringJoin("sockPipeDetach:", ptrToString(sh)));
sockFree(sh);
}
/**
Receives notice that a pipe that this SockHandle is part of is closing.
@param sh Pointer to the SockHandle in question.
*/
void sockPipeClose(SockHandle *sh) {
LOGDEBUG(stringJoin("sockPipeClose:", ptrToString(sh)));
sockClose(sh);
}
/**
Internal - Read data from a socket into a DataBlock.
@param so The socket handle to read from.
@param db The DataBlock to store read data.
@param status Pointer to a SockHandle status.
@return The new pointer for <B>db</B>.
*/
DataBlock *socketRead(SOCKET so, DataBlock *db, int *status) {
int i;
if(so == SOCKET_ERROR) {
return db;
}
db = dblockExpand(db, db->size + 1024);
randomAddEntropyClock(2);
i = recv(so, SOCKETBUFFERCAST db->data + db->size, db->maxSize - db->size, MSG_NOSIGNAL);
if(i == SOCKET_ERROR || i == 0) {
if(status != NULL) {
*status = SOCK_STATUS_CLOSING;
}
//sockRecordError(sh);
} else {
db->size += i;
LOGDEBUGTRAFFIC(stringJoinMany("socketRead: socket ",
intToString(so),
" got bytes ",
intToHexString(i),
NULL));
}
return db;
}
/**
Internal - Writes data to a socket from a DataBlock.
@param so The socket handle to read from.
@param db The DataBlock with the data to be sent.
@param status Pointer to a SockHandle status.
@return The new pointer for <B>db</B> cotaining the unsent portion.
*/
DataBlock *socketWrite(SOCKET so, DataBlock *db, int *status) {
int i = SOCK_MAX_WRITE;
int er;
if(so == SOCKET_ERROR || db == NULL || db->size == 0) {
return db;
}
if(db->size < i) {
if(db->size < 0) {
db->size = 0;
}
i = db->size;
}
if(i == 0) {
return db;
}
randomAddEntropyClock(2);
er = send(so, SOCKETBUFFERCAST db->data, i, MSG_NOSIGNAL);
if(er == SOCKET_ERROR || er == 0) {
if(status != NULL) {
*status = SOCK_STATUS_CLOSING;
}
//sockRecordError(sh);
} else {
db = dblockDelete(db, 0, er);
LOGDEBUGTRAFFIC(stringJoinMany("socketWrite: socket ",
intToString(so),
" sent bytes ",
intToHexString(er),
NULL));
}
return db;
}
/**
Reads data from the SockHandle's socket and into its Pipe.
@param sh Pointer to the SockHandle to use.
*/
void sockRead(SockHandle *sh) {
sh->IOPipe.inBuffer = socketRead(sh->Socket, sh->IOPipe.inBuffer, &sh->status);
}
/**
Writes the data queued in the SockHandle's Pipe out to it's corresponding socket.
@param sh Pointer to the SockHandle to use.
*/
void sockWrite(SockHandle *sh) {
sh->IOPipe.outBuffer = socketWrite(sh->Socket, sh->IOPipe.outBuffer, &sh->status);
}
//@}
syntax highlighted by Code2HTML, v. 0.9.1