diff options
Diffstat (limited to 'src/libs/network')
34 files changed, 4050 insertions, 0 deletions
diff --git a/src/libs/network/FILES b/src/libs/network/FILES new file mode 100644 index 0000000..ddcd685 --- /dev/null +++ b/src/libs/network/FILES @@ -0,0 +1,26 @@ +In libs/network/: +netport.{c,h} Portability definitions. +bytesex.h Functions for endianness conversions. +network.h Functions for initialising the network subsystem. +network_bsd.c Network subsystem functions for BSD sockets. +network_win.c Network subsystem functions for Winsock sockets. +wspiapiwrap.{c,h} Hack to make sure <wspiapi.h> is #included in exactly + one file. + +In libs/network/netmanager/: +ndesc.{c,h} Defines network descriptors. +netmanager.h Handles callbacks for network activity. +netmanager_bsd.{c,h} NetManager for systems with BSD sockets. +netmanager_win.{c,h} NetManager for Winsock systems. + +In libs/network/socket/: +socket.{c,h} Platform-independant socket layer. +socket_bsd.{c,h} Socket operations for BSD sockets. +socket_win.{c,h} Socket operations for Winsock sockets. + +In libs/network/connect/: +connect.{c,h} Routines for establishing outgoing connections. +listen.{c,h} Routines for receiving incoming connections. +resolve.{c,h} Routines for hostname lookups. + + diff --git a/src/libs/network/Makeinfo b/src/libs/network/Makeinfo new file mode 100644 index 0000000..40171fa --- /dev/null +++ b/src/libs/network/Makeinfo @@ -0,0 +1,14 @@ +uqm_SUBDIRS="connect netmanager socket" +uqm_CFILES="netport.c" +uqm_HFILES="bytesex.h netport.h network.h" + +if [ -n "$uqm_USE_WINSOCK" ]; then + uqm_CFILES="$uqm_CFILES network_win.c" + if [ -n "$MACRO___MINGW32__" ]; then + uqm_CFILES="$uqm_CFILES wspiapiwrap.c" + uqm_HFILES="$uqm_HFILES wspiapiwrap.h" + fi +else + uqm_CFILES="$uqm_CFILES network_bsd.c" +fi + diff --git a/src/libs/network/bytesex.h b/src/libs/network/bytesex.h new file mode 100644 index 0000000..4ee89d7 --- /dev/null +++ b/src/libs/network/bytesex.h @@ -0,0 +1,96 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Routines for changing the endianness of values. +// I'm not using ntohs() etc. as those would require include files that may +// have conflicting definitions. This is a problem on Windows, where these +// functions are in winsock2.h, which includes windows.h, which includes +// pretty much Microsoft's complete collection of .h files. + +#ifndef LIBS_NETWORK_BYTESEX_H_ +#define LIBS_NETWORK_BYTESEX_H_ + +#include "port.h" + // for inline +#include "endian_uqm.h" + // for WORDS_BIGENDIAN +#include "types.h" + +static inline uint16 +swapBytes16(uint16 x) { + return (x << 8) | (x >> 8); +} + +static inline uint32 +swapBytes32(uint32 x) { + return (x << 24) + | ((x & 0x0000ff00) << 8) + | ((x & 0x00ff0000) >> 8) + | (x >> 24); +} + +#ifdef WORDS_BIGENDIAN +// Already in network order. + +static inline uint16 +hton16(uint16 x) { + return x; +} + +static inline uint32 +hton32(uint32 x) { + return x; +} + +static inline uint16 +ntoh16(uint16 x) { + return x; +} + +static inline uint32 +ntoh32(uint32 x) { + return x; +} + +#else /* !defined(WORDS_BIGENDIAN) */ +// Need to swap bytes + +static inline uint16 +hton16(uint16 x) { + return swapBytes16(x); +} + +static inline uint32 +hton32(uint32 x) { + return swapBytes32(x); +} + +static inline uint16 +ntoh16(uint16 x) { + return swapBytes16(x); +} + +static inline uint32 +ntoh32(uint32 x) { + return swapBytes32(x); +} + +#endif /* defined(WORDS_BIGENDIAN) */ + +#endif /* LIBS_NETWORK_BYTESEX_H_ */ + diff --git a/src/libs/network/connect/Makeinfo b/src/libs/network/connect/Makeinfo new file mode 100644 index 0000000..5595270 --- /dev/null +++ b/src/libs/network/connect/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="connect.c listen.c resolve.c" +uqm_HFILES="connect.h listen.h resolve.h" diff --git a/src/libs/network/connect/connect.c b/src/libs/network/connect/connect.c new file mode 100644 index 0000000..4599b18 --- /dev/null +++ b/src/libs/network/connect/connect.c @@ -0,0 +1,490 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" + +#define CONNECT_INTERNAL +#define SOCKET_INTERNAL +#include "connect.h" + +#include "resolve.h" +#include "libs/alarm.h" +#include "../socket/socket.h" +#include "libs/misc.h" +#include "libs/log.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#ifdef USE_WINSOCK +# include <winsock2.h> +# include <ws2tcpip.h> +# include "../wspiapiwrap.h" +#else +# include <netdb.h> +#endif + +#define DEBUG_CONNECT_REF +#ifdef DEBUG_CONNECT_REF +# include "types.h" +#endif + + +static void connectHostNext(ConnectState *connectState); +static void doConnectCallback(ConnectState *connectState, NetDescriptor *nd, + const struct sockaddr *addr, socklen_t addrLen); +static void doConnectErrorCallback(ConnectState *connectState, + const ConnectError *error); + + +static ConnectState * +ConnectState_alloc(void) { + return (ConnectState *) malloc(sizeof (ConnectState)); +} + +static void +ConnectState_free(ConnectState *connectState) { + free(connectState); +} + +static void +ConnectState_delete(ConnectState *connectState) { + assert(connectState->nd == NULL); + assert(connectState->alarm == NULL); + assert(connectState->info == NULL); + assert(connectState->infoPtr == NULL); + ConnectState_free(connectState); +} + +void +ConnectState_incRef(ConnectState *connectState) { + assert(connectState->refCount < REFCOUNT_MAX); + connectState->refCount++; +#ifdef DEBUG_CONNECT_REF + log_add(log_Debug, "ConnectState %08" PRIxPTR ": ref++ (%d)", + (uintptr_t) connectState, connectState->refCount); +#endif +} + +bool +ConnectState_decRef(ConnectState *connectState) { + assert(connectState->refCount > 0); + connectState->refCount--; +#ifdef DEBUG_CONNECT_REF + log_add(log_Debug, "ConnectState %08" PRIxPTR ": ref-- (%d)", + (uintptr_t) connectState, connectState->refCount); +#endif + if (connectState->refCount == 0) { + ConnectState_delete(connectState); + return true; + } + return false; +} + +// decrements ref count by 1 +void +ConnectState_close(ConnectState *connectState) { + if (connectState->resolveState != NULL) { + Resolve_close(connectState->resolveState); + connectState->resolveState = NULL; + } + if (connectState->alarm != NULL) { + Alarm_remove(connectState->alarm); + connectState->alarm = NULL; + } + if (connectState->nd != NULL) { + NetDescriptor_close(connectState->nd); + connectState->nd = NULL; + } + if (connectState->info != NULL) { + freeaddrinfo(connectState->info); + connectState->info = NULL; + connectState->infoPtr = NULL; + } + connectState->state = Connect_closed; + ConnectState_decRef(connectState); +} + +void +ConnectState_setExtra(ConnectState *connectState, void *extra) { + connectState->extra = extra; +} + +void * +ConnectState_getExtra(ConnectState *connectState) { + return connectState->extra; +} + +static void +connectCallback(NetDescriptor *nd) { + // Called by the NetManager when a connection has been established. + ConnectState *connectState = + (ConnectState *) NetDescriptor_getExtra(nd); + int err; + + if (connectState->alarm != NULL) { + Alarm_remove(connectState->alarm); + connectState->alarm = NULL; + } + + if (connectState->state == Connect_closed) { + // The connection attempt has been aborted. +#ifdef DEBUG + log_add(log_Debug, "Connection attempt was aborted."); +#endif + ConnectState_decRef(connectState); + return; + } + + if (Socket_getError(NetDescriptor_getSocket(nd), &err) == -1) { + log_add(log_Fatal, "Socket_getError() failed: %s.", + strerror(errno)); + explode(); + } + if (err != 0) { +#ifdef DEBUG + log_add(log_Debug, "connect() failed: %s.", strerror(err)); +#endif + NetDescriptor_close(nd); + connectState->nd = NULL; + connectState->infoPtr = connectState->infoPtr->ai_next; + connectHostNext(connectState); + return; + } + +#ifdef DEBUG + log_add(log_Debug, "Connection established."); +#endif + + // Notify the higher layer. + connectState->nd = NULL; + // The callback function takes over ownership of the + // NetDescriptor. + NetDescriptor_setWriteCallback(nd, NULL); + // Note that connectState->info and connectState->infoPtr are cleaned up + // when ConnectState_close() is called by the callback function. + + ConnectState_incRef(connectState); + doConnectCallback(connectState, nd, connectState->infoPtr->ai_addr, + connectState->infoPtr->ai_addrlen); + { + // The callback called should release the last reference to + // the connectState, by calling ConnectState_close(). + bool released = ConnectState_decRef(connectState); + assert(released); + (void) released; // In case assert() evaluates to nothing. + } +} + +static void +connectTimeoutCallback(ConnectState *connectState) { + connectState->alarm = NULL; + + NetDescriptor_close(connectState->nd); + connectState->nd = NULL; + + connectState->infoPtr = connectState->infoPtr->ai_next; + connectHostNext(connectState); +} + +static void +setConnectTimeout(ConnectState *connectState) { + assert(connectState->alarm == NULL); + + connectState->alarm = + Alarm_addRelativeMs(connectState->flags.timeout, + (AlarmCallback) connectTimeoutCallback, connectState); +} + +// Try connecting to the next address. +static Socket * +tryConnectHostNext(ConnectState *connectState) { + struct addrinfo *info; + Socket *sock; + int connectResult; + + assert(connectState->nd == NULL); + + info = connectState->infoPtr; + + sock = Socket_openNative(info->ai_family, info->ai_socktype, + info->ai_protocol); + if (sock == Socket_noSocket) { + int savedErrno = errno; + log_add(log_Error, "socket() failed: %s.", strerror(errno)); + errno = savedErrno; + return Socket_noSocket; + } + + if (Socket_setNonBlocking(sock) == -1) { + int savedErrno = errno; + log_add(log_Error, "Could not make socket non-blocking: %s.", + strerror(errno)); + errno = savedErrno; + return Socket_noSocket; + } + + (void) Socket_setReuseAddr(sock); + // Ignore errors; it's not a big deal. + (void) Socket_setInlineOOB(sock); + // Ignore errors; it's not a big deal as the other party is not + // not supposed to send any OOB data. + (void) Socket_setKeepAlive(sock); + // Ignore errors; it's not a big deal. + + connectResult = Socket_connect(sock, info->ai_addr, info->ai_addrlen); + if (connectResult == 0) { + // Connection has already succeeded. + // We just wait for the writability callback anyhow, so that + // we can use one code path. + return sock; + } + + switch (errno) { + case EINPROGRESS: + // Connection in progress; wait for the write callback. + return sock; + } + + // Connection failed immediately. This is just for one of the addresses, + // so this does not have to be final. + // Note that as the socket is non-blocking, most failed connection + // errors will usually not be reported immediately. + { + int savedErrno = errno; + Socket_close(sock); +#ifdef DEBUG + log_add(log_Debug, "connect() immediately failed for one address: " + "%s.", strerror(errno)); + // TODO: add the address in the status message. +#endif + errno = savedErrno; + } + return Socket_noSocket; +} + +static void +connectRetryCallback(ConnectState *connectState) { + connectState->alarm = NULL; + + connectState->infoPtr = connectState->info; + connectHostNext(connectState); +} + +static void +setConnectRetryAlarm(ConnectState *connectState) { + assert(connectState->alarm == NULL); + assert(connectState->flags.retryDelayMs != Connect_noRetry); + + connectState->alarm = + Alarm_addRelativeMs(connectState->flags.retryDelayMs, + (AlarmCallback) connectRetryCallback, connectState); +} + +static void +connectHostReportAllFailed(ConnectState *connectState) { + // Could not connect to any host. + ConnectError error; + freeaddrinfo(connectState->info); + connectState->info = NULL; + connectState->infoPtr = NULL; + connectState->state = Connect_closed; + error.state = Connect_connecting; + error.err = ETIMEDOUT; + // No errno code is exactly suitable. We have been unable + // to connect to any host, but the reasons may vary + // (unreachable, refused, ...). + // ETIMEDOUT is the least specific portable errno code that + // seems appropriate. + doConnectErrorCallback(connectState, &error); +} + +static void +connectHostNext(ConnectState *connectState) { + Socket *sock; + + while (connectState->infoPtr != NULL) { + sock = tryConnectHostNext(connectState); + + if (sock != Socket_noSocket) { + // Connection succeeded or connection in progress + connectState->nd = + NetDescriptor_new(sock, (void *) connectState); + if (connectState->nd == NULL) { + ConnectError error; + int savedErrno = errno; + + log_add(log_Error, "NetDescriptor_new() failed: %s.", + strerror(errno)); + Socket_close(sock); + freeaddrinfo(connectState->info); + connectState->info = NULL; + connectState->infoPtr = NULL; + connectState->state = Connect_closed; + error.state = Connect_connecting; + error.err = savedErrno; + doConnectErrorCallback(connectState, &error); + return; + } + + NetDescriptor_setWriteCallback(connectState->nd, connectCallback); + setConnectTimeout(connectState); + return; + } + + connectState->infoPtr = connectState->infoPtr->ai_next; + } + + // Connect failed to all addresses. + + if (connectState->flags.retryDelayMs == Connect_noRetry) { + connectHostReportAllFailed(connectState); + return; + } + + setConnectRetryAlarm(connectState); +} + +static void +connectHostResolveCallback(ResolveState *resolveState, + struct addrinfo *info) { + ConnectState *connectState = + (ConnectState *) ResolveState_getExtra(resolveState); + + connectState->state = Connect_connecting; + + Resolve_close(resolveState); + connectState->resolveState = NULL; + + if (connectState->flags.familyPrefer != PF_UNSPEC) { + // Reorganise the 'info' list to put the structures of the + // prefered family in front. + struct addrinfo *preferred; + struct addrinfo **preferredEnd; + struct addrinfo *rest; + struct addrinfo **restEnd; + splitAddrInfoOnFamily(info, connectState->flags.familyPrefer, + &preferred, &preferredEnd, &rest, &restEnd); + info = preferred; + *preferredEnd = rest; + } + + connectState->info = info; + connectState->infoPtr = info; + + connectHostNext(connectState); +} + +static void +connectHostResolveErrorCallback(ResolveState *resolveState, + const ResolveError *resolveError) { + ConnectState *connectState = + (ConnectState *) ResolveState_getExtra(resolveState); + ConnectError connectError; + + assert(resolveError->gaiRes != 0); + + Resolve_close(resolveState); + connectState->resolveState = NULL; + + connectError.state = Connect_resolving; + connectError.resolveError = resolveError; + connectError.err = resolveError->err; + doConnectErrorCallback(connectState, &connectError); +} + +ConnectState * +connectHostByName(const char *host, const char *service, Protocol proto, + const ConnectFlags *flags, ConnectConnectCallback connectCallback, + ConnectErrorCallback errorCallback, void *extra) { + struct addrinfo hints; + ConnectState *connectState; + ResolveFlags resolveFlags; + // Structure is empty (for now). + + assert(flags->familyDemand == PF_inet || + flags->familyDemand == PF_inet6 || + flags->familyDemand == PF_unspec); + assert(flags->familyPrefer == PF_inet || + flags->familyPrefer == PF_inet6 || + flags->familyPrefer == PF_unspec); + assert(proto == IPProto_tcp || proto == IPProto_udp); + + memset(&hints, '\0', sizeof hints); + hints.ai_family = protocolFamilyTranslation[flags->familyDemand]; + hints.ai_protocol = protocolTranslation[proto]; + + if (proto == IPProto_tcp) { + hints.ai_socktype = SOCK_STREAM; + } else { + assert(proto == IPProto_udp); + hints.ai_socktype = SOCK_DGRAM; + } + hints.ai_flags = 0; + + connectState = ConnectState_alloc(); + connectState->refCount = 1; +#ifdef DEBUG_CONNECT_REF + log_add(log_Debug, "ConnectState %08" PRIxPTR ": ref=1 (%d)", + (uintptr_t) connectState, connectState->refCount); +#endif + connectState->state = Connect_resolving; + connectState->flags = *flags; + connectState->connectCallback = connectCallback; + connectState->errorCallback = errorCallback; + connectState->extra = extra; + connectState->info = NULL; + connectState->infoPtr = NULL; + connectState->nd = NULL; + connectState->alarm = NULL; + + connectState->resolveState = getaddrinfoAsync( + host, service, &hints, &resolveFlags, + (ResolveCallback) connectHostResolveCallback, + (ResolveErrorCallback) connectHostResolveErrorCallback, + (ResolveCallbackArg) connectState); + + return connectState; +} + +// NB: The callback function becomes the owner of nd +static void +doConnectCallback(ConnectState *connectState, NetDescriptor *nd, + const struct sockaddr *addr, socklen_t addrLen) { + assert(connectState->connectCallback != NULL); + + ConnectState_incRef(connectState); + // No need to increment nd as the callback function takes over ownership. + (*connectState->connectCallback)(connectState, nd, addr, addrLen); + ConnectState_decRef(connectState); +} + +static void +doConnectErrorCallback(ConnectState *connectState, + const ConnectError *error) { + assert(connectState->errorCallback != NULL); + + ConnectState_incRef(connectState); + (*connectState->errorCallback)(connectState, error); + ConnectState_decRef(connectState); +} + + + diff --git a/src/libs/network/connect/connect.h b/src/libs/network/connect/connect.h new file mode 100644 index 0000000..77d7a44 --- /dev/null +++ b/src/libs/network/connect/connect.h @@ -0,0 +1,111 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_CONNECT_CONNECT_H_ +#define LIBS_NETWORK_CONNECT_CONNECT_H_ + + +typedef struct ConnectFlags ConnectFlags; +typedef struct ConnectError ConnectError; +typedef struct ConnectState ConnectState; + +typedef enum { + Connect_closed, + Connect_resolving, + Connect_connecting +} ConnectStateState; + + +#include "../netmanager/netmanager.h" +#include "../socket/socket.h" +#include "resolve.h" + + +// For connectHost() +struct ConnectFlags { + ProtocolFamily familyDemand; + // Only accept a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + ProtocolFamily familyPrefer; + // Prefer a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + int timeout; + /* Number of milliseconds before timing out a connection attempt. + * Note that if a host has multiple addresses, a connect to that + * host will have this timeout *per address*. */ + int retryDelayMs; + /* Retry connecting this many ms after connecting to the last + * address for the specified host fails. Set to Connect_noRetry + * to give up after one try. */ +#define Connect_noRetry -1 +}; + +struct ConnectError { + ConnectStateState state; + // State where the error occured. + int err; + // errno value. Not relevant if state == resolving unless + // gaiRes == EAI_SYSTEM. + const ResolveError *resolveError; + // Only relevant if state == resolving. +}; + +typedef void (*ConnectConnectCallback)(ConnectState *connectState, + NetDescriptor *nd, const struct sockaddr *addr, socklen_t addrLen); +typedef void (*ConnectErrorCallback)(ConnectState *connectState, + const ConnectError *error); + +#ifdef CONNECT_INTERNAL + +#include "libs/alarm.h" + +struct ConnectState { + RefCount refCount; + + ConnectStateState state; + ConnectFlags flags; + + ConnectConnectCallback connectCallback; + ConnectErrorCallback errorCallback; + void *extra; + + struct addrinfo *info; + struct addrinfo *infoPtr; + + ResolveState *resolveState; + + NetDescriptor *nd; + Alarm *alarm; + // Used for both the timeout for a connection attempt + // and to retry after all addresses have been tried. +}; +#endif /* CONNECT_INTERNAL */ + +ConnectState *connectHostByName(const char *host, const char *service, + Protocol proto, const ConnectFlags *flags, + ConnectConnectCallback connectCallback, + ConnectErrorCallback errorCallback, void *extra); +void ConnectState_incRef(ConnectState *connectState); +bool ConnectState_decRef(ConnectState *connectState); +void ConnectState_close(ConnectState *connectState); +void ConnectState_setExtra(ConnectState *connectState, void *extra); +void *ConnectState_getExtra(ConnectState *connectState); + +#endif /* LIBS_NETWORK_CONNECT_CONNECT_H_ */ + + diff --git a/src/libs/network/connect/listen.c b/src/libs/network/connect/listen.c new file mode 100644 index 0000000..4a7a65c --- /dev/null +++ b/src/libs/network/connect/listen.c @@ -0,0 +1,456 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" +#include "../netport.h" + +#define LISTEN_INTERNAL +#define SOCKET_INTERNAL +#include "listen.h" + +#include "resolve.h" +#include "../socket/socket.h" +#include "../netmanager/netmanager.h" +#include "libs/misc.h" +#include "libs/log.h" + +#include <assert.h> +#include <errno.h> +#ifdef USE_WINSOCK +# include <winsock2.h> +# include <ws2tcpip.h> +# include "../wspiapiwrap.h" +#else +# include <netdb.h> +#endif +#include <stdlib.h> +#include <string.h> + +#define DEBUG_LISTEN_REF +#ifdef DEBUG_LISTEN_REF +# include "types.h" +#endif + + +static void acceptCallback(NetDescriptor *nd); +static void doListenErrorCallback(ListenState *listenState, + const ListenError *error); + + +static ListenState * +ListenState_alloc(void) { + return (ListenState *) malloc(sizeof (ListenState)); +} + +static void +ListenState_free(ListenState *listenState) { + free(listenState); +} + +static void +ListenState_delete(ListenState *listenState) { + assert(listenState->nds == NULL); + ListenState_free(listenState); +} + +void +ListenState_incRef(ListenState *listenState) { + assert(listenState->refCount < REFCOUNT_MAX); + listenState->refCount++; +#ifdef DEBUG_LISTEN_REF + log_add(log_Debug, "ListenState %08" PRIxPTR ": ref++ (%d)", + (uintptr_t) listenState, listenState->refCount); +#endif +} + +bool +ListenState_decRef(ListenState *listenState) { + assert(listenState->refCount > 0); + listenState->refCount--; +#ifdef DEBUG_LISTEN_REF + log_add(log_Debug, "ListenState %08" PRIxPTR ": ref-- (%d)", + (uintptr_t) listenState, listenState->refCount); +#endif + if (listenState->refCount == 0) { + ListenState_delete(listenState); + return true; + } + return false; +} + +// Decrements ref count byh 1 +void +ListenState_close(ListenState *listenState) { + if (listenState->resolveState != NULL) { + Resolve_close(listenState->resolveState); + listenState->resolveState = NULL; + } + if (listenState->nds != NULL) { + while (listenState->numNd > 0) { + listenState->numNd--; + NetDescriptor_close(listenState->nds[listenState->numNd]); + } + free(listenState->nds); + listenState->nds = NULL; + } + listenState->state = Listen_closed; + ListenState_decRef(listenState); +} + +void +ListenState_setExtra(ListenState *listenState, void *extra) { + listenState->extra = extra; +} + +void * +ListenState_getExtra(ListenState *listenState) { + return listenState->extra; +} + +static NetDescriptor * +listenPortSingle(struct ListenState *listenState, struct addrinfo *info) { + Socket *sock; + int bindResult; + int listenResult; + NetDescriptor *nd; + + sock = Socket_openNative(info->ai_family, info->ai_socktype, + info->ai_protocol); + if (sock == Socket_noSocket) { + int savedErrno = errno; + log_add(log_Error, "socket() failed: %s.", strerror(errno)); + errno = savedErrno; + return NULL; + } + + (void) Socket_setReuseAddr(sock); + // Ignore errors; it's not a big deal. + if (Socket_setNonBlocking(sock) == -1) { + int savedErrno = errno; + // Error message is already printed. + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + bindResult = Socket_bind(sock, info->ai_addr, info->ai_addrlen); + if (bindResult == -1) { + int savedErrno = errno; + if (errno == EADDRINUSE) { +#ifdef DEBUG + log_add(log_Warning, "bind() failed: %s.", strerror(errno)); +#endif + } else + log_add(log_Error, "bind() failed: %s.", strerror(errno)); + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + listenResult = Socket_listen(sock, listenState->flags.backlog); + if (listenResult == -1) { + int savedErrno = errno; + log_add(log_Error, "listen() failed: %s.", strerror(errno)); + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + nd = NetDescriptor_new(sock, (void *) listenState); + if (nd == NULL) { + int savedErrno = errno; + log_add(log_Error, "NetDescriptor_new() failed: %s.", + strerror(errno)); + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + NetDescriptor_setReadCallback(nd, acceptCallback); + + return nd; +} + +static void +listenPortMulti(struct ListenState *listenState, struct addrinfo *info) { + struct addrinfo *addrPtr; + size_t addrCount; + size_t addrOkCount; + NetDescriptor **nds; + + // Count how many addresses we've got. + addrCount = 0; + for (addrPtr = info; addrPtr != NULL; addrPtr = addrPtr->ai_next) + addrCount++; + + // This is where we intend to store the file descriptors of the + // listening sockets. + nds = malloc(addrCount * sizeof listenState->nds[0]); + + // Bind to each address. + addrOkCount = 0; + for (addrPtr = info; addrPtr != NULL; addrPtr = addrPtr->ai_next) { + NetDescriptor *nd; + nd = listenPortSingle(listenState, addrPtr); + if (nd == NULL) { + // Failed. On to the next. + // An error message is already printed for serious errors. + // If the address is already in use, we here also print + // a message when we are not already listening on one of + // the other addresses. + // This is because on some IPv6 capabable systems (like Linux), + // IPv6 sockets also handle IPv4 connections, which means + // that a separate IPv4 socket won't be able to bind to the + // port. + // BUG: if the IPv4 socket is in the list before the + // IPv6 socket, it will be the IPv6 which will fail to bind, + // so only IPv4 connections will be handled, as v4 sockets can't + // accept v6 connections. + // In practice, on Linux, I haven't seen it happen, but + // it's a real possibility. + if (errno == EADDRINUSE && addrOkCount == 0) { + log_add(log_Error, "Error while preparing a network socket " + "for incoming connections: %s", strerror(errno)); + } + continue; + } + + nds[addrOkCount] = nd; + addrOkCount++; + } + + freeaddrinfo(info); + + listenState->nds = + realloc(nds, addrOkCount * sizeof listenState->nds[0]); + listenState->numNd = addrOkCount; + + if (addrOkCount == 0) { + // Could not listen on any port. + ListenError error; + error.state = Listen_listening; + error.err = EIO; + // Nothing better to offer. + doListenErrorCallback(listenState, &error); + return; + } +} + +static void +listenPortResolveCallback(ResolveState *resolveState, + struct addrinfo *result) { + ListenState *listenState = + (ListenState *) ResolveState_getExtra(resolveState); + Resolve_close(listenState->resolveState); + listenState->resolveState = NULL; + listenState->state = Listen_listening; + listenPortMulti(listenState, result); +} + +static void +listenPortResolveErrorCallback(ResolveState *resolveState, + const ResolveError *resolveError) { + ListenState *listenState = + (ListenState *) ResolveState_getExtra(resolveState); + ListenError listenError; + + assert(resolveError->gaiRes != 0); + + listenError.state = Listen_resolving; + listenError.resolveError = resolveError; + listenError.err = resolveError->err; + doListenErrorCallback(listenState, &listenError); +} + +// 'proto' is one of IPProto_tcp or IPProto_udp. +ListenState * +listenPort(const char *service, Protocol proto, const ListenFlags *flags, + ListenConnectCallback connectCallback, + ListenErrorCallback errorCallback, void *extra) { + struct addrinfo hints; + ListenState *listenState; + ResolveFlags resolveFlags; + // Structure is empty (for now). + + assert(flags->familyDemand == PF_inet || + flags->familyDemand == PF_inet6 || + flags->familyDemand == PF_unspec); + assert(flags->familyPrefer == PF_inet || + flags->familyPrefer == PF_inet6 || + flags->familyPrefer == PF_unspec); + assert(proto == IPProto_tcp || proto == IPProto_udp); + + // Acquire a list of addresses to bind to. + memset(&hints, '\0', sizeof hints); + hints.ai_family = protocolFamilyTranslation[flags->familyDemand]; + hints.ai_protocol = protocolTranslation[proto]; + + if (proto == IPProto_tcp) { + hints.ai_socktype = SOCK_STREAM; + } else { + assert(proto == IPProto_udp); + hints.ai_socktype = SOCK_DGRAM; + } + hints.ai_flags = AI_PASSIVE; + + listenState = ListenState_alloc(); + listenState->refCount = 1; +#ifdef DEBUG_LISTEN_REF + log_add(log_Debug, "ListenState %08" PRIxPTR ": ref=1 (%d)", + (uintptr_t) listenState, listenState->refCount); +#endif + listenState->state = Listen_resolving; + listenState->flags = *flags; + listenState->connectCallback = connectCallback; + listenState->errorCallback = errorCallback; + listenState->extra = extra; + listenState->nds = NULL; + listenState->numNd = 0; + + listenState->resolveState = getaddrinfoAsync(NULL, service, &hints, + &resolveFlags, listenPortResolveCallback, + listenPortResolveErrorCallback, + (ResolveCallbackArg) listenState); + + return listenState; +} + +// NB: The callback function becomes the owner of newNd. +static void +doListenConnectCallback(ListenState *listenState, NetDescriptor *listenNd, + NetDescriptor *newNd, + const struct sockaddr *addr, SOCKLEN_T addrLen) { + assert(listenState->connectCallback != NULL); + + ListenState_incRef(listenState); + // No need to increment listenNd, as there's guaranteed to be one + // reference from listenState. And no need to increment newNd, + // as the callback function takes over ownership. + (*listenState->connectCallback)(listenState, listenNd, newNd, + addr, (socklen_t) addrLen); + ListenState_decRef(listenState); +} + +static void +doListenErrorCallback(ListenState *listenState, const ListenError *error) { + assert(listenState->errorCallback != NULL); + + ListenState_incRef(listenState); + (*listenState->errorCallback)(listenState, error); + ListenState_decRef(listenState); +} + +static void +acceptSingleConnection(ListenState *listenState, NetDescriptor *nd) { + Socket *sock; + Socket *acceptResult; + struct sockaddr_storage addr; + socklen_t addrLen; + NetDescriptor *newNd; + + sock = NetDescriptor_getSocket(nd); + addrLen = sizeof (addr); + acceptResult = Socket_accept(sock, (struct sockaddr *) &addr, &addrLen); + if (acceptResult == Socket_noSocket) { + switch (errno) { + case EWOULDBLOCK: + case ECONNABORTED: + // Nothing serious. Keep listening. + return; + case EMFILE: + case ENFILE: + case ENOBUFS: + case ENOMEM: +#ifdef ENOSR + case ENOSR: +#endif + // Serious problems, but future connections may still + // be possible. + log_add(log_Warning, "accept() reported '%s'", + strerror(errno)); + return; + default: + // Should not happen. + log_add(log_Fatal, "Internal error: accept() reported " + "'%s'", strerror(errno)); + explode(); + } + } + + (void) Socket_setReuseAddr(acceptResult); + // Ignore errors; it's not a big deal. + if (Socket_setNonBlocking(acceptResult) == -1) { + int savedErrno = errno; + log_add(log_Error, "Could not make socket non-blocking: %s.", + strerror(errno)); + Socket_close(acceptResult); + errno = savedErrno; + return; + } + (void) Socket_setInlineOOB(acceptResult); + // Ignore errors; it's not a big deal as the other + // party is not not supposed to send any OOB data. + (void) Socket_setKeepAlive(sock); + // Ignore errors; it's not a big deal. + +#ifdef DEBUG + { + char hostname[NI_MAXHOST]; + int gniRes; + + gniRes = getnameinfo((struct sockaddr *) &addr, addrLen, + hostname, sizeof hostname, NULL, 0, 0); + if (gniRes != 0) { + log_add(log_Error, "Error while performing hostname " + "lookup for incoming connection: %s", + (gniRes == EAI_SYSTEM) ? strerror(errno) : + gai_strerror(gniRes)); + } else { + log_add(log_Debug, "Accepted incoming connection from '%s'.", + hostname); + } + } +#endif + + newNd = NetDescriptor_new(acceptResult, NULL); + if (newNd == NULL) { + int savedErrno = errno; + log_add(log_Error, "NetDescriptor_new() failed: %s.", + strerror(errno)); + Socket_close(acceptResult); + errno = savedErrno; + return; + } + + doListenConnectCallback(listenState, nd, newNd, + (struct sockaddr *) &addr, addrLen); + // NB: newNd is now handed over to the callback function, and should + // no longer be referenced from here. +} + +// Called when select() has indicated readability on a listening socket, +// i.e. when a connection is in the queue. +static void +acceptCallback(NetDescriptor *nd) { + ListenState *listenState = (ListenState *) NetDescriptor_getExtra(nd); + + acceptSingleConnection(listenState, nd); +} + + diff --git a/src/libs/network/connect/listen.h b/src/libs/network/connect/listen.h new file mode 100644 index 0000000..e44ef53 --- /dev/null +++ b/src/libs/network/connect/listen.h @@ -0,0 +1,106 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_CONNECT_LISTEN_H_ +#define LIBS_NETWORK_CONNECT_LISTEN_H_ + +typedef struct ListenFlags ListenFlags; +typedef enum { + Listen_closed, + Listen_resolving, + Listen_listening +} ListenStateState; +typedef struct ListenError ListenError; +typedef struct ListenState ListenState; + +#include "port.h" + +#ifdef USE_WINSOCK +// I do not want to include winsock2.h, because of possible conflicts with +// code that includes this file. +// Note that listen.c itself includes winsock2.h; SOCKLEN_T is used there +// only where necessary to keep the API consistent. +# define SOCKLEN_T size_t +struct sockaddr; +#else +# include <netinet/in.h> +# define SOCKLEN_T socklen_t +#endif + +#include "resolve.h" +#include "../socket/socket.h" + +#include "../netmanager/netmanager.h" + +// For listenPort() +struct ListenFlags { + ProtocolFamily familyDemand; + // Only accept a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + ProtocolFamily familyPrefer; + // Prefer a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + int backlog; + // As the 2rd parameter to listen(); +}; + +struct ListenError { + ListenStateState state; + // State where the error occured. + int err; + // errno value. Not relevant if state == resolving unless + // gaiRes == EAI_SYSTEM. + const ResolveError *resolveError; + // Only relevant if state == resolving. +}; + +typedef void (*ListenConnectCallback)(ListenState *listenState, + NetDescriptor *listenNd, NetDescriptor *newNd, + const struct sockaddr *addr, SOCKLEN_T addrLen); +typedef void (*ListenErrorCallback)(ListenState *listenState, + const ListenError *error); + +#ifdef LISTEN_INTERNAL +struct ListenState { + RefCount refCount; + + ListenStateState state; + ListenFlags flags; + + ListenConnectCallback connectCallback; + ListenErrorCallback errorCallback; + void *extra; + + ResolveState *resolveState; + NetDescriptor **nds; + size_t numNd; + // INV: (numNd == NULL) == (nds == NULL) +}; +#endif /* defined(LISTEN_INTERNAL) */ + +ListenState *listenPort(const char *service, Protocol proto, + const ListenFlags *flags, ListenConnectCallback connectCallback, + ListenErrorCallback errorCallback, void *extra); +void ListenState_close(ListenState *listenState); +void ListenState_incRef(ListenState *listenState); +bool ListenState_decRef(ListenState *listenState); +void ListenState_setExtra(ListenState *listenState, void *extra); +void *ListenState_getExtra(ListenState *listenState); + +#endif /* LIBS_NETWORK_CONNECT_LISTEN_H_ */ + diff --git a/src/libs/network/connect/resolve.c b/src/libs/network/connect/resolve.c new file mode 100644 index 0000000..646e437 --- /dev/null +++ b/src/libs/network/connect/resolve.c @@ -0,0 +1,211 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define RESOLVE_INTERNAL +#include "resolve.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> + +#define DEBUG_RESOLVE_REF +#ifdef DEBUG_RESOLVE_REF +# include "types.h" +# include "libs/log.h" +# include <string.h> +#endif + +static ResolveState * +ResolveState_new(void) { + return (ResolveState *) malloc(sizeof (ResolveState)); +} + +static void +ResolveState_free(ResolveState *resolveState) { + free(resolveState); +} + +static void +ResolveState_delete(ResolveState *resolveState) { + assert(resolveState->callbackID == CallbackID_invalid); + ResolveState_free(resolveState); +} + +void +ResolveState_incRef(ResolveState *resolveState) { + assert(resolveState->refCount < REFCOUNT_MAX); + resolveState->refCount++; +#ifdef DEBUG_RESOLVE_REF + log_add(log_Debug, "ResolveState %08" PRIxPTR ": ref++ (%d)", + (uintptr_t) resolveState, resolveState->refCount); +#endif +} + +bool +ResolveState_decRef(ResolveState *resolveState) { + assert(resolveState->refCount > 0); + resolveState->refCount--; +#ifdef DEBUG_RESOLVE_REF + log_add(log_Debug, "ResolveState %08" PRIxPTR ": ref-- (%d)", + (uintptr_t) resolveState, resolveState->refCount); +#endif + if (resolveState->refCount == 0) { + ResolveState_delete(resolveState); + return true; + } + return false; +} + +void +ResolveState_setExtra(ResolveState *resolveState, void *extra) { + resolveState->extra = extra; +} + +void * +ResolveState_getExtra(ResolveState *resolveState) { + return resolveState->extra; +} + +static void +doResolveCallback(ResolveState *resolveState) { + ResolveState_incRef(resolveState); + (*resolveState->callback)(resolveState, resolveState->result); + { + bool released = ResolveState_decRef(resolveState); + assert(released); + (void) released; // In case assert() evaluates to nothing. + } +} + +static void +doResolveErrorCallback(ResolveState *resolveState) { + ResolveState_incRef(resolveState); + resolveState->errorCallback(resolveState, &resolveState->error); + { + bool released = ResolveState_decRef(resolveState); + assert(released); + (void) released; // In case assert() evaluates to nothing. + } +} + +static void +resolveCallback(ResolveState *resolveState) { + resolveState->callbackID = CallbackID_invalid; + if (resolveState->error.gaiRes == 0) { + // Successful lookup. + doResolveCallback(resolveState); + } else { + // Lookup failed. + doResolveErrorCallback(resolveState); + } +} + +// Function that does getaddrinfo() and calls the callback function when +// the result is available. +// TODO: actually make this function asynchronous. Right now it just calls +// getaddrinfo() (which blocks) and then schedules the callback. +ResolveState * +getaddrinfoAsync(const char *node, const char *service, + const struct addrinfo *hints, ResolveFlags *flags, + ResolveCallback callback, ResolveErrorCallback errorCallback, + ResolveCallbackArg extra) { + ResolveState *resolveState; + + resolveState = ResolveState_new(); + resolveState->refCount = 1; +#ifdef DEBUG_RESOLVE_REF + log_add(log_Debug, "ResolveState %08" PRIxPTR ": ref=1 (%d)", + (uintptr_t) resolveState, resolveState->refCount); +#endif + resolveState->state = Resolve_resolving; + resolveState->flags = *flags; + resolveState->callback = callback; + resolveState->errorCallback = errorCallback; + resolveState->extra = extra; + resolveState->result = NULL; + + resolveState->error.gaiRes = + getaddrinfo(node, service, hints, &resolveState->result); + resolveState->error.err = errno; + + resolveState->callbackID = Callback_add( + (CallbackFunction) resolveCallback, (CallbackArg) resolveState); + + return resolveState; +} + +void +Resolve_close(ResolveState *resolveState) { + if (resolveState->callbackID != CallbackID_invalid) { + Callback_remove(resolveState->callbackID); + resolveState->callbackID = CallbackID_invalid; + } + resolveState->state = Resolve_closed; + ResolveState_decRef(resolveState); +} + +// Split an addrinfo list into two separate lists, one with structures with +// the specified value for the ai_family field, the other with the other +// structures. The order of entries in the resulting lists will remain +// the same as in the original list. +// info - the original list +// family - the family for the first list +// selected - pointer to where the list of selected structures should be +// stored +// selectedEnd - pointer to where the end of 'selected' should be stored +// rest - pointer to where the list of not-selected structures should be +// stored +// restEnd - pointer to where the end of 'rest' should be stored +// +// Yes, it is allowed to modify the ai_next field of addrinfo structures. +// Or at least, it's not disallowed by RFC 3493, and the following +// text from that RFC seems to suggest it should be possible: +// ] The freeaddrinfo() function must support the freeing of arbitrary +// ] sublists of an addrinfo list originally returned by getaddrinfo(). +void +splitAddrInfoOnFamily(struct addrinfo *info, int family, + struct addrinfo **selected, struct addrinfo ***selectedEnd, + struct addrinfo **rest, struct addrinfo ***restEnd) { + struct addrinfo *selectedFirst; + struct addrinfo **selectedNext; + struct addrinfo *restFirst; + struct addrinfo **restNext; + + selectedNext = &selectedFirst; + restNext = &restFirst; + while (info != NULL) { + if (info->ai_family == family) { + *selectedNext = info; + selectedNext = &(*selectedNext)->ai_next; + } else { + *restNext = info; + restNext = &(*restNext)->ai_next; + } + info = info->ai_next; + } + *selectedNext = NULL; + *restNext = NULL; + + // Fill in the result parameters. + *selected = selectedFirst; + *selectedEnd = selectedNext; + *rest = restFirst; + *restEnd = restNext; +} + + diff --git a/src/libs/network/connect/resolve.h b/src/libs/network/connect/resolve.h new file mode 100644 index 0000000..509c3b9 --- /dev/null +++ b/src/libs/network/connect/resolve.h @@ -0,0 +1,109 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_CONNECT_RESOLVE_H_ +#define LIBS_NETWORK_CONNECT_RESOLVE_H_ + + +typedef struct ResolveFlags ResolveFlags; +typedef struct ResolveError ResolveError; +typedef enum { + Resolve_closed, + Resolve_resolving +} ResolveStateState; +typedef struct ResolveState ResolveState; + +#include "port.h" +#include "../netport.h" + +// For addrinfo. +#ifdef USE_WINSOCK +// Not including <winsock2.h> because of possible conflicts with files +// including this file. +struct addrinfo; +#else +# include <netdb.h> +#endif + +#include "libs/callback.h" +#include "../netmanager/netmanager.h" + + +struct ResolveFlags { + // Nothing yet. + + int dummy; // empty struct declarations are not allowed by C'99. +}; + +struct ResolveError { + int gaiRes; + int err; + // errno value. Only relevant if gaiRes == EAI_SYSTEM. +}; + +typedef void *ResolveCallbackArg; +typedef void (*ResolveCallback)(ResolveState *resolveState, + struct addrinfo *result); + // The receiver of the callback is owner of 'result' and + // should call freeaddrinfo(). +typedef void (*ResolveErrorCallback)(ResolveState *resolveState, + const ResolveError *error); + +#ifdef RESOLVE_INTERNAL +#ifdef USE_WINSOCK +# include <winsock2.h> +# include <ws2tcpip.h> +# include "../wspiapiwrap.h" +#else /* !defined(USE_WINSOCK) */ +# include <netdb.h> +#endif /* !defined(USE_WINSOCK) */ + +struct ResolveState { + RefCount refCount; + + ResolveStateState state; + ResolveFlags flags; + + ResolveCallback callback; + ResolveErrorCallback errorCallback; + void *extra; + + CallbackID callbackID; + ResolveError error; + struct addrinfo *result; +}; +#endif /* RESOLVE_INTERNAL */ + + +void ResolveState_incRef(ResolveState *resolveState); +bool ResolveState_decRef(ResolveState *resolveState); +void ResolveState_setExtra(ResolveState *resolveState, void *extra); +void *ResolveState_getExtra(ResolveState *resolveState); +ResolveState *getaddrinfoAsync(const char *node, const char *service, + const struct addrinfo *hints, ResolveFlags *flags, + ResolveCallback callback, ResolveErrorCallback errorCallback, + ResolveCallbackArg extra); +void Resolve_close(ResolveState *resolveState); + +void splitAddrInfoOnFamily(struct addrinfo *info, int family, + struct addrinfo **selected, struct addrinfo ***selectedEnd, + struct addrinfo **rest, struct addrinfo ***restEnd); + + +#endif /* LIBS_NETWORK_CONNECT_RESOLVE_H_ */ + diff --git a/src/libs/network/netmanager/Makeinfo b/src/libs/network/netmanager/Makeinfo new file mode 100644 index 0000000..ddf28b3 --- /dev/null +++ b/src/libs/network/netmanager/Makeinfo @@ -0,0 +1,11 @@ +uqm_CFILES="ndesc.c" +uqm_HFILES="ndesc.h netmanager.h" + +if [ -n "$uqm_USE_WINSOCK" ]; then + uqm_CFILES="$uqm_CFILES netmanager_win.c" + uqm_HFILES="$uqm_HFILES netmanager_win.h" +else + uqm_CFILES="$uqm_CFILES netmanager_bsd.c" + uqm_HFILES="$uqm_HFILES netmanager_bsd.h" +fi + diff --git a/src/libs/network/netmanager/ndesc.c b/src/libs/network/netmanager/ndesc.c new file mode 100644 index 0000000..e407ff9 --- /dev/null +++ b/src/libs/network/netmanager/ndesc.c @@ -0,0 +1,211 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NETDESCRIPTOR_INTERNAL +#include "ndesc.h" + +#include "netmanager.h" +#include "libs/callback.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> + +#undef DEBUG_NETDESCRIPTOR_REF +#ifdef DEBUG_NETDESCRIPTOR_REF +# include "libs/log.h" +# include <inttypes.h> +#endif + + +static NetDescriptor * +NetDescriptor_alloc(void) { + return malloc(sizeof (NetDescriptor)); +} + +static void +NetDescriptor_free(NetDescriptor *nd) { + free(nd); +} + +// Sets the ref count to 1. +NetDescriptor * +NetDescriptor_new(Socket *socket, void *extra) { + NetDescriptor *nd; + + nd = NetDescriptor_alloc(); + nd->refCount = 1; +#ifdef DEBUG_NETDESCRIPTOR_REF + log_add(log_Debug, "NetDescriptor %08" PRIxPTR ": ref=1 (%d)", + (uintptr_t) nd, nd->refCount); +#endif + + nd->flags.closed = false; + nd->readCallback = NULL; + nd->writeCallback = NULL; + nd->exceptionCallback = NULL; + nd->closeCallback = NULL; + nd->socket = socket; + nd->smd = NULL; + nd->extra = extra; + + if (NetManager_addDesc(nd) == -1) { + int savedErrno = errno; + NetDescriptor_free(nd); + errno = savedErrno; + return NULL; + } + + return nd; +} + +static void +NetDescriptor_delete(NetDescriptor *nd) { + assert(nd->socket == Socket_noSocket); + assert(nd->smd == NULL); + + NetDescriptor_free(nd); +} + +// Called from the callback handler. +static void +NetDescriptor_closeCallback(NetDescriptor *nd) { + if (nd->closeCallback != NULL) { + // The check is necessary because the close callback may have + // been removed before it is triggered. + (*nd->closeCallback)(nd); + } + NetDescriptor_decRef(nd); +} + +void +NetDescriptor_close(NetDescriptor *nd) { + assert(!nd->flags.closed); + assert(nd->socket != Socket_noSocket); + + NetManager_removeDesc(nd); + (void) Socket_close(nd->socket); + nd->socket = Socket_noSocket; + nd->flags.closed = true; + if (nd->closeCallback != NULL) { + // Keep one reference around until the close callback has been + // called. + (void) Callback_add( + (CallbackFunction) NetDescriptor_closeCallback, + (CallbackArg) nd); + } else + NetDescriptor_decRef(nd); +} + +void +NetDescriptor_incRef(NetDescriptor *nd) { + assert(nd->refCount < REFCOUNT_MAX); + nd->refCount++; +#ifdef DEBUG_NETDESCRIPTOR_REF + log_add(log_Debug, "NetDescriptor %08" PRIxPTR ": ref++ (%d)", + (uintptr_t) nd, nd->refCount); +#endif +} + +// returns true iff the ref counter has reached 0. +bool +NetDescriptor_decRef(NetDescriptor *nd) { + assert(nd->refCount > 0); + nd->refCount--; +#ifdef DEBUG_NETDESCRIPTOR_REF + log_add(log_Debug, "NetDescriptor %08" PRIxPTR ": ref-- (%d)", + (uintptr_t) nd, nd->refCount); +#endif + if (nd->refCount == 0) { + NetDescriptor_delete(nd); + return true; + } + return false; +} + +// The socket will no longer be managed by the NetManager. +void +NetDescriptor_detach(NetDescriptor *nd) { + NetManager_removeDesc(nd); + nd->socket = Socket_noSocket; + nd->flags.closed = true; + NetDescriptor_decRef(nd); +} + +Socket * +NetDescriptor_getSocket(NetDescriptor *nd) { + return nd->socket; +} + +void +NetDescriptor_setExtra(NetDescriptor *nd, void *extra) { + nd->extra = extra; +} + +void * +NetDescriptor_getExtra(const NetDescriptor *nd) { + return nd->extra; +} + +void +NetDescriptor_setReadCallback(NetDescriptor *nd, + NetDescriptor_ReadCallback callback) { + nd->readCallback = callback; + if (!nd->flags.closed) { + if (nd->readCallback != NULL) { + NetManager_activateReadCallback(nd); + } else + NetManager_deactivateReadCallback(nd); + } +} + +void +NetDescriptor_setWriteCallback(NetDescriptor *nd, + NetDescriptor_WriteCallback callback) { + nd->writeCallback = callback; + if (!nd->flags.closed) { + if (nd->writeCallback != NULL) { + NetManager_activateWriteCallback(nd); + } else + NetManager_deactivateWriteCallback(nd); + } +} + +void +NetDescriptor_setExceptionCallback(NetDescriptor *nd, + NetDescriptor_ExceptionCallback callback) { + nd->exceptionCallback = callback; + if (!nd->flags.closed) { + if (nd->exceptionCallback != NULL) { + NetManager_activateExceptionCallback(nd); + } else + NetManager_deactivateExceptionCallback(nd); + } +} + +// The close callback is called as a result of a socket being closed, either +// because of a local command or a remote disconnect. +// The close callback will only be scheduled when this happens. The +// callback will not be called until the Callback_process() is called. +void +NetDescriptor_setCloseCallback(NetDescriptor *nd, + NetDescriptor_CloseCallback callback) { + nd->closeCallback = callback; +} + + diff --git a/src/libs/network/netmanager/ndesc.h b/src/libs/network/netmanager/ndesc.h new file mode 100644 index 0000000..8834db9 --- /dev/null +++ b/src/libs/network/netmanager/ndesc.h @@ -0,0 +1,82 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_NETMANAGER_NDESC_H_ +#define LIBS_NETWORK_NETMANAGER_NDESC_H_ + +#include "types.h" + + +typedef struct NetDescriptor NetDescriptor; + +typedef void (*NetDescriptor_ReadCallback)(NetDescriptor *nd); +typedef void (*NetDescriptor_WriteCallback)(NetDescriptor *nd); +typedef void (*NetDescriptor_ExceptionCallback)(NetDescriptor *nd); +typedef void (*NetDescriptor_CloseCallback)(NetDescriptor *nd); + +typedef uint32 RefCount; +#define REFCOUNT_MAX UINT32_MAX + +#include "../socket/socket.h" +#include "netmanager.h" + +#ifdef NETDESCRIPTOR_INTERNAL +// All created NetDescriptors are registered to the NetManager. +// They are unregisted when the NetDescriptor is closed. +// On creation the ref count is set to 1. On close it is decremented by 1. +struct NetDescriptor { + struct { + bool closed: 1; + } flags; + + RefCount refCount; + + NetDescriptor_ReadCallback readCallback; + NetDescriptor_WriteCallback writeCallback; + NetDescriptor_ExceptionCallback exceptionCallback; + NetDescriptor_CloseCallback closeCallback; + + Socket *socket; + SocketManagementData *smd; + + // Extra state-dependant information for the user. + void *extra; +}; +#endif + +NetDescriptor *NetDescriptor_new(Socket *socket, void *extra); +void NetDescriptor_close(NetDescriptor *nd); +void NetDescriptor_incRef(NetDescriptor *nd); +bool NetDescriptor_decRef(NetDescriptor *nd); +void NetDescriptor_detach(NetDescriptor *nd); +Socket *NetDescriptor_getSocket(NetDescriptor *nd); +void NetDescriptor_setExtra(NetDescriptor *nd, void *extra); +void *NetDescriptor_getExtra(const NetDescriptor *nd); +void NetDescriptor_setReadCallback(NetDescriptor *nd, + NetDescriptor_ReadCallback callback); +void NetDescriptor_setWriteCallback(NetDescriptor *nd, + NetDescriptor_WriteCallback callback); +void NetDescriptor_setExceptionCallback(NetDescriptor *nd, + NetDescriptor_ExceptionCallback callback); +void NetDescriptor_setCloseCallback(NetDescriptor *nd, + NetDescriptor_CloseCallback callback); + + +#endif /* LIBS_NETWORK_NETMANAGER_NDESC_H_ */ + + diff --git a/src/libs/network/netmanager/ndindex.ci b/src/libs/network/netmanager/ndindex.ci new file mode 100644 index 0000000..f922d8b --- /dev/null +++ b/src/libs/network/netmanager/ndindex.ci @@ -0,0 +1,103 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// This file is part of netmanager_bsd.c, from where it is #included. +// Only used for BSD sockets. + +// This file provides a mapping of Sockets to NetDescriptors. + + +static NetDescriptor *netDescriptors[FD_SETSIZE]; + // INV: flags.closed is not set for entries in netDescriptors. +static size_t maxND; + // One past the largest used ND in netDescriptors, as used in + // the first argument of select(); + + +static inline void +NDIndex_init(void) { + size_t i; + size_t numND = sizeof (netDescriptors) / sizeof (netDescriptors[0]); + + for (i = 0; i < numND; i++) + netDescriptors[i] = NULL; +} + +static inline void +NDIndex_uninit(void) { + // Nothing to do. +} + +static inline int +NDIndex_registerNDWithSocket(Socket *sock, NetDescriptor *nd) { + if ((unsigned int) sock->fd >= FD_SETSIZE) { + errno = EMFILE; + return -1; + } + + netDescriptors[sock->fd] = nd; + + if ((size_t) sock->fd >= maxND) + maxND = (size_t) sock->fd + 1; + + return 0; +} + +static inline void +NDIndex_unregisterNDForSocket(Socket *sock) { + NetDescriptor **last; + + netDescriptors[sock->fd] = NULL; + + last = &netDescriptors[sock->fd]; + + if ((size_t) sock->fd + 1 == maxND) { + do { + maxND--; + if (last == &netDescriptors[0]) + break; + last--; + } while (*last == NULL); + } +} + +static inline NetDescriptor * +NDIndex_getNDForSocket(Socket *sock) { + assert((size_t) sock->fd < maxND); + return netDescriptors[sock->fd]; +} + +static inline NetDescriptor * +NDIndex_getNDForSocketFd(int fd) { + assert((size_t) fd < maxND); + return netDescriptors[fd]; +} + +static inline bool +NDIndex_socketRegistered(Socket *sock) { + return ((size_t) sock->fd < maxND) + && (NDIndex_getNDForSocket(sock) != NULL); +} + +// Get the first argument to be used in select(). +static inline size_t +NDIndex_getSelectNumND(void) { + return maxND; +} + + diff --git a/src/libs/network/netmanager/netmanager.h b/src/libs/network/netmanager/netmanager.h new file mode 100644 index 0000000..42be572 --- /dev/null +++ b/src/libs/network/netmanager/netmanager.h @@ -0,0 +1,48 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_NETMANAGER_NETMANAGER_H_ +#define LIBS_NETWORK_NETMANAGER_NETMANAGER_H_ + +#include "port.h" +#include "types.h" + +#ifdef USE_WINSOCK +# include "netmanager_win.h" +#else +# include "netmanager_bsd.h" +#endif + +#include "ndesc.h" + +void NetManager_init(void); +void NetManager_uninit(void); +int NetManager_process(uint32 *timeoutMs); + +// Only for internal use by the NetManager: +int NetManager_addDesc(NetDescriptor *nd); +void NetManager_removeDesc(NetDescriptor *nd); +void NetManager_activateReadCallback(NetDescriptor *nd); +void NetManager_deactivateReadCallback(NetDescriptor *nd); +void NetManager_activateWriteCallback(NetDescriptor *nd); +void NetManager_deactivateWriteCallback(NetDescriptor *nd); +void NetManager_activateExceptionCallback(NetDescriptor *nd); +void NetManager_deactivateExceptionCallback(NetDescriptor *nd); + +#endif /* LIBS_NETWORK_NETMANAGER_NETMANAGER_H_ */ + diff --git a/src/libs/network/netmanager/netmanager_bsd.c b/src/libs/network/netmanager/netmanager_bsd.c new file mode 100644 index 0000000..29159f8 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_bsd.c @@ -0,0 +1,223 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define SOCKET_INTERNAL +#define NETDESCRIPTOR_INTERNAL +#include "netmanager_bsd.h" +#include "ndesc.h" +#include "../socket/socket.h" + +#include "ndesc.h" +#include "types.h" +#include "libs/log.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "netmanager_common.ci" +#include "ndindex.ci" + + +// INV: The following sets only contain sockets present in the netDescriptor +// array. +static fd_set readSet; +static fd_set writeSet; +static fd_set exceptionSet; + + +void +NetManager_init(void) { + NDIndex_init(); + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_ZERO(&exceptionSet); +} + +void +NetManager_uninit(void) { + NDIndex_uninit(); +} + +// Register the NetDescriptor with the NetManager. +int +NetManager_addDesc(NetDescriptor *nd) { + int fd; + assert(nd->socket != Socket_noSocket); + assert(!NDIndex_socketRegistered(nd->socket)); + + if (NDIndex_registerNDWithSocket(nd->socket, nd) == -1) { + // errno is set + return -1; + } + + fd = nd->socket->fd; + if (nd->readCallback != NULL) + FD_SET(fd, &readSet); + if (nd->writeCallback != NULL) + FD_SET(fd, &writeSet); + if (nd->exceptionCallback != NULL) + FD_SET(fd, &exceptionSet); + return 0; +} + +void +NetManager_removeDesc(NetDescriptor *nd) { + int fd; + + assert(nd->socket != Socket_noSocket); + assert(NDIndex_getNDForSocket(nd->socket) == nd); + + fd = nd->socket->fd; + FD_CLR(fd, &readSet); + FD_CLR(fd, &writeSet); + FD_CLR(fd, &exceptionSet); + + NDIndex_unregisterNDForSocket(nd->socket); +} + +void +NetManager_activateReadCallback(NetDescriptor *nd) { + FD_SET(nd->socket->fd, &readSet); +} + +void +NetManager_deactivateReadCallback(NetDescriptor *nd) { + FD_CLR(nd->socket->fd, &readSet); +} + +void +NetManager_activateWriteCallback(NetDescriptor *nd) { + FD_SET(nd->socket->fd, &writeSet); +} + +void +NetManager_deactivateWriteCallback(NetDescriptor *nd) { + FD_CLR(nd->socket->fd, &writeSet); +} + +void +NetManager_activateExceptionCallback(NetDescriptor *nd) { + FD_SET(nd->socket->fd, &exceptionSet); +} + +void +NetManager_deactivateExceptionCallback(NetDescriptor *nd) { + FD_CLR(nd->socket->fd, &exceptionSet); +} + +// This function may be called again from inside a callback function +// triggered by this function. BUG: This may result in callbacks being +// called multiple times. +// This function should however not be called from multiple threads at once. +int +NetManager_process(uint32 *timeoutMs) { + struct timeval timeout; + size_t i; + int selectResult; + fd_set newReadSet; + fd_set newWriteSet; + fd_set newExceptionSet; + bool bitSet; + + timeout.tv_sec = *timeoutMs / 1000; + timeout.tv_usec = (*timeoutMs % 1000) * 1000; + + // Structure assignment: + newReadSet = readSet; + newWriteSet = writeSet; + newExceptionSet = exceptionSet; + + do { + selectResult = select(NDIndex_getSelectNumND(), + &newReadSet, &newWriteSet, &newExceptionSet, &timeout); + // BUG: If select() is restarted because of EINTR, the timeout + // may start over. (Linux changes 'timeout' to the time left, + // but most other platforms don't.) + } while (selectResult == -1 && errno == EINTR); + if (selectResult == -1) { + int savedErrno = errno; + log_add(log_Error, "select() failed: %s.", strerror(errno)); + errno = savedErrno; + *timeoutMs = (timeout.tv_sec * 1000) + (timeout.tv_usec / 1000); + // XXX: rounding microseconds down. Is that the correct + // thing to do? + return -1; + } + + for (i = 0; i < maxND; i++) { + NetDescriptor *nd; + + if (selectResult == 0) { + // No more bits set in the fd_sets + break; + } + + nd = NDIndex_getNDForSocketFd(i); + if (nd == NULL) + continue; + + bitSet = false; + // Is one of the bits in the fd_sets set? + + // A callback may cause a NetDescriptor to be closed. The deletion + // of the structure will be scheduled, but will still be + // available at least until this function returns. + + if (FD_ISSET(i, &newExceptionSet)) + { + bool closed; + bitSet = true; + closed = NetManager_doExceptionCallback(nd); + if (closed) + goto next; + } + + if (FD_ISSET(i, &newWriteSet)) + { + bool closed; + bitSet = true; + closed = NetManager_doWriteCallback(nd); + if (closed) + goto next; + } + + if (FD_ISSET(i, &newReadSet)) + { + bool closed; + bitSet = true; + closed = NetManager_doReadCallback(nd); + if (closed) + goto next; + } + +next: + if (bitSet) + selectResult--; + } + + *timeoutMs = (timeout.tv_sec * 1000) + (timeout.tv_usec / 1000); + // XXX: rounding microseconds down. Is that the correct + // thing to do? + return 0; +} + + + diff --git a/src/libs/network/netmanager/netmanager_bsd.h b/src/libs/network/netmanager/netmanager_bsd.h new file mode 100644 index 0000000..b94dd69 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_bsd.h @@ -0,0 +1,26 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_NETMANAGER_NETMANAGER_BSD_H_ +#define LIBS_NETWORK_NETMANAGER_NETMANAGER_BSD_H_ + +typedef struct SocketManagementDataBsd SocketManagementDataBsd; +typedef SocketManagementDataBsd SocketManagementData; + +#endif /* LIBS_NETWORK_NETMANAGER_NETMANAGER_BSD_H_ */ + diff --git a/src/libs/network/netmanager/netmanager_common.ci b/src/libs/network/netmanager/netmanager_common.ci new file mode 100644 index 0000000..058e739 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_common.ci @@ -0,0 +1,58 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// This file is part of netmanager_bsd.ci and netmanager_win.ci, +// from where it is #included. + +static bool +NetManager_doReadCallback(NetDescriptor *nd) { + bool closed; + + assert(nd->readCallback != NULL); + NetDescriptor_incRef(nd); + (*nd->readCallback)(nd); + closed = nd->flags.closed; + NetDescriptor_decRef(nd); + return closed; +} + +static bool +NetManager_doWriteCallback(NetDescriptor *nd) { + bool closed; + + assert(nd->writeCallback != NULL); + NetDescriptor_incRef(nd); + (*nd->writeCallback)(nd); + closed = nd->flags.closed; + NetDescriptor_decRef(nd); + return closed; +} + +static bool +NetManager_doExceptionCallback(NetDescriptor *nd) { + bool closed; + + assert(nd->exceptionCallback != NULL); + NetDescriptor_incRef(nd); + (*nd->exceptionCallback)(nd); + closed = nd->flags.closed; + NetDescriptor_decRef(nd); + return closed; +} + + diff --git a/src/libs/network/netmanager/netmanager_win.c b/src/libs/network/netmanager/netmanager_win.c new file mode 100644 index 0000000..f146732 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_win.c @@ -0,0 +1,464 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" +#include "../netport.h" + +#define NETMANAGER_INTERNAL +#define NETDESCRIPTOR_INTERNAL +#define SOCKET_INTERNAL +#include "netmanager_win.h" +#include "../socket/socket.h" + +#include "ndesc.h" +#include "types.h" +#include "libs/misc.h" +#include "libs/log.h" + +#include <assert.h> +#include <winsock2.h> + +#include "netmanager_common.ci" + +int closeWSAEvent(WSAEVENT event); + + +// The elements of the following arrays with the same index belong to +// eachother. +#define MAX_SOCKETS WSA_MAXIMUM_WAIT_EVENTS + // We cannot have more sockets than we can have events, as + // all may need an event at some point. +static NetDescriptor *netDescriptors[MAX_SOCKETS]; +static WSAEVENT events[WSA_MAXIMUM_WAIT_EVENTS]; +static size_t numSockets; +static size_t numActiveEvents; +// For each NetDescriptor registered with the NetManager, an event +// is created. Only the first numActiveEvents will be processed though. +// Inv: numActiveEvents <= numSockets +// Inv: for all i: 0 <= i < numActiveEvents: events[i] is active +// (events[i] being active also means netDescriptor[i]->smd->eventMask +// != 0) +// Inv: for all i: 0 <= i < numSockets: netDescriptor[i]->smd->index == i + +void +NetManager_init(void) { + numActiveEvents = 0; +} + +void +NetManager_uninit(void) { + assert(numActiveEvents == 0); +} + +static inline SocketManagementDataWin * +SocketManagementData_alloc(void) { + return malloc(sizeof (SocketManagementDataWin)); +} + +static inline void +SocketManagementData_free(SocketManagementDataWin *smd) { + free(smd); +} + +// XXX: This function should be moved to some file with generic network +// functions. +int +closeWSAEvent(WSAEVENT event) { + for (;;) { + int error; + + if (WSACloseEvent(event)) + break; + + error = WSAGetLastError(); + if (error != WSAEINPROGRESS) { + log_add(log_Error, + "WSACloseEvent() failed with error code %d.", error); + errno = winsockErrorToErrno(error); + return -1; + } + } + return 0; +} + +// Register the NetDescriptor with the NetManager. +int +NetManager_addDesc(NetDescriptor *nd) { + long eventMask = 0; + WSAEVENT event; + + if (numSockets >= WSA_MAXIMUM_WAIT_EVENTS) { + errno = EMFILE; + return -1; + } + + if (nd->readCallback != NULL) + eventMask |= FD_READ | FD_ACCEPT; + + if (nd->writeCallback != NULL) + eventMask |= FD_WRITE /* | FD_CONNECT */; + + if (nd->exceptionCallback != NULL) + eventMask |= FD_OOB; + + eventMask |= FD_CLOSE; + + event = WSACreateEvent(); + if (event == WSA_INVALID_EVENT) { + errno = getWinsockErrno(); + return -1; + } + + nd->smd = SocketManagementData_alloc(); + if (eventMask != 0) { + // XXX: This guard is now always true, because of FD_CLOSE. + // This means that numActiveEvents will always be equal to + // numEvents. + // Once I'm convinced this is the right way to go, + // I can remove some unnecessary code. + if (WSAEventSelect(nd->socket->sock, event, eventMask) == + SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + int closeStatus = closeWSAEvent(event); + if (closeStatus == -1) { + log_add(log_Fatal, "closeWSAEvent() failed: %s.", + strerror(errno)); + explode(); + } + SocketManagementData_free(nd->smd); + errno = savedErrno; + return -1; + } + + // Move existing socket for which there exists no event, so + // so that all sockets for which there exists an event are at + // the front of the array of netdescriptors. + if (numActiveEvents < numSockets) { + netDescriptors[numSockets] = netDescriptors[numActiveEvents]; + netDescriptors[numSockets]->smd->index = numSockets; + } + + nd->smd->index = numActiveEvents; + numActiveEvents++; + } else { + nd->smd->index = numSockets; + } + nd->smd->eventMask = eventMask; + + netDescriptors[nd->smd->index] = nd; + events[nd->smd->index] = event; + numSockets++; + + return 0; +} + +void +NetManager_removeDesc(NetDescriptor *nd) { + assert(nd->smd != NULL); + assert(nd->smd->index < numSockets); + assert(nd == netDescriptors[nd->smd->index]); + + { + int closeStatus = closeWSAEvent(events[nd->smd->index]); + if (closeStatus == -1) + explode(); + } + + if (nd->smd->index < numActiveEvents) { + size_t index = nd->smd->index; + if (index + 1 != numActiveEvents) { + // Keep the list of active events consecutive by filling + // the new hole with the last active event. + events[index] = events[numActiveEvents - 1]; + netDescriptors[index] = netDescriptors[numActiveEvents - 1]; + netDescriptors[index]->smd->index = index; + } + numActiveEvents--; + } + + SocketManagementData_free(nd->smd); + nd->smd = NULL; + + numSockets--; +} + +static void +swapSockets(int index1, int index2) { + NetDescriptor *tempNd; + WSAEVENT tempEvent; + + tempNd = netDescriptors[index2]; + tempEvent = events[index2]; + + netDescriptors[index2] = netDescriptors[index1]; + events[index2] = events[index1]; + netDescriptors[index2]->smd->index = index2; + + netDescriptors[index1] = tempNd; + events[index1] = tempEvent; + netDescriptors[index1]->smd->index = index1; +} + +static int +NetManager_updateEvent(NetDescriptor *nd) { + assert(nd == netDescriptors[nd->smd->index]); + + if (WSAEventSelect(nd->socket->sock, + events[nd->smd->index], nd->smd->eventMask) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + int closeStatus = closeWSAEvent(events[nd->smd->index]); + if (closeStatus == -1) { + log_add(log_Fatal, "closeWSAEvent() failed: %s.", + strerror(errno)); + explode(); + } + errno = savedErrno; + return -1; + } + + if (nd->smd->eventMask != 0) { + // There are some events that we are interested in. + if (nd->smd->index >= numActiveEvents) { + // Event was not yet active. + if (nd->smd->index != numActiveEvents) { + // Need to keep the active nds and events in the front of + // their arrays. + swapSockets(nd->smd->index, numActiveEvents); + } + numActiveEvents++; + } + } else { + // There are no events that we are interested in. + if (nd->smd->index < numActiveEvents) { + // Event was active. + if (nd->smd->index != numActiveEvents - 1) { + // Need to keep the active nds and events in the front of + // their arrays. + swapSockets(nd->smd->index, numActiveEvents - 1); + } + } + numActiveEvents--; + } + + return 0; +} + +static void +activateSomeCallback(NetDescriptor *nd, long eventMask) { + nd->smd->eventMask |= eventMask; + { + int status = NetManager_updateEvent(nd); + if (status == -1) { + log_add(log_Fatal, "NetManager_updateEvent() failed: %s.", + strerror(errno)); + explode(); + // TODO: better error handling. + } + } +} + +static void +deactivateSomeCallback(NetDescriptor *nd, long eventMask) { + nd->smd->eventMask &= ~eventMask; + { + int status = NetManager_updateEvent(nd); + if (status == -1) { + log_add(log_Fatal, "NetManager_updateEvent() failed: %s.", + strerror(errno)); + explode(); + // TODO: better error handling + } + } +} + +void +NetManager_activateReadCallback(NetDescriptor *nd) { + activateSomeCallback(nd, FD_READ | FD_ACCEPT); +} + +void +NetManager_deactivateReadCallback(NetDescriptor *nd) { + deactivateSomeCallback(nd, FD_READ | FD_ACCEPT); +} + +void +NetManager_activateWriteCallback(NetDescriptor *nd) { + activateSomeCallback(nd, FD_WRITE /* | FD_CONNECT */); +} + +void +NetManager_deactivateWriteCallback(NetDescriptor *nd) { + deactivateSomeCallback(nd, FD_WRITE /* | FD_CONNECT */); +} + +void +NetManager_activateExceptionCallback(NetDescriptor *nd) { + activateSomeCallback(nd, FD_OOB); +} + +void +NetManager_deactivateExceptionCallback(NetDescriptor *nd) { + deactivateSomeCallback(nd, FD_OOB); +} + +static inline int +NetManager_processEvent(size_t index) { + WSANETWORKEVENTS networkEvents; + int enumRes; + + enumRes = WSAEnumNetworkEvents(netDescriptors[index]->socket->sock, + events[index], &networkEvents); + if (enumRes == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + if (networkEvents.lNetworkEvents & FD_READ) { + bool closed; + if (networkEvents.iErrorCode[FD_READ_BIT] != 0) { + // No special handling is required; the callback function + // will try to do a recv() and will get the error then. + } + + closed = NetManager_doReadCallback(netDescriptors[index]); + if (closed) + goto closed; + } + if (networkEvents.lNetworkEvents & FD_WRITE) { + bool closed; + if (networkEvents.iErrorCode[FD_WRITE_BIT] != 0) { + // No special handling is required; the callback function + // will try to do a send() and will get the error then. + } + + closed = NetManager_doWriteCallback(netDescriptors[index]); + if (closed) + goto closed; + } + if (networkEvents.lNetworkEvents & FD_OOB) { + bool closed; + if (networkEvents.iErrorCode[FD_OOB_BIT] != 0) { + // No special handling is required; the callback function + // will get the error then when it tries to do a recv(). + } + + closed = NetManager_doExceptionCallback(netDescriptors[index]); + if (closed) + goto closed; + } + if (networkEvents.lNetworkEvents & FD_ACCEPT) { + // There is no specific accept callback (because the BSD sockets + // don't work with specific notification for accept); we use + // the read callback instead. + bool closed; + if (networkEvents.iErrorCode[FD_READ_BIT] != 0) { + // No special handling is required; the callback function + // will try to do an accept() and will get the error then. + } + + closed = NetManager_doReadCallback(netDescriptors[index]); + if (closed) + goto closed; + } +#if 0 + // No need for this. Windows also sets FD_WRITE in this case, and + // writability is what we check for anyhow. + if (networkEvents.lNetworkEvents & FD_CONNECT) { + // There is no specific connect callback (because the BSD sockets + // don't work with specific notification for connect); we use + // the write callback instead. + bool closed; + if (networkEvents.iErrorCode[FD_WRITE_BIT] != 0) { + // No special handling is required; the callback function + // should do getsockopt() with SO_ERROR to get the error. + } + + closed = NetManager_doWriteCallback(netDescriptors[index]); + if (closed) + goto closed; + } +#endif + if (networkEvents.lNetworkEvents & FD_CLOSE) { + // The close event is handled last, in case there was still + // data in the buffers which could be processed. + NetDescriptor_close(netDescriptors[index]); + goto closed; + } + +closed: /* No special actions required for now. */ + + return 0; +} + +// This function may be called again from inside a callback function +// triggered by this function. BUG: This may result in callbacks being +// called multiple times. +// This function should however not be called from multiple threads at once. +int +NetManager_process(uint32 *timeoutMs) { + DWORD timeoutTemp; + DWORD waitResult; + DWORD startEvent; + + timeoutTemp = (DWORD) *timeoutMs; + + // WSAWaitForMultipleEvents only reports events for one socket at a + // time. In order to have each socket checked once, we call it + // again after it has reported an event, but passing only the + // events not yet processed. The second time, the timeout will be set + // to 0, so it won't wait. + startEvent = 0; + while (startEvent < numActiveEvents) { + waitResult = WSAWaitForMultipleEvents(numActiveEvents - startEvent, + &events[startEvent], FALSE, timeoutTemp, FALSE); + + if (waitResult == WSA_WAIT_IO_COMPLETION) + continue; + + if (waitResult == WSA_WAIT_TIMEOUT) { + // No events waiting. + *timeoutMs = 0; + return 0; + } + + if (waitResult == WSA_WAIT_FAILED) { + errno = getWinsockErrno(); + *timeoutMs = timeoutTemp; + return -1; + } + + { + DWORD eventIndex = waitResult - WSA_WAIT_EVENT_0; + if (NetManager_processEvent((size_t) eventIndex) == -1) { + // errno is set + *timeoutMs = timeoutTemp; + return -1; + } + + // Check the rest of the sockets, but don't wait anymore. + startEvent += eventIndex + 1; + timeoutTemp = 0; + } + } + + *timeoutMs = timeoutTemp; + return 0; +} + + diff --git a/src/libs/network/netmanager/netmanager_win.h b/src/libs/network/netmanager/netmanager_win.h new file mode 100644 index 0000000..03d65e4 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_win.h @@ -0,0 +1,35 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_NETMANAGER_NETMANAGER_WIN_H_ +#define LIBS_NETWORK_NETMANAGER_NETMANAGER_WIN_H_ + +typedef struct SocketManagementDataWin SocketManagementDataWin; +typedef SocketManagementDataWin SocketManagementData; + +#ifdef NETMANAGER_INTERNAL +struct SocketManagementDataWin { + size_t index; + long eventMask; +}; +#endif /* NETMANAGER_INTERNAL */ + + +#endif /* LIBS_NETWORK_NETMANAGER_NETMANAGER_WIN_H_ */ + + diff --git a/src/libs/network/netport.c b/src/libs/network/netport.c new file mode 100644 index 0000000..bfd478c --- /dev/null +++ b/src/libs/network/netport.c @@ -0,0 +1,91 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" +#include "netport.h" + +#ifdef USE_WINSOCK +# include <winsock2.h> + +int +winsockErrorToErrno(int winsockError) { + switch (winsockError) { + case WSAEINTR: return EINTR; + case WSAEACCES: return EACCES; + case WSAEFAULT: return EFAULT; + case WSAEINVAL: return EINVAL; + case WSAEMFILE: return EMFILE; + case WSAEWOULDBLOCK: return EWOULDBLOCK; + case WSAEINPROGRESS: return EINPROGRESS; + case WSAEALREADY: return EALREADY; + case WSAENOTSOCK: return ENOTSOCK; + case WSAEDESTADDRREQ: return EDESTADDRREQ; + case WSAEMSGSIZE: return EMSGSIZE; + case WSAEPROTOTYPE: return EPROTOTYPE; + case WSAENOPROTOOPT: return ENOPROTOOPT; + case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT; + case WSAESOCKTNOSUPPORT: return ESOCKTNOSUPPORT; + case WSAEOPNOTSUPP: return EOPNOTSUPP; + case WSAEPFNOSUPPORT: return EPFNOSUPPORT; + case WSAEAFNOSUPPORT: return EAFNOSUPPORT; + case WSAEADDRINUSE: return EADDRINUSE; + case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL; + case WSAENETDOWN: return ENETDOWN; + case WSAENETUNREACH: return ENETUNREACH; + case WSAENETRESET: return ENETRESET; + case WSAECONNABORTED: return ECONNABORTED; + case WSAECONNRESET: return ECONNRESET; + case WSAENOBUFS: return ENOBUFS; + case WSAEISCONN: return EISCONN; + case WSAENOTCONN: return ENOTCONN; + case WSAESHUTDOWN: return ESHUTDOWN; + case WSAETIMEDOUT: return ETIMEDOUT; + case WSAECONNREFUSED: return ECONNREFUSED; + case WSAEHOSTDOWN: return EHOSTDOWN; + case WSAEHOSTUNREACH: return EHOSTUNREACH; + case WSAEPROCLIM: return EPROCLIM; + case WSASYSNOTREADY: return ENOSYS; + case WSAVERNOTSUPPORTED: return ENOSYS; + case WSANOTINITIALISED: return ENOSYS; + case WSAEDISCON: return ECONNRESET; + case WSATYPE_NOT_FOUND: return ENODATA; + case WSAHOST_NOT_FOUND: return ENODATA; + case WSATRY_AGAIN: return EAGAIN; + case WSANO_RECOVERY: return EIO; + case WSANO_DATA: return ENODATA; + case WSA_INVALID_HANDLE: return EBADF; + case WSA_INVALID_PARAMETER: return EINVAL; + case WSA_IO_INCOMPLETE: return EAGAIN; + case WSA_IO_PENDING: return EINPROGRESS; + case WSA_NOT_ENOUGH_MEMORY: return ENOMEM; + case WSA_OPERATION_ABORTED: return EINTR; + case WSAEINVALIDPROCTABLE: return ENOSYS; + case WSAEINVALIDPROVIDER: return ENOSYS; + case WSAEPROVIDERFAILEDINIT: return ENOSYS; + case WSASYSCALLFAILURE: return EIO; + default: return EIO; + } +} + +int +getWinsockErrno(void) { + return winsockErrorToErrno(WSAGetLastError()); +} +#endif /* defined (USE_WINSOCK) */ + diff --git a/src/libs/network/netport.h b/src/libs/network/netport.h new file mode 100644 index 0000000..ee99699 --- /dev/null +++ b/src/libs/network/netport.h @@ -0,0 +1,43 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_NETPORT_H_ +#define LIBS_NETWORK_NETPORT_H_ + +#include "port.h" + +#ifdef USE_WINSOCK +int winsockErrorToErrno(int winsockError); +int getWinsockErrno(void); +# define EAI_SYSTEM 0x02000001 + // Any value will do that doesn't conflict with an existing value. + +#ifdef __MINGW32__ +// MinGW does not have a working gai_strerror() yet. +static inline const char * +gai_strerror(int err) { + (void) err; + return "[gai_strerror() is not available on MinGW]"; +} +#endif /* defined(__MINGW32__) */ + +#endif + +#endif /* LIBS_NETWORK_NETPORT_H_ */ + + diff --git a/src/libs/network/network.h b/src/libs/network/network.h new file mode 100644 index 0000000..d8c596e --- /dev/null +++ b/src/libs/network/network.h @@ -0,0 +1,27 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_NETWORK_H_ +#define LIBS_NETWORK_NETWORK_H_ + +void Network_init(void); +void Network_uninit(void); + +#endif /* LIBS_NETWORK_NETWORK_H_ */ + + diff --git a/src/libs/network/network_bsd.c b/src/libs/network/network_bsd.c new file mode 100644 index 0000000..a213c4e --- /dev/null +++ b/src/libs/network/network_bsd.c @@ -0,0 +1,30 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "network.h" + +void +Network_init(void) { + // Nothing to do. +} + +void +Network_uninit(void) { + // Nothing to do. +} + diff --git a/src/libs/network/network_win.c b/src/libs/network/network_win.c new file mode 100644 index 0000000..a5c6abf --- /dev/null +++ b/src/libs/network/network_win.c @@ -0,0 +1,75 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netport.h" + +#include "network.h" + +#include "libs/misc.h" +#include "libs/log.h" + +#include <errno.h> +#include <winsock2.h> + +void +Network_init(void) { + WSADATA data; + int startupResult; + WORD requestVersion = MAKEWORD(2, 2); + + startupResult = WSAStartup(requestVersion, &data); + if (startupResult != 0) { + int savedErrno = winsockErrorToErrno(startupResult); + log_add(log_Fatal, "WSAStartup failed."); + errno = savedErrno; + explode(); + } + +#ifdef DEBUG + log_add(log_Debug, "Winsock version %d.%d found: \"%s\".", + LOBYTE(data.wHighVersion), HIBYTE(data.wHighVersion), + data.szDescription); + log_add(log_Debug, "Requesting to use Winsock version %d.%d, got " + "version %d.%d.", + LOBYTE(requestVersion), HIBYTE(requestVersion), + LOBYTE(data.wVersion), HIBYTE(data.wVersion)); +#endif + if (data.wVersion != requestVersion) { + log_add(log_Fatal, "Winsock version %d.%d presented, requested " + "%d.%d.", LOBYTE(data.wVersion), HIBYTE(data.wVersion), + LOBYTE(requestVersion), HIBYTE(requestVersion)); + (void) WSACleanup(); + // Ignoring errors; we're going to abort anyhow. + explode(); + } +} + +void +Network_uninit(void) { + int cleanupResult; + + cleanupResult = WSACleanup(); + if (cleanupResult == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Fatal, "WSACleanup failed."); + errno = savedErrno; + explode(); + } +} + + diff --git a/src/libs/network/socket/Makeinfo b/src/libs/network/socket/Makeinfo new file mode 100644 index 0000000..19e1637 --- /dev/null +++ b/src/libs/network/socket/Makeinfo @@ -0,0 +1,11 @@ +uqm_CFILES="socket.c" +uqm_HFILES="socket.h" + +if [ -n "$uqm_USE_WINSOCK" ]; then + uqm_CFILES="$uqm_CFILES socket_win.c" + uqm_HFILES="$uqm_HFILES socket_win.h" +else + uqm_CFILES="$uqm_CFILES socket_bsd.c" + uqm_HFILES="$uqm_CFILES socket_bsd.h" +fi + diff --git a/src/libs/network/socket/socket.c b/src/libs/network/socket/socket.c new file mode 100644 index 0000000..be7d601 --- /dev/null +++ b/src/libs/network/socket/socket.c @@ -0,0 +1,61 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "port.h" + +#define SOCKET_INTERNAL +#include "socket.h" + +#ifdef USE_WINSOCK +# include <winsock2.h> +#else +# include <sys/socket.h> +# include <netinet/in.h> +#endif + + +//////////////////////////////////////////////////////////////////////////// + + +const int protocolFamilyTranslation[] = { + /* .[PF_unspec] = */ PF_UNSPEC, + /* .[PF_inet] = */ PF_INET, + /* .[PF_inet6] = */ PF_INET6, +}; + +const int protocolTranslation[] = { + /* .[IPProto_tcp] = */ IPPROTO_TCP, + /* .[IPProto_udp] = */ IPPROTO_UDP, +}; + +const int socketTypeTranslation[] = { + /* .[Sock_stream] = */ SOCK_STREAM, + /* .[Sock_dgram] = */ SOCK_DGRAM, +}; + + +Socket * +Socket_open(ProtocolFamily domain, SocketType type, Protocol protocol) { + return Socket_openNative(protocolFamilyTranslation[domain], + socketTypeTranslation[type], protocolTranslation[protocol]); +} + + +//////////////////////////////////////////////////////////////////////////// + + diff --git a/src/libs/network/socket/socket.h b/src/libs/network/socket/socket.h new file mode 100644 index 0000000..5c75467 --- /dev/null +++ b/src/libs/network/socket/socket.h @@ -0,0 +1,99 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_SOCKET_SOCKET_H_ +#define LIBS_NETWORK_SOCKET_SOCKET_H_ + +typedef struct Socket Socket; +#define Socket_noSocket ((Socket *) NULL) + +#include "port.h" + +#ifdef USE_WINSOCK +# include "socket_win.h" +#else +# include "socket_bsd.h" +#endif + + +//////////////////////////////////////////////////////////////////////////// + + +// Defining our own types for protocol families and protocols instead of +// using the system defines, so that the layer using this API does not have +// to have anything to do with the system layer. + +typedef enum { + PF_unspec, + PF_inet, + PF_inet6, +} ProtocolFamily; +typedef ProtocolFamily AddressFamily; + +typedef enum { + IPProto_tcp, + IPProto_udp, +} Protocol; + +typedef enum { + Sock_stream, + Sock_dgram, +} SocketType; + +#ifdef SOCKET_INTERNAL +extern const int protocolFamilyTranslation[]; +#define addressFamilyTranslation protocolFamilyTranslation; +extern const int protocolTranslation[]; +extern const int socketTypeTranslation[]; +#endif + + +//////////////////////////////////////////////////////////////////////////// + + +Socket *Socket_open(ProtocolFamily domain, SocketType type, + Protocol protocol); +#ifdef SOCKET_INTERNAL +Socket *Socket_openNative(int domain, int type, int protocol); +#endif +int Socket_close(Socket *sock); + +int Socket_connect(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen); +int Socket_bind(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen); +int Socket_listen(Socket *sock, int backlog); +Socket *Socket_accept(Socket *sock, struct sockaddr *addr, socklen_t *addrLen); +ssize_t Socket_send(Socket *sock, const void *buf, size_t len, int flags); +ssize_t Socket_sendto(Socket *sock, const void *buf, size_t len, int flags, + const struct sockaddr *addr, socklen_t addrLen); +ssize_t Socket_recv(Socket *sock, void *buf, size_t len, int flags); +ssize_t Socket_recvfrom(Socket *sock, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromLen); + +int Socket_setNonBlocking(Socket *sock); +int Socket_setReuseAddr(Socket *sock); +int Socket_setNodelay(Socket *sock); +int Socket_setTOS(Socket *sock, int tos); +int Socket_setInteractive(Socket *sock); +int Socket_setInlineOOB(Socket *sock); +int Socket_setKeepAlive(Socket *sock); +int Socket_getError(Socket *sock, int *err); + +#endif /* LIBS_NETWORK_SOCKET_SOCKET_H_ */ + diff --git a/src/libs/network/socket/socket_bsd.c b/src/libs/network/socket/socket_bsd.c new file mode 100644 index 0000000..2bf25b8 --- /dev/null +++ b/src/libs/network/socket/socket_bsd.c @@ -0,0 +1,283 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Socket functions for BSD sockets. + +#define SOCKET_INTERNAL +#include "socket.h" + +#include "libs/log.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) +# include <netinet/in_systm.h> +# include <netinet/in.h> +#endif +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <unistd.h> +#include <fcntl.h> + + +static Socket * +Socket_alloc(void) { + return malloc(sizeof (Socket)); +} + +static void +Socket_free(Socket *sock) { + free(sock); +} + +Socket * +Socket_openNative(int domain, int type, int protocol) { + Socket *result; + int fd; + + fd = socket(domain, type, protocol); + if (fd == -1) { + // errno is set + return Socket_noSocket; + } + + result = Socket_alloc(); + result->fd = fd; + return result; +} + +int +Socket_close(Socket *sock) { + int closeResult; + + do { + closeResult = close(sock->fd); + if (closeResult == 0) { + Socket_free(sock); + return 0; + } + } while (errno == EINTR); + + return -1; +} + +int +Socket_connect(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen) { + int connectResult; + + do { + connectResult = connect(sock->fd, addr, addrLen); + } while (connectResult == -1 && errno == EINTR); + + return connectResult; +} + +int +Socket_bind(Socket *sock, const struct sockaddr *addr, socklen_t addrLen) { + return bind(sock->fd, addr, addrLen); +} + +int +Socket_listen(Socket *sock, int backlog) { + return listen(sock->fd, backlog); +} + +Socket * +Socket_accept(Socket *sock, struct sockaddr *addr, socklen_t *addrLen) { + int acceptResult; + socklen_t tempAddrLen; + + do { + tempAddrLen = *addrLen; + acceptResult = accept(sock->fd, addr, &tempAddrLen); + if (acceptResult != -1) { + Socket *result = Socket_alloc(); + result->fd = acceptResult; + *addrLen = tempAddrLen; + return result; + } + + } while (errno == EINTR); + + // errno is set + return Socket_noSocket; +} + +ssize_t +Socket_send(Socket *sock, const void *buf, size_t len, int flags) { + return send(sock->fd, buf, len, flags); +} + +ssize_t +Socket_sendto(Socket *sock, const void *buf, size_t len, int flags, + const struct sockaddr *addr, socklen_t addrLen) { + return sendto(sock->fd, buf, len, flags, addr, addrLen); +} + +ssize_t +Socket_recv(Socket *sock, void *buf, size_t len, int flags) { + return recv(sock->fd, buf, len, flags); +} + +ssize_t +Socket_recvfrom(Socket *sock, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromLen) { + return recvfrom(sock->fd, buf, len, flags, from, fromLen); +} + +int +Socket_setNonBlocking(Socket *sock) { + int flags; + + flags = fcntl(sock->fd, F_GETFL); + if (flags == -1) { + int savedErrno = errno; + log_add(log_Error, "Getting file descriptor flags of socket failed: " + "%s.", strerror(errno)); + errno = savedErrno; + return -1; + } + + if (fcntl(sock->fd, F_SETFL, flags | O_NONBLOCK) == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting non-blocking mode on socket failed: " + "%s.", strerror(errno)); + errno = savedErrno; + return -1; + } + + return 0; +} + +int +Socket_setReuseAddr(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof flag) + == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting socket reuse failed: %s.", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +// Send data as soon as it is available. Do not collect data to send at +// once. +int +Socket_setNodelay(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof flag) + == -1) { +#ifdef DEBUG + int savedErrno = errno; + log_add(log_Warning, "Disabling Nagle algorithm failed: %s.", + strerror(errno)); + errno = savedErrno; +#endif + return -1; + } + return 0; +} + +// 'tos' should be IPTOS_LOWDELAY, IPTOS_THROUGHPUT, IPTOS_THROUGHPUT, +// IPTOS_RELIABILITY, or IPTOS_MINCOST. +int +Socket_setTOS(Socket *sock, int tos) { + if (setsockopt(sock->fd, IPPROTO_IP, IP_TOS, &tos, sizeof tos) == -1) { +#ifdef DEBUG + int savedErrno = errno; + log_add(log_Warning, "Setting socket type-of-service failed: %s.", + strerror(errno)); + errno = savedErrno; +#endif + return -1; + } + return 0; +} + +// This function setups the socket for optimal configuration for an +// interactive connection. +int +Socket_setInteractive(Socket *sock) { + if (Socket_setNodelay(sock) == -1) { + // errno is set + return -1; + } + + if (Socket_setTOS(sock, IPTOS_LOWDELAY) == -1) { + // errno is set + return -1; + } + return 0; +} + +int +Socket_setInlineOOB(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, SOL_SOCKET, SO_OOBINLINE, &flag, sizeof flag) + == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting inline OOB on socket failed: %s", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_setKeepAlive(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, IPPROTO_TCP, SO_KEEPALIVE, &flag, sizeof flag) + == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting keep-alive on socket failed: %s", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_getError(Socket *sock, int *err) { + socklen_t errLen = sizeof(*err); + + if (getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, err, &errLen) == -1) { + // errno is set + return -1; + } + + assert(errLen == sizeof(*err)); + // err is set + return 0; +} + + diff --git a/src/libs/network/socket/socket_bsd.h b/src/libs/network/socket/socket_bsd.h new file mode 100644 index 0000000..8019c01 --- /dev/null +++ b/src/libs/network/socket/socket_bsd.h @@ -0,0 +1,33 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_SOCKET_SOCKET_BSD_H_ +#define LIBS_NETWORK_SOCKET_SOCKET_BSD_H_ + +#include "types.h" +#include <sys/socket.h> + +#ifdef SOCKET_INTERNAL +struct Socket { + int fd; +}; +#endif /* SOCKET_INTERNAL */ + + +#endif /* LIBS_NETWORK_SOCKET_SOCKET_BSD_H_ */ + diff --git a/src/libs/network/socket/socket_win.c b/src/libs/network/socket/socket_win.c new file mode 100644 index 0000000..e1ed002 --- /dev/null +++ b/src/libs/network/socket/socket_win.c @@ -0,0 +1,314 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Socket functions for Winsock sockets. + +#define PORT_WANT_ERRNO +#include "port.h" +#include "../netport.h" + +#define SOCKET_INTERNAL +#include "socket.h" + +#include "libs/log.h" + +#include <assert.h> +#include <errno.h> +#include <winsock2.h> + + +Socket * +Socket_alloc(void) { + return malloc(sizeof (Socket)); +} + +void +Socket_free(Socket *sock) { + free(sock); +} + +Socket * +Socket_openNative(int domain, int type, int protocol) { + Socket *result; + SOCKET sock; + + sock = socket(domain, type, protocol); + if (sock == INVALID_SOCKET) { + errno = getWinsockErrno(); + return Socket_noSocket; + } + + result = Socket_alloc(); + result->sock = sock; + return result; +} + +int +Socket_close(Socket *sock) { + int closeResult; + + do { + closeResult = closesocket(sock->sock); + if (closeResult != SOCKET_ERROR) { + Socket_free(sock); + return 0; + } + + errno = getWinsockErrno(); + } while (errno == EINTR); + + return -1; +} + +int +Socket_connect(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen) { + int connectResult; + + do { + connectResult = connect(sock->sock, addr, addrLen); + if (connectResult == 0) + return 0; + + errno = getWinsockErrno(); + } while (errno == EINTR); + + if (errno == EWOULDBLOCK) { + // Windows returns (WSA)EWOULDBLOCK when a connection is being + // initiated on a non-blocking socket, while other platforms + // use EINPROGRESS in such cases. + errno = EINPROGRESS; + } + + return -1; +} + +int +Socket_bind(Socket *sock, const struct sockaddr *addr, socklen_t addrLen) { + int bindResult; + + bindResult = bind(sock->sock, addr, addrLen); + if (bindResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return 0; +} + +int +Socket_listen(Socket *sock, int backlog) { + int listenResult; + + listenResult = listen(sock->sock, backlog); + if (listenResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return 0; +} + +Socket * +Socket_accept(Socket *sock, struct sockaddr *addr, socklen_t *addrLen) { + SOCKET acceptResult; + socklen_t tempAddrLen; + + do { + tempAddrLen = *addrLen; + acceptResult = accept(sock->sock, addr, &tempAddrLen); + if (acceptResult != INVALID_SOCKET) { + Socket *result = Socket_alloc(); + result->sock = acceptResult; + *addrLen = tempAddrLen; + return result; + } + + errno = getWinsockErrno(); + } while (errno == EINTR); + + // errno is set + return Socket_noSocket; +} + +ssize_t +Socket_send(Socket *sock, const void *buf, size_t len, int flags) { + int sendResult; + + sendResult = send(sock->sock, buf, len, flags); + if (sendResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return sendResult; +} + +ssize_t +Socket_sendto(Socket *sock, const void *buf, size_t len, int flags, + const struct sockaddr *addr, socklen_t addrLen) { + int sendResult; + + sendResult = sendto(sock->sock, buf, len, flags, addr, addrLen); + if (sendResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return sendResult; +} + +ssize_t +Socket_recv(Socket *sock, void *buf, size_t len, int flags) { + int recvResult; + + recvResult = recv(sock->sock, buf, len, flags); + if (recvResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return recvResult; +} + +ssize_t +Socket_recvfrom(Socket *sock, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromLen) { + int recvResult; + + recvResult = recvfrom(sock->sock, buf, len, flags, from, fromLen); + if (recvResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return recvResult; +} + +int +Socket_setNonBlocking(Socket *sock) { + unsigned long flag = 1; + + if (ioctlsocket(sock->sock, FIONBIO, &flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting non-block mode on socket failed: %s.", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_setReuseAddr(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, + (const char *) &flag, sizeof flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting socket reuse failed: %s.", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +// Send data as soon as it is available. Do not collect data to send at +// once. +int +Socket_setNodelay(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, IPPROTO_TCP, TCP_NODELAY, + (const char *) &flag, sizeof flag) == SOCKET_ERROR) { +#ifdef DEBUG + int savedErrno = getWinsockErrno(); + log_add(log_Warning, "Disabling Nagle algorithm failed: %s.", + strerror(errno)); + errno = savedErrno; +#endif + return -1; + } + return 0; +} + +// This function setups the socket for optimal configuration for an +// interactive connection. +int +Socket_setInteractive(Socket *sock) { + if (Socket_setNodelay(sock) == -1) { + // errno is set + return -1; + } + +#if 0 + if (Socket_setTOS(sock, IPTOS_LOWDELAY) == -1) { + // errno is set + return -1; + } +#endif + return 0; +} + +int +Socket_setInlineOOB(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, SOL_SOCKET, SO_OOBINLINE, (const char *) &flag, + sizeof flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting inline OOB on socket failed: %s", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_setKeepAlive(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, IPPROTO_TCP, SO_KEEPALIVE, + (const char *) &flag, sizeof flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting keep-alive on socket failed: %s", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_getError(Socket *sock, int *err) { + int errLen = sizeof(*err); + + if (getsockopt(sock->sock, SOL_SOCKET, SO_ERROR, (char *) err, &errLen) + == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + assert(errLen == sizeof(*err)); + // err is set + return 0; +} + + diff --git a/src/libs/network/socket/socket_win.h b/src/libs/network/socket/socket_win.h new file mode 100644 index 0000000..ed1674c --- /dev/null +++ b/src/libs/network/socket/socket_win.h @@ -0,0 +1,34 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_SOCKET_SOCKET_WIN_H_ +#define LIBS_NETWORK_SOCKET_SOCKET_WIN_H_ + +#ifdef SOCKET_INTERNAL +#include <winsock2.h> +struct Socket { + SOCKET sock; +}; +#endif /* SOCKET_INTERNAL */ + +typedef int socklen_t; +struct sockaddr; + +#endif /* LIBS_NETWORK_SOCKET_SOCKET_WIN_H_ */ + + diff --git a/src/libs/network/wspiapiwrap.c b/src/libs/network/wspiapiwrap.c new file mode 100644 index 0000000..2eddeb4 --- /dev/null +++ b/src/libs/network/wspiapiwrap.c @@ -0,0 +1,34 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Only used for MinGW + +// HACK. MinGW misses some functionality, so we're #including +// the actual Windows wspiapi.h file. Because that file includes +// inline functions that aren't static, it can only be included +// once per executable when using gcc (for MSVC this is apparently +// not a problem), so this file is it. The prototypes of these +// functions are added to wspiapiwrap.h + +#include "netport.h" + +#if defined(USE_WINSOCK) && defined(__MINGW32__) +# include <ws2tcpip.h> +# include <wspiapi.h> +#endif + diff --git a/src/libs/network/wspiapiwrap.h b/src/libs/network/wspiapiwrap.h new file mode 100644 index 0000000..23361a4 --- /dev/null +++ b/src/libs/network/wspiapiwrap.h @@ -0,0 +1,33 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBS_NETWORK_WSPIAPIWRAP_H_ +#define LIBS_NETWORK_WSPIAPIWRAP_H_ + +// HACK. See wspiapiwrap.c +# define getaddrinfo WspiapiGetAddrInfo +# define getnameinfo WspiapiGetNameInfo +# define freeaddrinfo WspiapiFreeAddrInfo +void WINAPI WspiapiFreeAddrInfo (struct addrinfo *ai); +int WINAPI WspiapiGetAddrInfo(const char *nodename, const char *servname, + const struct addrinfo *hints, struct addrinfo **res); +int WINAPI WspiapiGetNameInfo (const struct sockaddr *sa, socklen_t salen, + char *host, size_t hostlen, char *serv, size_t servlen, int flags); + +#endif /* LIBS_NETWORK_WSPIAPIWRAP_H_ */ + |