1167 lines
31 KiB
C++
1167 lines
31 KiB
C++
|
/*
|
||
|
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
|
||
|
}
|