warzone2100/lib/netplay/netsocket.cpp

1167 lines
31 KiB
C++
Raw Normal View History

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2010 Warzone Resurrection Project
Warzone 2100 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 of the License, or
(at your option) any later version.
Warzone 2100 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 Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file netsocket.cpp
*
* Basic raw socket handling code.
*/
#include "lib/framework/frame.h"
#include "netsocket.h"
#include <vector>
#include <algorithm>
#if defined(WZ_OS_UNIX)
# include <arpa/inet.h>
# include <errno.h>
# include <fcntl.h>
# include <netdb.h>
# include <netinet/in.h>
# include <sys/ioctl.h>
# include <sys/socket.h>
# include <sys/types.h>
# include <sys/select.h>
# include <unistd.h>
typedef int SOCKET;
static const SOCKET INVALID_SOCKET = -1;
#elif defined(WZ_OS_WIN)
# include <winsock2.h>
# include <ws2tcpip.h>
# undef EAGAIN
# undef EBADF
# undef ECONNRESET
# undef EINPROGRESS
# undef EINTR
# undef EISCONN
# undef ETIMEDOUT
# undef EWOULDBLOCK
# define EAGAIN WSAEWOULDBLOCK
# define EBADF WSAEBADF
# define ECONNRESET WSAECONNRESET
# define EINPROGRESS WSAEINPROGRESS
# define EINTR WSAEINTR
# define EISCONN WSAEISCONN
# define ETIMEDOUT WSAETIMEDOUT
# define EWOULDBLOCK WSAEWOULDBLOCK
typedef SSIZE_T ssize_t;
# ifndef AI_V4MAPPED
# define AI_V4MAPPED 0x0008 /* IPv4 mapped addresses are acceptable. */
# endif
# ifndef AI_ADDRCONFIG
# define AI_ADDRCONFIG 0x0020 /* Use configuration of this host to choose returned address type.. */
# endif
#endif
// Fallback for systems that don't #define this flag
#ifndef MSG_NOSIGNAL
# define MSG_NOSIGNAL 0
#endif
enum
{
SOCK_CONNECTION,
SOCK_IPV4_LISTEN = SOCK_CONNECTION,
SOCK_IPV6_LISTEN,
SOCK_COUNT,
};
struct Socket
{
/* Multiple socket handles only for listening sockets. This allows us
* to listen on multiple protocols and address families (e.g. IPv4 and
* IPv6).
*
* All non-listening sockets will only use the first socket handle.
*/
SOCKET fd[SOCK_COUNT];
bool ready;
char textAddress[40];
};
struct SocketSet
{
std::vector<Socket *> fds;
};
bool socketReadReady(Socket const *sock)
{
return sock->ready;
}
int getSockErr(void)
{
#if defined(WZ_OS_UNIX)
return errno;
#elif defined(WZ_OS_WIN)
return WSAGetLastError();
#endif
}
void setSockErr(int error)
{
#if defined(WZ_OS_UNIX)
errno = error;
#elif defined(WZ_OS_WIN)
WSASetLastError(error);
#endif
}
#if defined(WZ_OS_WIN)
static HMODULE winsock2_dll = NULL;
static unsigned int major_windows_version = 0;
static int (WINAPI * getaddrinfo_dll_func)(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res) = NULL;
static int (WINAPI * freeaddrinfo_dll_func)(struct addrinfo *res) = NULL;
# define getaddrinfo getaddrinfo_dll_dispatcher
# define freeaddrinfo freeaddrinfo_dll_dispatcher
static int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res)
{
struct addrinfo hint;
if (hints)
{
memcpy(&hint, hints, sizeof(hint));
}
switch (major_windows_version)
{
case 0:
case 1:
case 2:
case 3:
// Windows 95, 98 and ME
case 4:
debug(LOG_ERROR, "Name resolution isn't supported on this version (%u) of Windows", major_windows_version);
return EAI_FAIL;
// Windows 2000, XP and Server 2003
case 5:
if (hints)
{
// These flags are only supported from version 6 and onward
hint.ai_flags &= ~(AI_V4MAPPED | AI_ADDRCONFIG);
}
// Windows Vista and Server 2008
case 6:
// Onward (aka: in the future)
default:
if (!winsock2_dll)
{
debug(LOG_ERROR, "Failed to load winsock2 DLL. Required for name resolution.");
return EAI_FAIL;
}
if (!getaddrinfo_dll_func)
{
debug(LOG_ERROR, "Failed to retrieve \"getaddrinfo\" function from winsock2 DLL. Required for name resolution.");
return EAI_FAIL;
}
return getaddrinfo_dll_func(node, service, hints ? &hint: NULL, res);
}
}
static void freeaddrinfo(struct addrinfo *res)
{
switch (major_windows_version)
{
case 0:
case 1:
case 2:
case 3:
// Windows 95, 98 and ME
case 4:
debug(LOG_ERROR, "Name resolution isn't supported on this version (%u) of Windows", major_windows_version);
return;
// Windows 2000, XP and Server 2003
case 5:
// Windows Vista and Server 2008
case 6:
// Onward (aka: in the future)
default:
if (!winsock2_dll)
{
debug(LOG_ERROR, "Failed to load winsock2 DLL. Required for name resolution.");
return;
}
if (!freeaddrinfo_dll_func)
{
debug(LOG_ERROR, "Failed to retrieve \"freeaddrinfo\" function from winsock2 DLL. Required for name resolution.");
return;
}
freeaddrinfo_dll_func(res);
}
}
#endif
static int addressToText(const struct sockaddr* addr, char* buf, size_t size)
{
switch (addr->sa_family)
{
case AF_INET:
{
unsigned char* address = (unsigned char*)&((const struct sockaddr_in*)addr)->sin_addr.s_addr;
return snprintf(buf, size,
"%hhu.%hhu.%hhu.%hhu",
address[0],
address[1],
address[2],
address[3]);
}
case AF_INET6:
{
uint16_t* address = (uint16_t*)&((const struct sockaddr_in6*)addr)->sin6_addr.s6_addr;
return snprintf(buf, size,
"%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx",
ntohs(address[0]),
ntohs(address[1]),
ntohs(address[2]),
ntohs(address[3]),
ntohs(address[4]),
ntohs(address[5]),
ntohs(address[6]),
ntohs(address[7]));
}
default:
ASSERT(!"Unknown address family", "Got non IPv4 or IPv6 address!");
return -1;
}
}
const char* strSockError(int error)
{
#if defined(WZ_OS_WIN)
switch (error)
{
case 0: return "No error";
case WSAEINTR: return "Interrupted system call";
case WSAEBADF: return "Bad file number";
case WSAEACCES: return "Permission denied";
case WSAEFAULT: return "Bad address";
case WSAEINVAL: return "Invalid argument";
case WSAEMFILE: return "Too many open sockets";
case WSAEWOULDBLOCK: return "Operation would block";
case WSAEINPROGRESS: return "Operation now in progress";
case WSAEALREADY: return "Operation already in progress";
case WSAENOTSOCK: return "Socket operation on non-socket";
case WSAEDESTADDRREQ: return "Destination address required";
case WSAEMSGSIZE: return "Message too long";
case WSAEPROTOTYPE: return "Protocol wrong type for socket";
case WSAENOPROTOOPT: return "Bad protocol option";
case WSAEPROTONOSUPPORT: return "Protocol not supported";
case WSAESOCKTNOSUPPORT: return "Socket type not supported";
case WSAEOPNOTSUPP: return "Operation not supported on socket";
case WSAEPFNOSUPPORT: return "Protocol family not supported";
case WSAEAFNOSUPPORT: return "Address family not supported";
case WSAEADDRINUSE: return "Address already in use";
case WSAEADDRNOTAVAIL: return "Can't assign requested address";
case WSAENETDOWN: return "Network is down";
case WSAENETUNREACH: return "Network is unreachable";
case WSAENETRESET: return "Net connection reset";
case WSAECONNABORTED: return "Software caused connection abort";
case WSAECONNRESET: return "Connection reset by peer";
case WSAENOBUFS: return "No buffer space available";
case WSAEISCONN: return "Socket is already connected";
case WSAENOTCONN: return "Socket is not connected";
case WSAESHUTDOWN: return "Can't send after socket shutdown";
case WSAETOOMANYREFS: return "Too many references, can't splice";
case WSAETIMEDOUT: return "Connection timed out";
case WSAECONNREFUSED: return "Connection refused";
case WSAELOOP: return "Too many levels of symbolic links";
case WSAENAMETOOLONG: return "File name too long";
case WSAEHOSTDOWN: return "Host is down";
case WSAEHOSTUNREACH: return "No route to host";
case WSAENOTEMPTY: return "Directory not empty";
case WSAEPROCLIM: return "Too many processes";
case WSAEUSERS: return "Too many users";
case WSAEDQUOT: return "Disc quota exceeded";
case WSAESTALE: return "Stale NFS file handle";
case WSAEREMOTE: return "Too many levels of remote in path";
case WSASYSNOTREADY: return "Network system is unavailable";
case WSAVERNOTSUPPORTED: return "Winsock version out of range";
case WSANOTINITIALISED: return "WSAStartup not yet called";
case WSAEDISCON: return "Graceful shutdown in progress";
case WSAHOST_NOT_FOUND: return "Host not found";
case WSANO_DATA: return "No host data of that type was found";
default: return "Unknown error";
}
#elif defined(WZ_OS_UNIX)
return strerror(error);
#endif
}
/**
* Test whether the given socket still has an open connection.
*
* @return true when the connection is open, false when it's closed or in an
* error state, check getSockErr() to find out which.
*/
static bool connectionIsOpen(Socket* sock)
{
const SocketSet set = {std::vector<Socket *>(1, sock)};
ASSERT_OR_RETURN((setSockErr(EBADF), false),
sock && sock->fd[SOCK_CONNECTION] != INVALID_SOCKET, "Invalid socket");
// Check whether the socket is still connected
int ret = checkSockets(&set, 0);
if (ret == SOCKET_ERROR)
{
return false;
}
else if (ret == (int)set.fds.size() && sock->ready)
{
/* The next recv(2) call won't block, but we're writing. So
* check the read queue to see if the connection is closed.
* If there's no data in the queue that means the connection
* is closed.
*/
#if defined(WZ_OS_WIN)
unsigned long readQueue;
ret = ioctlsocket(sock->fd[SOCK_CONNECTION], FIONREAD, &readQueue);
#else
int readQueue;
ret = ioctl(sock->fd[SOCK_CONNECTION], FIONREAD, &readQueue);
#endif
if (ret == SOCKET_ERROR)
{
debug(LOG_NET, "socket error");
return false;
}
else if (readQueue == 0)
{
// Disconnected
setSockErr(ECONNRESET);
debug(LOG_NET, "Read queue empty - failing (ECONNRESET)");
return false;
}
}
return true;
}
/**
* Similar to read(2) with the exception that this function won't be
* interrupted by signals (EINTR).
*/
ssize_t readNoInt(Socket* sock, void* buf, size_t max_size)
{
ssize_t received;
if (sock->fd[SOCK_CONNECTION] == INVALID_SOCKET)
{
debug(LOG_ERROR, "Invalid socket");
setSockErr(EBADF);
return SOCKET_ERROR;
}
do
{
received = recv(sock->fd[SOCK_CONNECTION], buf, max_size, 0);
} while (received == SOCKET_ERROR && getSockErr() == EINTR);
sock->ready = false;
return received;
}
/**
* Similar to write(2) with the exception that this function will block until
* <em>all</em> data has been written or an error occurs.
*
* @return @c size when succesful or @c SOCKET_ERROR if an error occurred.
*/
ssize_t writeAll(Socket* sock, const void* buf, size_t size)
{
size_t written = 0;
if (!sock
|| sock->fd[SOCK_CONNECTION] == INVALID_SOCKET)
{
debug(LOG_ERROR, "Invalid socket (EBADF)");
setSockErr(EBADF);
return SOCKET_ERROR;
}
while (written < size)
{
ssize_t ret;
ret = send(sock->fd[SOCK_CONNECTION], &((char*)buf)[written], size - written, MSG_NOSIGNAL);
if (ret == SOCKET_ERROR)
{
switch (getSockErr())
{
case EAGAIN:
#if defined(EWOULDBLOCK) && EAGAIN != EWOULDBLOCK
case EWOULDBLOCK:
#endif
if (!connectionIsOpen(sock))
{
debug(LOG_NET, "Socket error");
return SOCKET_ERROR;
}
case EINTR:
continue;
#if defined(EPIPE)
case EPIPE:
debug(LOG_NET, "EPIPE generated");
// fall through
#endif
default:
return SOCKET_ERROR;
}
}
written += ret;
}
return written;
}
SocketSet *allocSocketSet()
{
return new SocketSet;
}
void deleteSocketSet(SocketSet *set)
{
delete set;
}
/**
* Add the given socket to the given socket set.
*
* @return true if @c socket is succesfully added to @set.
*/
void SocketSet_AddSocket(SocketSet* set, Socket* socket)
{
ASSERT_OR_RETURN(, set != NULL, "NULL SocketSet provided");
ASSERT_OR_RETURN(, socket != NULL, "NULL Socket provided");
/* Check whether this socket is already present in this set (i.e. it
* shouldn't be added again).
*/
size_t i = std::find(set->fds.begin(), set->fds.end(), socket) - set->fds.begin();
if (i != set->fds.size())
{
debug(LOG_NET, "Already found, socket: (set->fds[%lu]) %p", (unsigned long)i, socket);
return;
}
set->fds.push_back(socket);
debug(LOG_NET, "Socket added: set->fds[%lu] = %p", (unsigned long)i, socket);
}
/**
* Remove the given socket from the given socket set.
*/
void SocketSet_DelSocket(SocketSet* set, Socket* socket)
{
ASSERT_OR_RETURN(, set != NULL, "NULL SocketSet provided");
ASSERT_OR_RETURN(, socket != NULL, "NULL Socket provided");
size_t i = std::find(set->fds.begin(), set->fds.end(), socket) - set->fds.begin();
if (i != set->fds.size())
{
debug(LOG_NET, "Socket %p erased (set->fds[%lu])", socket, (unsigned long)i);
set->fds.erase(set->fds.begin() + i);
}
}
static bool setSocketBlocking(const SOCKET fd, bool blocking)
{
#if defined(WZ_OS_UNIX)
int sockopts = fcntl(fd, F_GETFL);
if (sockopts == SOCKET_ERROR)
{
debug(LOG_NET, "Failed to retrieve current socket options: %s", strSockError(getSockErr()));
return false;
}
// Set or clear O_NONBLOCK flag
if (blocking)
sockopts &= ~O_NONBLOCK;
else
sockopts |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, sockopts) == SOCKET_ERROR)
#elif defined(WZ_OS_WIN)
unsigned long nonblocking = !blocking;
if (ioctlsocket(fd, FIONBIO, &nonblocking) == SOCKET_ERROR)
#endif
{
debug(LOG_NET, "Failed to set socket %sblocking: %s", (blocking ? "" : "non-"), strSockError(getSockErr()));
return false;
}
debug(LOG_NET, "Socket is set to %sblocking.", (blocking ? "" : "non-"));
return true;
}
static void socketBlockSIGPIPE(const SOCKET fd, bool block_sigpipe)
{
#if defined(SO_NOSIGPIPE)
const int no_sigpipe = block_sigpipe ? 1 : 0;
if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof(no_sigpipe)) == SOCKET_ERROR)
{
debug(LOG_INFO, "Failed to set SO_NOSIGPIPE on socket, SIGPIPE might be raised when connections gets broken. Error: %s", strSockError(getSockErr()));
}
// this is only for unix, windows don't have SIGPIPE
debug(LOG_NET, "Socket fd %x sets SIGPIPE to %sblocked.", fd, (block_sigpipe ? "" : "non-"));
#else
// Prevent warnings
(void)fd;
(void)block_sigpipe;
#endif
}
int checkSockets(const SocketSet* set, unsigned int timeout)
{
if (set->fds.empty())
{
return 0;
}
int ret;
fd_set fds;
#if defined(WZ_OS_UNIX)
SOCKET maxfd = INT_MIN;
#elif defined(WZ_OS_WIN)
SOCKET maxfd = 0;
#endif
for (size_t i = 0; i < set->fds.size(); ++i)
{
ASSERT(set->fds[i]->fd[SOCK_CONNECTION] != INVALID_SOCKET, "Invalid file descriptor!");
maxfd = std::max(maxfd, set->fds[i]->fd[SOCK_CONNECTION]);
}
do
{
struct timeval tv = { timeout / 1000, (timeout % 1000) * 1000 };
FD_ZERO(&fds);
for (size_t i = 0; i < set->fds.size(); ++i)
{
const SOCKET fd = set->fds[i]->fd[SOCK_CONNECTION];
FD_SET(fd, &fds);
}
ret = select(maxfd + 1, &fds, NULL, NULL, &tv);
} while (ret == SOCKET_ERROR && getSockErr() == EINTR);
if (ret == SOCKET_ERROR)
{
debug(LOG_ERROR, "select failed: %s", strSockError(getSockErr()));
return SOCKET_ERROR;
}
for (size_t i = 0; i < set->fds.size(); ++i)
{
set->fds[i]->ready = FD_ISSET(set->fds[i]->fd[SOCK_CONNECTION], &fds);
}
return ret;
}
/**
* Similar to read(2) with the exception that this function won't be
* interrupted by signals (EINTR) and will only return when <em>exactly</em>
* @c size bytes have been received. I.e. this function blocks until all data
* has been received or a timeout occurred.
*
* @param timeout When non-zero this function times out after @c timeout
* milliseconds. When zero this function blocks until success or
* an error occurs.
*
* @c return @c size when succesful, less than @c size but at least zero (0)
* when the other end disconnected or a timeout occurred. Or @c SOCKET_ERROR if
* an error occurred.
*/
ssize_t readAll(Socket* sock, void* buf, size_t size, unsigned int timeout)
{
const SocketSet set = {std::vector<Socket *>(1, sock)};
size_t received = 0;
if (!sock
|| sock->fd[SOCK_CONNECTION] == INVALID_SOCKET)
{
debug(LOG_ERROR, "Invalid socket (%p), sock->fd[SOCK_CONNECTION]=%x (error: EBADF)", sock, sock->fd[SOCK_CONNECTION]);
setSockErr(EBADF);
return SOCKET_ERROR;
}
while (received < size)
{
ssize_t ret;
// If a timeout is set, wait for that amount of time for data to arrive (or abort)
if (timeout)
{
ret = checkSockets(&set, timeout);
if (ret < (ssize_t)set.fds.size()
|| !sock->ready)
{
if (ret == 0)
{
debug(LOG_NET, "socket (%p) has timed out.", socket);
setSockErr(ETIMEDOUT);
}
debug(LOG_NET, "socket (%p) error.", socket);
return SOCKET_ERROR;
}
}
ret = recv(sock->fd[SOCK_CONNECTION], &((char*)buf)[received], size - received, 0);
sock->ready = false;
if (ret == 0)
{
debug(LOG_NET, "Socket %x disconnected.", sock->fd[SOCK_CONNECTION]);
setSockErr(ECONNRESET);
return received;
}
if (ret == SOCKET_ERROR)
{
switch (getSockErr())
{
case EAGAIN:
#if defined(EWOULDBLOCK) && EAGAIN != EWOULDBLOCK
case EWOULDBLOCK:
#endif
case EINTR:
continue;
default:
return SOCKET_ERROR;
}
}
received += ret;
}
return received;
}
void socketClose(Socket* sock)
{
unsigned int i;
int err = 0;
if (sock)
{
for (i = 0; i < ARRAY_SIZE(sock->fd); ++i)
{
if (sock->fd[i] != INVALID_SOCKET)
{
#if defined(WZ_OS_WIN)
err = closesocket(sock->fd[i]);
#else
err = close(sock->fd[i]);
#endif
if (err)
{
debug(LOG_ERROR, "Failed to close socket %p: %s", sock, strSockError(getSockErr()));
}
/* Make sure that dangling pointers to this
* structure don't think they've got their
* hands on a valid socket.
*/
sock->fd[i] = INVALID_SOCKET;
}
}
free(sock);
sock = NULL;
}
}
Socket *socketAccept(Socket *sock)
{
unsigned int i;
ASSERT(sock != NULL, "NULL Socket provided");
/* Search for a socket that has a pending connection on it and accept
* the first one.
*/
for (i = 0; i < ARRAY_SIZE(sock->fd); ++i)
{
if (sock->fd[i] != INVALID_SOCKET)
{
struct sockaddr_storage addr;
socklen_t addr_len = sizeof(addr);
Socket *conn;
unsigned int j;
const SOCKET newConn = accept(sock->fd[i], (struct sockaddr*)&addr, &addr_len);
if (newConn == INVALID_SOCKET)
{
// Ignore the case where no connection is pending
if (getSockErr() != EAGAIN
&& getSockErr() != EWOULDBLOCK)
{
debug(LOG_ERROR, "accept failed for socket %p: %s", sock, strSockError(getSockErr()));
}
continue;
}
socketBlockSIGPIPE(newConn, true);
conn = (Socket *)malloc(sizeof(*conn));
if (conn == NULL)
{
debug(LOG_ERROR, "Out of memory!");
abort();
return NULL;
}
// Mark all unused socket handles as invalid
for (j = 0; j < ARRAY_SIZE(conn->fd); ++j)
{
conn->fd[j] = INVALID_SOCKET;
}
conn->ready = false;
conn->fd[SOCK_CONNECTION] = newConn;
sock->ready = false;
addressToText((const struct sockaddr*)&addr, conn->textAddress, sizeof(conn->textAddress));
debug(LOG_NET, "Incoming connection from [%s]:%d", conn->textAddress, (unsigned int)ntohs(((const struct sockaddr_in*)&addr)->sin_port));
debug(LOG_NET, "Using socket %p", conn);
return conn;
}
}
return NULL;
}
Socket *socketOpen(const SocketAddress *addr, unsigned timeout)
{
unsigned int i;
int ret;
Socket *const conn = (Socket *)malloc(sizeof(*conn));
if (conn == NULL)
{
debug(LOG_ERROR, "Out of memory!");
abort();
return NULL;
}
ASSERT(addr != NULL, "NULL Socket provided");
addressToText(addr->ai_addr, conn->textAddress, sizeof(conn->textAddress));
debug(LOG_NET, "Connecting to [%s]:%d", conn->textAddress, (int)ntohs(((const struct sockaddr_in*)addr->ai_addr)->sin_port));
// Mark all unused socket handles as invalid
for (i = 0; i < ARRAY_SIZE(conn->fd); ++i)
{
conn->fd[i] = INVALID_SOCKET;
}
conn->ready = false;
conn->fd[SOCK_CONNECTION] = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (conn->fd[SOCK_CONNECTION] == INVALID_SOCKET)
{
debug(LOG_ERROR, "Failed to create a socket (%p): %s", conn, strSockError(getSockErr()));
socketClose(conn);
return NULL;
}
debug(LOG_NET, "setting socket (%p) blocking status (false).", conn);
if (!setSocketBlocking(conn->fd[SOCK_CONNECTION], false))
{
debug(LOG_NET, "Couldn't set socket (%p) blocking status (false). Closing.", conn);
socketClose(conn);
return NULL;
}
socketBlockSIGPIPE(conn->fd[SOCK_CONNECTION], true);
ret = connect(conn->fd[SOCK_CONNECTION], addr->ai_addr, addr->ai_addrlen);
if (ret == SOCKET_ERROR)
{
fd_set conReady;
#if defined(WZ_OS_WIN)
fd_set conFailed;
#endif
if ((getSockErr() != EINPROGRESS
&& getSockErr() != EAGAIN
&& getSockErr() != EWOULDBLOCK)
#if defined(WZ_OS_UNIX)
|| conn->fd[SOCK_CONNECTION] >= FD_SETSIZE
#endif
|| timeout == 0)
{
debug(LOG_NET, "Failed to start connecting: %s, using socket %p", strSockError(getSockErr()), conn);
socketClose(conn);
return NULL;
}
do
{
struct timeval tv = { timeout / 1000, (timeout % 1000) * 1000 };
FD_ZERO(&conReady);
FD_SET(conn->fd[SOCK_CONNECTION], &conReady);
#if defined(WZ_OS_WIN)
FD_ZERO(&conFailed);
FD_SET(conn->fd[SOCK_CONNECTION], &conFailed);
#endif
#if defined(WZ_OS_WIN)
ret = select(conn->fd[SOCK_CONNECTION] + 1, NULL, &conReady, &conFailed, &tv);
#else
ret = select(conn->fd[SOCK_CONNECTION] + 1, NULL, &conReady, NULL, &tv);
#endif
} while (ret == SOCKET_ERROR && getSockErr() == EINTR);
if (ret == SOCKET_ERROR)
{
debug(LOG_NET, "Failed to wait for connection: %s, socket %p. Closing.", strSockError(getSockErr()), conn);
socketClose(conn);
return NULL;
}
if (ret == 0)
{
setSockErr(ETIMEDOUT);
debug(LOG_NET, "Timed out while waiting for connection to be established: %s, using socket %p. Closing.", strSockError(getSockErr()), conn);
socketClose(conn);
return NULL;
}
#if defined(WZ_OS_WIN)
ASSERT(FD_ISSET(conn->fd[SOCK_CONNECTION], &conReady) || FD_ISSET(conn->fd[SOCK_CONNECTION], &conFailed), "\"sock\" is the only file descriptor in set, it should be the one that is set.");
#else
ASSERT(FD_ISSET(conn->fd[SOCK_CONNECTION], &conReady), "\"sock\" is the only file descriptor in set, it should be the one that is set.");
#endif
#if defined(WZ_OS_WIN)
if (FD_ISSET(conn->fd[SOCK_CONNECTION], &conFailed))
#elif defined(WZ_OS_UNIX)
if (connect(conn->fd[SOCK_CONNECTION], addr->ai_addr, addr->ai_addrlen) == SOCKET_ERROR
&& getSockErr() != EISCONN)
#endif
{
debug(LOG_NET, "Failed to connect: %s, with socket %p. Closing.", strSockError(getSockErr()), conn);
socketClose(conn);
return NULL;
}
}
debug(LOG_NET, "setting socket (%p) blocking status (true).", conn);
if (!setSocketBlocking(conn->fd[SOCK_CONNECTION], true))
{
debug(LOG_NET, "Failed to set socket %p blocking status (true). Closing.", conn);
socketClose(conn);
return NULL;
}
return conn;
}
Socket *socketListen(unsigned int port)
{
/* Enable the V4 to V6 mapping, but only when available, because it
* isn't available on all platforms.
*/
#if defined(IPV6_V6ONLY)
static const int ipv6_v6only = 0;
#endif
static const int so_reuseaddr = 1;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
unsigned int i;
Socket *const conn = (Socket *)malloc(sizeof(*conn));
if (conn == NULL)
{
debug(LOG_ERROR, "Out of memory!");
abort();
return NULL;
}
// Mark all unused socket handles as invalid
for (i = 0; i < ARRAY_SIZE(conn->fd); ++i)
{
conn->fd[i] = INVALID_SOCKET;
}
strncpy(conn->textAddress, "LISTENING SOCKET", sizeof(conn->textAddress));
// Listen on all local IPv4 and IPv6 addresses for the given port
addr4.sin_family = AF_INET;
addr4.sin_port = htons(port);
addr4.sin_addr.s_addr = INADDR_ANY;
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(port);
addr6.sin6_addr = in6addr_any;
addr6.sin6_flowinfo = 0;
addr6.sin6_scope_id = 0;
conn->ready = false;
conn->fd[SOCK_IPV4_LISTEN] = socket(addr4.sin_family, SOCK_STREAM, 0);
conn->fd[SOCK_IPV6_LISTEN] = socket(addr6.sin6_family, SOCK_STREAM, 0);
if (conn->fd[SOCK_IPV4_LISTEN] == INVALID_SOCKET
&& conn->fd[SOCK_IPV6_LISTEN] == INVALID_SOCKET)
{
debug(LOG_ERROR, "Failed to create an IPv4 and IPv6 (only supported address families) socket (%p): %s. Closing.", conn, strSockError(getSockErr()));
socketClose(conn);
return NULL;
}
if (conn->fd[SOCK_IPV4_LISTEN] != INVALID_SOCKET)
{
debug(LOG_NET, "Successfully created an IPv4 socket (%p)", conn);
}
if (conn->fd[SOCK_IPV6_LISTEN] != INVALID_SOCKET)
{
debug(LOG_NET, "Successfully created an IPv6 socket (%p)", conn);
}
#if defined(IPV6_V6ONLY)
if (conn->fd[SOCK_IPV6_LISTEN] != INVALID_SOCKET)
{
if (setsockopt(conn->fd[SOCK_IPV6_LISTEN], IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_v6only, sizeof(ipv6_v6only)) == SOCKET_ERROR)
{
debug(LOG_INFO, "Failed to set IPv6 socket to perform IPv4 to IPv6 mapping. Falling back to using two sockets. Error: %s", strSockError(getSockErr()));
}
else
{
debug(LOG_NET, "Successfully enabled IPv4 to IPv6 mapping. Cleaning up IPv4 socket.");
#if defined(WZ_OS_WIN)
closesocket(conn->fd[SOCK_IPV4_LISTEN]);
#else
close(conn->fd[SOCK_IPV4_LISTEN]);
#endif
conn->fd[SOCK_IPV4_LISTEN] = INVALID_SOCKET;
}
}
#endif
if (conn->fd[SOCK_IPV4_LISTEN] != INVALID_SOCKET)
{
if (setsockopt(conn->fd[SOCK_IPV4_LISTEN], SOL_SOCKET, SO_REUSEADDR, &so_reuseaddr, sizeof(so_reuseaddr)) == SOCKET_ERROR)
{
debug(LOG_WARNING, "Failed to set SO_REUSEADDR on IPv4 socket. Error: %s", strSockError(getSockErr()));
}
debug(LOG_NET, "setting socket (%p) blocking status (false, IPv4).", conn);
if (bind(conn->fd[SOCK_IPV4_LISTEN], (const struct sockaddr*)&addr4, sizeof(addr4)) == SOCKET_ERROR
|| listen(conn->fd[SOCK_IPV4_LISTEN], 5) == SOCKET_ERROR
|| !setSocketBlocking(conn->fd[SOCK_IPV4_LISTEN], false))
{
debug(LOG_ERROR, "Failed to set up IPv4 socket for listening on port %u: %s", port, strSockError(getSockErr()));
#if defined(WZ_OS_WIN)
closesocket(conn->fd[SOCK_IPV4_LISTEN]);
#else
close(conn->fd[SOCK_IPV4_LISTEN]);
#endif
conn->fd[SOCK_IPV4_LISTEN] = INVALID_SOCKET;
}
}
if (conn->fd[SOCK_IPV6_LISTEN] != INVALID_SOCKET)
{
if (setsockopt(conn->fd[SOCK_IPV6_LISTEN], SOL_SOCKET, SO_REUSEADDR, &so_reuseaddr, sizeof(so_reuseaddr)) == SOCKET_ERROR)
{
debug(LOG_INFO, "Failed to set SO_REUSEADDR on IPv6 socket. Error: %s", strSockError(getSockErr()));
}
debug(LOG_NET, "setting socket (%p) blocking status (false, IPv6).", conn);
if (bind(conn->fd[SOCK_IPV6_LISTEN], (const struct sockaddr*)&addr6, sizeof(addr6)) == SOCKET_ERROR
|| listen(conn->fd[SOCK_IPV6_LISTEN], 5) == SOCKET_ERROR
|| !setSocketBlocking(conn->fd[SOCK_IPV6_LISTEN], false))
{
debug(LOG_ERROR, "Failed to set up IPv6 socket for listening on port %u: %s", port, strSockError(getSockErr()));
#if defined(WZ_OS_WIN)
closesocket(conn->fd[SOCK_IPV6_LISTEN]);
#else
close(conn->fd[SOCK_IPV6_LISTEN]);
#endif
conn->fd[SOCK_IPV6_LISTEN] = INVALID_SOCKET;
}
}
// Check whether we still have at least a single (operating) socket.
if (conn->fd[SOCK_IPV4_LISTEN] == INVALID_SOCKET
&& conn->fd[SOCK_IPV6_LISTEN] == INVALID_SOCKET)
{
debug(LOG_NET, "No IPv4 or IPv6 sockets created.");
socketClose(conn);
return NULL;
}
return conn;
}
Socket *socketOpenAny(const SocketAddress *addr, unsigned timeout)
{
Socket *ret = NULL;
while (addr != NULL && ret == NULL)
{
ret = socketOpen(addr, timeout);
addr = addr->ai_next;
}
return ret;
}
size_t socketArrayOpen(Socket **sockets, size_t maxSockets, const SocketAddress *addr, unsigned timeout)
{
size_t i = 0;
while (i < maxSockets && addr != NULL)
{
if (addr->ai_family == AF_INET || addr->ai_family == AF_INET6)
{
sockets[i] = socketOpen(addr, timeout);
i += sockets[i] != NULL;
}
addr = addr->ai_next;
}
std::fill(sockets + i, sockets + maxSockets, (Socket *)NULL);
return i;
}
void socketArrayClose(Socket **sockets, size_t maxSockets)
{
std::for_each(sockets, sockets + maxSockets, socketClose); // Close any open sockets.
std::fill (sockets, sockets + maxSockets, (Socket *)NULL); // Set the pointers to NULL.
}
char const *getSocketTextAddress(Socket const *sock)
{
return sock->textAddress;
}
SocketAddress *resolveHost(const char *host, unsigned int port)
{
struct addrinfo* results;
char* service;
struct addrinfo hint;
int error, flags = 0;
hint.ai_family = AF_UNSPEC;
hint.ai_socktype = SOCK_STREAM;
hint.ai_protocol = 0;
#ifdef AI_V4MAPPED
flags |= AI_V4MAPPED;
#endif
#ifdef AI_ADDRCONFIG
flags |= AI_ADDRCONFIG;
#endif
hint.ai_flags = flags;
hint.ai_addrlen = 0;
hint.ai_addr = NULL;
hint.ai_canonname = NULL;
hint.ai_next = NULL;
sasprintf(&service, "%u", port);
error = getaddrinfo(host, service, &hint, &results);
if (error != 0)
{
debug(LOG_NET, "getaddrinfo failed for %s:%s: %s", host, service, gai_strerror(error));
return NULL;
}
return results;
}
void deleteSocketAddress(SocketAddress *addr)
{
freeaddrinfo(addr);
}
// ////////////////////////////////////////////////////////////////////////
// setup stuff
void SOCKETinit()
{
#if defined(WZ_OS_WIN)
static bool firstCall = true;
if (firstCall)
{
firstCall = false;
static WSADATA stuff;
WORD ver_required = (2 << 8) + 2;
if (WSAStartup(ver_required, &stuff) != 0)
{
debug(LOG_ERROR, "Failed to initialize Winsock: %s", strSockError(getSockErr()));
return -1;
}
winsock2_dll = LoadLibraryA("ws2_32.dll");
if (winsock2_dll)
{
getaddrinfo_dll_func = GetProcAddress(winsock2_dll, "getaddrinfo");
freeaddrinfo_dll_func = GetProcAddress(winsock2_dll, "freeaddrinfo");
}
// Determine major Windows version
major_windows_version = LOBYTE(LOWORD(GetVersion()));
}
#endif
}
void SOCKETshutdown()
{
#if defined(WZ_OS_WIN)
WSACleanup();
if (winsock2_dll)
{
FreeLibrary(winsock2_dll);
winsock2_dll = NULL;
getaddrinfo_dll_func = NULL;
freeaddrinfo_dll_func = NULL;
}
#endif
}