diff options
Diffstat (limited to 'src/libs/network/connect')
-rw-r--r-- | src/libs/network/connect/Makeinfo | 2 | ||||
-rw-r--r-- | src/libs/network/connect/connect.c | 490 | ||||
-rw-r--r-- | src/libs/network/connect/connect.h | 111 | ||||
-rw-r--r-- | src/libs/network/connect/listen.c | 456 | ||||
-rw-r--r-- | src/libs/network/connect/listen.h | 106 | ||||
-rw-r--r-- | src/libs/network/connect/resolve.c | 211 | ||||
-rw-r--r-- | src/libs/network/connect/resolve.h | 109 |
7 files changed, 1485 insertions, 0 deletions
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_ */ + |