X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Futil%2Fnetwork.c;h=b5e65de60069ea5532610d93e6448dcded56a9a2;hb=c74002485dc595212d33c5e3ac490e3929331892;hp=021f5afaf83349fb51eff87bc927c53901293609;hpb=42c8b584e6e8554329ca6282545b775b63af9c39;p=oweals%2Fgnunet.git diff --git a/src/util/network.c b/src/util/network.c index 021f5afaf..b5e65de60 100644 --- a/src/util/network.c +++ b/src/util/network.c @@ -1,1237 +1,993 @@ -/* - This file is part of GNUnet. - (C) 2009 Christian Grothoff (and other contributing authors) - - GNUnet 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, or (at your - option) any later version. - - GNUnet 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 GNUnet; see the file COPYING. If not, write to the - Free Software Foundation, Inc., 59 Temple Place - Suite 330, - Boston, MA 02111-1307, USA. -*/ - -/** - * @file util/network/network.c - * @brief basic, low-level TCP networking interface - * @author Christian Grothoff - * - * This code is rather complex. Only modify it if you - * 1) Have a NEW testcase showing that the new code - * is needed and correct - * 2) All EXISTING testcases pass with the new code - * These rules should apply in general, but for this - * module they are VERY, VERY important. - * - * TODO: - * - can we merge receive_ready and receive_again? - * - can we integrate the nth.timeout_task with the write_task's timeout? - */ - -#include "platform.h" -#include "gnunet_common.h" -#include "gnunet_network_lib.h" -#include "gnunet_scheduler_lib.h" - -#define DEBUG_NETWORK GNUNET_NO - -struct GNUNET_NETWORK_TransmitHandle -{ - - /** - * Function to call if the send buffer has notify_size - * bytes available. - */ - GNUNET_NETWORK_TransmitReadyNotify notify_ready; - - /** - * Closure for notify_ready. - */ - void *notify_ready_cls; - - /** - * Our socket handle. - */ - struct GNUNET_NETWORK_SocketHandle *sh; - - /** - * Timeout for receiving (in absolute time). - */ - struct GNUNET_TIME_Absolute transmit_timeout; - - /** - * Task called on timeout. - */ - GNUNET_SCHEDULER_TaskIdentifier timeout_task; - - /** - * At what number of bytes available in the - * write buffer should the notify method be called? - */ - size_t notify_size; - -}; - -/** - * @brief handle for a network socket - */ -struct GNUNET_NETWORK_SocketHandle -{ - - /** - * Scheduler that was used for the connect task. - */ - struct GNUNET_SCHEDULER_Handle *sched; - - /** - * Address information for connect (may be NULL). - */ - struct addrinfo *ai; - - /** - * Index for the next struct addrinfo for connect attempts (may be NULL) - */ - struct addrinfo *ai_pos; - - /** - * Network address of the other end-point, may be NULL. - */ - struct sockaddr *addr; - - /** - * Pointer to our write buffer. - */ - char *write_buffer; - - /** - * Size of our write buffer. - */ - size_t write_buffer_size; - - /** - * Current write-offset in write buffer (where - * would we write next). - */ - size_t write_buffer_off; - - /** - * Current read-offset in write buffer (how many - * bytes have already been send). - */ - size_t write_buffer_pos; - - /** - * Length of addr. - */ - socklen_t addrlen; - - /** - * Connect task that we may need to wait for. - */ - GNUNET_SCHEDULER_TaskIdentifier connect_task; - - /** - * Read task that we may need to wait for. - */ - GNUNET_SCHEDULER_TaskIdentifier read_task; - - /** - * Write task that we may need to wait for. - */ - GNUNET_SCHEDULER_TaskIdentifier write_task; - - /** - * The handle we return for GNUNET_NETWORK_notify_transmit_ready. - */ - struct GNUNET_NETWORK_TransmitHandle nth; - - /** - * Underlying OS's socket, set to -1 after fatal errors. - */ - int sock; - - /** - * Port to connect to. - */ - uint16_t port; - - /** - * Function to call on data received, NULL - * if no receive is pending. - */ - GNUNET_NETWORK_Receiver receiver; - - /** - * Closure for receiver. - */ - void *receiver_cls; - - /** - * Timeout for receiving (in absolute time). - */ - struct GNUNET_TIME_Absolute receive_timeout; - - /** - * Maximum number of bytes to read - * (for receiving). - */ - size_t max; - -}; - - -/** - * Create a socket handle by boxing an existing OS socket. The OS - * socket should henceforth be no longer used directly. - * GNUNET_socket_destroy will close it. - * - * @param sched scheduler to use - * @param osSocket existing socket to box - * @param maxbuf maximum write buffer size for the socket (use - * 0 for sockets that need no write buffers, such as listen sockets) - * @return the boxed socket handle - */ -struct GNUNET_NETWORK_SocketHandle * -GNUNET_NETWORK_socket_create_from_existing (struct GNUNET_SCHEDULER_Handle - *sched, int osSocket, - size_t maxbuf) -{ - struct GNUNET_NETWORK_SocketHandle *ret; - ret = GNUNET_malloc (sizeof (struct GNUNET_NETWORK_SocketHandle) + maxbuf); - ret->write_buffer = (char *) &ret[1]; - ret->write_buffer_size = maxbuf; - ret->sock = osSocket; - ret->sched = sched; - return ret; -} - - -/** - * Create a socket handle by accepting on a listen socket. This - * function may block if the listen socket has no connection ready. - * - * @param sched scheduler to use - * @param access function to use to check if access is allowed - * @param access_cls closure for access - * @param lsock listen socket - * @param maxbuf maximum write buffer size for the socket (use - * 0 for sockets that need no write buffers, such as listen sockets) - * @return the socket handle, NULL on error - */ -struct GNUNET_NETWORK_SocketHandle * -GNUNET_NETWORK_socket_create_from_accept (struct GNUNET_SCHEDULER_Handle - *sched, - GNUNET_NETWORK_AccessCheck access, - void *access_cls, int lsock, - size_t maxbuf) -{ - struct GNUNET_NETWORK_SocketHandle *ret; - char addr[32]; - socklen_t addrlen; - int fd; - int aret; - struct sockaddr_in *v4; - struct sockaddr_in6 *v6; - struct sockaddr *sa; - void *uaddr; - - addrlen = sizeof (addr); - fd = accept (lsock, (struct sockaddr *) &addr, &addrlen); - if (fd == -1) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "accept"); - return NULL; - } -#ifndef MINGW - // FIXME NILS - if (0 != fcntl (fd, F_SETFD, fcntl (fd, F_GETFD) | FD_CLOEXEC)) - GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, - "fcntl"); -#endif - if (addrlen > sizeof (addr)) - { - GNUNET_break (0); - GNUNET_break (0 == CLOSE (fd)); - return NULL; - } - - sa = (struct sockaddr *) addr; - v6 = (struct sockaddr_in6 *) addr; - if ((sa->sa_family == AF_INET6) && (IN6_IS_ADDR_V4MAPPED (&v6->sin6_addr))) - { - /* convert to V4 address */ - v4 = GNUNET_malloc (sizeof (struct sockaddr_in)); - memset (v4, 0, sizeof (struct sockaddr_in)); - v4->sin_family = AF_INET; - memcpy (&v4->sin_addr, - &((char *) &v6->sin6_addr)[sizeof (struct in6_addr) - - sizeof (struct in_addr)], - sizeof (struct in_addr)); - v4->sin_port = v6->sin6_port; - uaddr = v4; - addrlen = sizeof (struct sockaddr_in); - } - else - { - uaddr = GNUNET_malloc (addrlen); - memcpy (uaddr, addr, addrlen); - } - - if ((access != NULL) && - (GNUNET_YES != (aret = access (access_cls, uaddr, addrlen)))) - { - if (aret == GNUNET_NO) - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _("Access denied to `%s'\n"), - GNUNET_a2s(uaddr, addrlen)); - GNUNET_break (0 == SHUTDOWN (fd, SHUT_RDWR)); - GNUNET_break (0 == CLOSE (fd)); - GNUNET_free (uaddr); - return NULL; - } -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _("Accepting connection from `%s'\n"), - GNUNET_a2s(uaddr, addrlen)); -#endif - ret = GNUNET_malloc (sizeof (struct GNUNET_NETWORK_SocketHandle) + maxbuf); - ret->write_buffer = (char *) &ret[1]; - ret->write_buffer_size = maxbuf; - ret->addr = uaddr; - ret->addrlen = addrlen; - ret->sock = fd; - ret->sched = sched; - return ret; -} - -/** - * Obtain the network address of the other party. - * - * @param sock the client to get the address for - * @param addr where to store the address - * @param addrlen where to store the length of the address - * @return GNUNET_OK on success - */ -int -GNUNET_NETWORK_socket_get_address (struct GNUNET_NETWORK_SocketHandle *sock, - void **addr, size_t * addrlen) -{ - if ((sock->addr == NULL) || (sock->addrlen == 0)) - return GNUNET_NO; - *addr = GNUNET_malloc (sock->addrlen); - memcpy (*addr, sock->addr, sock->addrlen); - *addrlen = sock->addrlen; - return GNUNET_OK; -} - - -/** - * Set if a socket should use blocking or non-blocking IO. - * - * @return GNUNET_OK on success, GNUNET_SYSERR on error - */ -static int -socket_set_blocking (int handle, int doBlock) -{ -#if MINGW - u_long mode; - mode = !doBlock; -#if HAVE_PLIBC_FD - if (ioctlsocket (plibc_fd_get_handle (handle), FIONBIO, &mode) == - SOCKET_ERROR) - { - SetErrnoFromWinsockError (WSAGetLastError ()); - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "ioctlsocket"); - return GNUNET_SYSERR; - } -#else - if (ioctlsocket (handle, FIONBIO, &mode) == SOCKET_ERROR) - { - SetErrnoFromWinsockError (WSAGetLastError ()); - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "ioctlsocket"); - return GNUNET_SYSERR; - } -#endif - /* store the blocking mode */ -#if HAVE_PLIBC_FD - plibc_fd_set_blocking (handle, doBlock); -#else - __win_SetHandleBlockingMode (handle, doBlock); -#endif - return GNUNET_OK; - -#else - /* not MINGW */ - int flags = fcntl (handle, F_GETFL); - if (flags == -1) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "fcntl"); - return GNUNET_SYSERR; - } - if (doBlock) - flags &= ~O_NONBLOCK; - else - flags |= O_NONBLOCK; - if (0 != fcntl (handle, F_SETFL, flags)) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "fcntl"); - return GNUNET_SYSERR; - } - return GNUNET_OK; -#endif -} - - -/** - * Initiate asynchronous TCP connect request. - * - * @param sock what socket to connect - * @return GNUNET_SYSERR error (no more addresses to try) - */ -static int -try_connect (struct GNUNET_NETWORK_SocketHandle *sock) -{ - int s; - - if (sock->addr != NULL) - { - GNUNET_free (sock->addr); - sock->addr = NULL; - sock->addrlen = 0; - } - while (1) - { - if (sock->ai_pos == NULL) - { - /* no more addresses to try, fatal! */ - return GNUNET_SYSERR; - } - switch (sock->ai_pos->ai_family) - { - case AF_INET: - ((struct sockaddr_in *) sock->ai_pos->ai_addr)->sin_port = - htons (sock->port); - break; - case AF_INET6: - ((struct sockaddr_in6 *) sock->ai_pos->ai_addr)->sin6_port = - htons (sock->port); - break; - default: - sock->ai_pos = sock->ai_pos->ai_next; - continue; - } - s = SOCKET (sock->ai_pos->ai_family, SOCK_STREAM, 0); - if (s == -1) - { - /* maybe unsupported address family, try next */ - GNUNET_log_strerror (GNUNET_ERROR_TYPE_INFO, "socket"); - sock->ai_pos = sock->ai_pos->ai_next; - continue; - } -#ifndef MINGW - // FIXME NILS - if (0 != fcntl (s, F_SETFD, fcntl (s, F_GETFD) | FD_CLOEXEC)) - GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, - "fcntl"); -#endif - if (GNUNET_SYSERR == socket_set_blocking (s, GNUNET_NO)) - { - /* we'll treat this one as fatal */ - GNUNET_break (0 == CLOSE (s)); - return GNUNET_SYSERR; - } -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _("Trying to connect to `%s'\n"), - GNUNET_a2s(sock->ai_pos->ai_addr, - sock->ai_pos->ai_addrlen)); -#endif - if ((0 != CONNECT (s, - sock->ai_pos->ai_addr, - sock->ai_pos->ai_addrlen)) && (errno != EINPROGRESS)) - { - /* maybe refused / unsupported address, try next */ - GNUNET_log_strerror (GNUNET_ERROR_TYPE_INFO, "connect"); - GNUNET_break (0 == CLOSE (s)); - sock->ai_pos = sock->ai_pos->ai_next; - continue; - } - break; - } - /* got one! copy address information! */ - sock->addrlen = sock->ai_pos->ai_addrlen; - sock->addr = GNUNET_malloc (sock->addrlen); - memcpy (sock->addr, sock->ai_pos->ai_addr, sock->addrlen); - sock->ai_pos = sock->ai_pos->ai_next; - sock->sock = s; - return GNUNET_OK; -} - - -/** - * Scheduler let us know that we're either ready to - * write on the socket OR connect timed out. Do the - * right thing. - */ -static void -connect_continuation (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - struct GNUNET_NETWORK_SocketHandle *sock = cls; - unsigned int len; - int error; - - /* nobody needs to wait for us anymore... */ - sock->connect_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; - /* Note: write-ready does NOT mean connect succeeded, - we need to use getsockopt to be sure */ - len = sizeof (error); - errno = 0; - error = 0; - if ((0 == (tc->reason & GNUNET_SCHEDULER_REASON_WRITE_READY)) || - (0 != getsockopt (sock->sock, SOL_SOCKET, SO_ERROR, &error, &len)) || - (error != 0) || (errno != 0)) - { -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Failed to establish TCP connection to `%s'\n", - GNUNET_a2s(sock->addr, sock->addrlen)); -#endif - /* connect failed / timed out */ - GNUNET_break (0 == CLOSE (sock->sock)); - sock->sock = -1; - if (GNUNET_SYSERR == try_connect (sock)) - { - /* failed for good */ -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Failed to establish TCP connection, no further addresses to try.\n"); -#endif - /* connect failed / timed out */ - GNUNET_break (sock->ai_pos == NULL); - freeaddrinfo (sock->ai); - sock->ai = NULL; - return; - } - sock->connect_task = GNUNET_SCHEDULER_add_write (tc->sched, GNUNET_NO, /* abort on shutdown */ - GNUNET_SCHEDULER_PRIORITY_KEEP, - GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, - GNUNET_NETWORK_CONNECT_RETRY_TIMEOUT, - sock->sock, - &connect_continuation, - sock); - return; - } - /* connect succeeded! clean up "ai" */ -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Connection to `%s' succeeded!\n", - GNUNET_a2s(sock->addr, sock->addrlen)); -#endif - freeaddrinfo (sock->ai); - sock->ai_pos = NULL; - sock->ai = NULL; -} - - -/** - * Create a socket handle by (asynchronously) connecting to a host. - * This function returns immediately, even if the connection has not - * yet been established. This function only creates TCP connections. - * - * @param sched scheduler to use - * @param hostname name of the host to connect to - * @param port port to connect to - * @param maxbuf maximum write buffer size for the socket (use - * 0 for sockets that need no write buffers, such as listen sockets) - * @return the socket handle - */ -struct GNUNET_NETWORK_SocketHandle * -GNUNET_NETWORK_socket_create_from_connect (struct GNUNET_SCHEDULER_Handle - *sched, const char *hostname, - uint16_t port, size_t maxbuf) -{ - struct GNUNET_NETWORK_SocketHandle *ret; - struct addrinfo hints; - int ec; - - ret = GNUNET_malloc (sizeof (struct GNUNET_NETWORK_SocketHandle) + maxbuf); - ret->sock = -1; - ret->sched = sched; - ret->write_buffer = (char *) &ret[1]; - ret->write_buffer_size = maxbuf; - ret->port = port; - memset (&hints, 0, sizeof (hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - if (0 != (ec = getaddrinfo (hostname, NULL, &hints, &ret->ai))) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK, - "`%s' failed for hostname `%s': %s\n", - "getaddrinfo", hostname, gai_strerror (ec)); - GNUNET_free (ret); - return NULL; - } - ret->ai_pos = ret->ai; - if (GNUNET_SYSERR == try_connect (ret)) - { - freeaddrinfo (ret->ai); - GNUNET_free (ret); - return NULL; - } - ret->connect_task = GNUNET_SCHEDULER_add_write (sched, GNUNET_NO, /* abort on shutdown */ - GNUNET_SCHEDULER_PRIORITY_KEEP, - GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, - GNUNET_NETWORK_CONNECT_RETRY_TIMEOUT, - ret->sock, - &connect_continuation, ret); - return ret; - -} - - -/** - * Create a socket handle by (asynchronously) connecting to a host. - * This function returns immediately, even if the connection has not - * yet been established. This function only creates TCP connections. - * - * @param sched scheduler to use - * @param af_family address family to use - * @param serv_addr server address - * @param addrlen length of server address - * @param maxbuf maximum write buffer size for the socket (use - * 0 for sockets that need no write buffers, such as listen sockets) - * @return the socket handle - */ -struct GNUNET_NETWORK_SocketHandle * -GNUNET_NETWORK_socket_create_from_sockaddr (struct GNUNET_SCHEDULER_Handle - *sched, int af_family, - const struct sockaddr *serv_addr, - socklen_t addrlen, size_t maxbuf) -{ - int s; - struct GNUNET_NETWORK_SocketHandle *ret; - - s = SOCKET (af_family, SOCK_STREAM, 0); - if (s == -1) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING | - GNUNET_ERROR_TYPE_BULK, "socket"); - return NULL; - } -#ifndef MINGW - if (0 != fcntl (s, F_SETFD, fcntl (s, F_GETFD) | FD_CLOEXEC)) - GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, - "fcntl"); -#endif - if (GNUNET_SYSERR == socket_set_blocking (s, GNUNET_NO)) - { - /* we'll treat this one as fatal */ - GNUNET_break (0 == CLOSE (s)); - return NULL; - } -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _("Trying to connect to `%s'\n"), - GNUNET_a2s(serv_addr, addrlen)); -#endif - if ((0 != CONNECT (s, serv_addr, addrlen)) && (errno != EINPROGRESS)) - { - /* maybe refused / unsupported address, try next */ - GNUNET_log_strerror (GNUNET_ERROR_TYPE_INFO, "connect"); - GNUNET_break (0 == CLOSE (s)); - return NULL; - } - ret = GNUNET_NETWORK_socket_create_from_existing (sched, s, maxbuf); - ret->addr = GNUNET_malloc (addrlen); - memcpy (ret->addr, serv_addr, addrlen); - ret->addrlen = addrlen; - return ret; -} - - -/** - * Check if socket is valid (no fatal errors have happened so far). - * Note that a socket that is still trying to connect is considered - * valid. - * - * @param sock socket to check - * @return GNUNET_YES if valid, GNUNET_NO otherwise - */ -int -GNUNET_NETWORK_socket_check (struct GNUNET_NETWORK_SocketHandle *sock) -{ - if (sock->ai != NULL) - return GNUNET_YES; /* still trying to connect */ - return (sock->sock == -1) ? GNUNET_NO : GNUNET_YES; -} - - -/** - * Scheduler let us know that the connect task is finished (or was - * cancelled due to shutdown). Now really clean up. - */ -static void -destroy_continuation (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - struct GNUNET_NETWORK_SocketHandle *sock = cls; - GNUNET_NETWORK_TransmitReadyNotify notify; - - if (sock->write_task != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) - { - GNUNET_SCHEDULER_add_after (sock->sched, - GNUNET_YES, - GNUNET_SCHEDULER_PRIORITY_KEEP, - sock->write_task, - &destroy_continuation, sock); - return; - } - if (sock->sock != -1) - { -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Shutting down socket.\n"); -#endif - SHUTDOWN (sock->sock, SHUT_RDWR); - } - if (sock->read_task != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) - { - GNUNET_SCHEDULER_add_after (sock->sched, - GNUNET_YES, - GNUNET_SCHEDULER_PRIORITY_KEEP, - sock->read_task, - &destroy_continuation, sock); - return; - } - if (NULL != (notify = sock->nth.notify_ready)) - { - sock->nth.notify_ready = NULL; - notify (sock->nth.notify_ready_cls, 0, NULL); - if (sock->nth.timeout_task != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) - { - GNUNET_SCHEDULER_cancel (sock->sched, sock->nth.timeout_task); - sock->nth.timeout_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; - } - } - if (sock->sock != -1) - GNUNET_break (0 == CLOSE (sock->sock)); - GNUNET_free_non_null (sock->addr); - if (sock->ai != NULL) - freeaddrinfo (sock->ai); - GNUNET_free (sock); -} - - -/** - * Close the socket and free associated resources. Pending - * transmissions are simply dropped. A pending receive call will be - * called with an error code of "EPIPE". - * - * @param sock socket to destroy - */ -void -GNUNET_NETWORK_socket_destroy (struct GNUNET_NETWORK_SocketHandle *sock) -{ - if (sock->write_buffer_off == 0) - sock->ai_pos = NULL; /* if we're still trying to connect and have - no message pending, stop trying! */ - GNUNET_assert (sock->sched != NULL); - GNUNET_SCHEDULER_add_after (sock->sched, - GNUNET_YES, - GNUNET_SCHEDULER_PRIORITY_KEEP, - sock->connect_task, - &destroy_continuation, sock); -} - -/** - * Tell the receiver callback that a timeout was reached. - */ -static void -signal_timeout (struct GNUNET_NETWORK_SocketHandle *sh) -{ - GNUNET_NETWORK_Receiver receiver; - -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Network signals time out to receiver!\n"); -#endif - GNUNET_assert (NULL != (receiver = sh->receiver)); - sh->receiver = NULL; - receiver (sh->receiver_cls, NULL, 0, NULL, 0, 0); -} - - -/** - * Tell the receiver callback that we had an IO error. - */ -static void -signal_error (struct GNUNET_NETWORK_SocketHandle *sh, int errcode) -{ - GNUNET_NETWORK_Receiver receiver; - GNUNET_assert (NULL != (receiver = sh->receiver)); - sh->receiver = NULL; - receiver (sh->receiver_cls, NULL, 0, sh->addr, sh->addrlen, errcode); -} - - -/** - * This function is called once we either timeout - * or have data ready to read. - */ -static void -receive_ready (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - struct GNUNET_NETWORK_SocketHandle *sh = cls; - struct GNUNET_TIME_Absolute now; - char buffer[sh->max]; - ssize_t ret; - GNUNET_NETWORK_Receiver receiver; - - sh->read_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; - now = GNUNET_TIME_absolute_get (); - if ((now.value > sh->receive_timeout.value) || - (0 != (tc->reason & GNUNET_SCHEDULER_REASON_TIMEOUT)) || - (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))) - { -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Receive encounters error: time out...\n"); -#endif - signal_timeout (sh); - return; - } - if (sh->sock == -1) - { - /* connect failed for good */ -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Receive encounters error, socket closed...\n"); -#endif - signal_error (sh, ECONNREFUSED); - return; - } - GNUNET_assert (FD_ISSET (sh->sock, tc->read_ready)); -RETRY: - ret = RECV (sh->sock, buffer, sh->max, -#ifndef MINGW - // FIXME MINGW - MSG_DONTWAIT -#else - 0 -#endif - ); - if (ret == -1) - { - if (errno == EINTR) - goto RETRY; -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Error receiving: %s\n", STRERROR (errno)); -#endif - signal_error (sh, errno); - return; - } -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "receive_ready read %u/%u bytes from `%s'!\n", - (unsigned int) ret, - sh->max, - GNUNET_a2s(sh->addr, sh->addrlen)); -#endif - GNUNET_assert (NULL != (receiver = sh->receiver)); - sh->receiver = NULL; - receiver (sh->receiver_cls, buffer, ret, sh->addr, sh->addrlen, 0); -} - - -/** - * This function is called after establishing a connection either has - * succeeded or timed out. Note that it is possible that the attempt - * timed out and that we're immediately retrying. If we are retrying, - * we need to wait again (or timeout); if we succeeded, we need to - * wait for data (or timeout). - */ -static void -receive_again (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - struct GNUNET_NETWORK_SocketHandle *sh = cls; - struct GNUNET_TIME_Absolute now; - - sh->read_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; - if ((sh->sock == -1) && - (sh->connect_task == GNUNET_SCHEDULER_NO_PREREQUISITE_TASK)) - { - /* not connected and no longer trying */ -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Receive encounters error, socket closed...\n"); -#endif - signal_error (sh, ECONNREFUSED); - return; - } - now = GNUNET_TIME_absolute_get (); - if ((now.value > sh->receive_timeout.value) || - (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))) - { -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Receive encounters error: time out...\n"); -#endif - signal_timeout (sh); - return; - } - if (sh->connect_task != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) - { - /* connect was retried */ - sh->read_task = GNUNET_SCHEDULER_add_after (tc->sched, - GNUNET_YES, - GNUNET_SCHEDULER_PRIORITY_KEEP, - sh->connect_task, - &receive_again, sh); - return; - } - /* connect succeeded, wait for data! */ - sh->read_task = GNUNET_SCHEDULER_add_read (tc->sched, - GNUNET_YES, - GNUNET_SCHEDULER_PRIORITY_KEEP, - sh->connect_task, - GNUNET_TIME_absolute_get_remaining - (sh->receive_timeout), - sh->sock, &receive_ready, - sh); -} - - -/** - * Receive data from the given socket. Note that this function will - * call "receiver" asynchronously using the scheduler. It will - * "immediately" return. Note that there MUST only be one active - * receive call per socket at any given point in time (so do not - * call receive again until the receiver callback has been invoked). - * - * @param sched scheduler to use - * @param sock socket handle - * @param max maximum number of bytes to read - * @param timeout maximum amount of time to wait (use -1 for "forever") - * @param receiver function to call with received data - * @param receiver_cls closure for receiver - * @return scheduler task ID used for receiving, GNUNET_SCHEDULER_NO_PREREQUISITE_TASK on error - */ -GNUNET_SCHEDULER_TaskIdentifier -GNUNET_NETWORK_receive (struct GNUNET_NETWORK_SocketHandle *sock, - size_t max, - struct GNUNET_TIME_Relative timeout, - GNUNET_NETWORK_Receiver receiver, void *receiver_cls) -{ - struct GNUNET_SCHEDULER_TaskContext tc; - - GNUNET_assert ((sock->read_task == GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) && - (sock->receiver == NULL)); - sock->receiver = receiver; - sock->receiver_cls = receiver_cls; - sock->receive_timeout = GNUNET_TIME_relative_to_absolute (timeout); - sock->max = max; - memset (&tc, 0, sizeof (tc)); - tc.sched = sock->sched; - tc.reason = GNUNET_SCHEDULER_REASON_PREREQ_DONE; - receive_again (sock, &tc); - return sock->read_task; -} - - -/** - * Cancel receive job on the given socket. Note that the - * receiver callback must not have been called yet in order - * for the cancellation to be valid. - * - * @param sock socket handle - * @param task task identifier returned from the receive call - * @return closure of the original receiver callback - */ -void * -GNUNET_NETWORK_receive_cancel (struct GNUNET_NETWORK_SocketHandle *sock, - GNUNET_SCHEDULER_TaskIdentifier task) -{ - GNUNET_assert (sock->read_task == task); - GNUNET_assert (sock == GNUNET_SCHEDULER_cancel (sock->sched, task)); - sock->read_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; - sock->receiver = NULL; - return sock->receiver_cls; -} - - -/** - * Try to call the transmit notify method (check if we do - * have enough space available first)! - * - * @param sock socket for which we should do this processing - * @return GNUNET_YES if we were able to call notify - */ -static int -process_notify (struct GNUNET_NETWORK_SocketHandle *sock) -{ - size_t used; - size_t avail; - size_t size; - GNUNET_NETWORK_TransmitReadyNotify notify; - - GNUNET_assert (sock->write_task == GNUNET_SCHEDULER_NO_PREREQUISITE_TASK); - if (NULL == (notify = sock->nth.notify_ready)) - return GNUNET_NO; - used = sock->write_buffer_off - sock->write_buffer_pos; - avail = sock->write_buffer_size - used; - size = sock->nth.notify_size; - if (sock->nth.notify_size > avail) - return GNUNET_NO; - sock->nth.notify_ready = NULL; - if (sock->nth.timeout_task != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) - { - GNUNET_SCHEDULER_cancel (sock->sched, sock->nth.timeout_task); - sock->nth.timeout_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; - } - if (sock->write_buffer_size - sock->write_buffer_off < size) - { - /* need to compact */ - memmove (sock->write_buffer, - &sock->write_buffer[sock->write_buffer_pos], used); - sock->write_buffer_off -= sock->write_buffer_pos; - sock->write_buffer_pos = 0; - } - GNUNET_assert (sock->write_buffer_size - sock->write_buffer_off >= size); - size = notify (sock->nth.notify_ready_cls, - sock->write_buffer_size - sock->write_buffer_off, - &sock->write_buffer[sock->write_buffer_off]); - sock->write_buffer_off += size; - return GNUNET_YES; -} - - -/** - * Task invoked by the scheduler when a call to transmit - * is timing out (we never got enough buffer space to call - * the callback function before the specified timeout - * expired). - * - * This task notifies the client about the timeout. - */ -static void -transmit_timeout (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - struct GNUNET_NETWORK_SocketHandle *sock = cls; - GNUNET_NETWORK_TransmitReadyNotify notify; - -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Transmit fails, time out reached.\n"); -#endif - notify = sock->nth.notify_ready; - sock->nth.notify_ready = NULL; - notify (sock->nth.notify_ready_cls, 0, NULL); -} - - -static void -transmit_error (struct GNUNET_NETWORK_SocketHandle *sock) -{ - if (sock->nth.notify_ready == NULL) - return; /* nobody to tell about it */ - if (sock->nth.timeout_task != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) - { - GNUNET_SCHEDULER_cancel (sock->sched, sock->nth.timeout_task); - sock->nth.timeout_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; - } - transmit_timeout (sock, NULL); -} - - -/** - * See if we are now connected. If not, wait longer for - * connect to succeed. If connected, we should be able - * to write now as well, unless we timed out. - */ -static void -transmit_ready (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - struct GNUNET_NETWORK_SocketHandle *sock = cls; - ssize_t ret; - size_t have; - - GNUNET_assert (sock->write_task != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK); - sock->write_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; - if (sock->connect_task != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) - { - /* still waiting for connect */ - GNUNET_assert (sock->write_task == - GNUNET_SCHEDULER_NO_PREREQUISITE_TASK); - sock->write_task = - GNUNET_SCHEDULER_add_delayed (tc->sched, GNUNET_NO, - GNUNET_SCHEDULER_PRIORITY_KEEP, - sock->connect_task, - GNUNET_TIME_UNIT_ZERO, &transmit_ready, - sock); - return; - } - if (sock->sock == -1) - { -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _("Could not satisfy pending transmission request, socket closed or connect failed.\n")); -#endif - transmit_error (sock); - return; /* connect failed for good, we're finished */ - } - if ((tc->write_ready == NULL) || (!FD_ISSET (sock->sock, tc->write_ready))) - { - /* special circumstances: not yet ready to write */ - goto SCHEDULE_WRITE; - } - GNUNET_assert (sock->write_buffer_off >= sock->write_buffer_pos); - process_notify (sock); - have = sock->write_buffer_off - sock->write_buffer_pos; - if (have == 0) - { - /* no data ready for writing, terminate write loop */ - return; - } -RETRY: - ret = SEND (sock->sock, - &sock->write_buffer[sock->write_buffer_pos], - have, -#ifndef MINGW - // FIXME NILS - MSG_DONTWAIT | MSG_NOSIGNAL -#else - 0 -#endif - ); - if (ret == -1) - { - if (errno == EINTR) - goto RETRY; -#if DEBUG_NETWORK - GNUNET_log_strerror (GNUNET_ERROR_TYPE_DEBUG, "send"); -#endif - SHUTDOWN (sock->sock, SHUT_RDWR); - GNUNET_break (0 == CLOSE (sock->sock)); - sock->sock = -1; - transmit_error (sock); - return; - } -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "transmit_ready transmitted %u/%u bytes to `%s'\n", - (unsigned int) ret, - have, - GNUNET_a2s(sock->addr, sock->addrlen)); -#endif - sock->write_buffer_pos += ret; - if (sock->write_buffer_pos == sock->write_buffer_off) - { - /* transmitted all pending data */ - sock->write_buffer_pos = 0; - sock->write_buffer_off = 0; - } - if ((sock->write_buffer_off == 0) && (NULL == sock->nth.notify_ready)) - return; /* all data sent! */ - /* not done writing, schedule more */ -SCHEDULE_WRITE: - if (sock->write_task == GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) - sock->write_task = - GNUNET_SCHEDULER_add_write (tc->sched, - GNUNET_NO, - GNUNET_SCHEDULER_PRIORITY_KEEP, - GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, - GNUNET_TIME_UNIT_FOREVER_REL, - sock->sock, &transmit_ready, sock); -} - - -/** - * Ask the socket to call us once the specified number of bytes - * are free in the transmission buffer. May call the notify - * method immediately if enough space is available. - * - * @param sock socket - * @param size number of bytes to send - * @param timeout after how long should we give up (and call - * notify with buf NULL and size 0)? - * @param notify function to call - * @param notify_cls closure for notify - * @return non-NULL if the notify callback was queued, - * NULL if we are already going to notify someone else (busy) - */ -struct GNUNET_NETWORK_TransmitHandle * -GNUNET_NETWORK_notify_transmit_ready (struct GNUNET_NETWORK_SocketHandle - *sock, size_t size, - struct GNUNET_TIME_Relative timeout, - GNUNET_NETWORK_TransmitReadyNotify - notify, void *notify_cls) -{ - if (sock->nth.notify_ready != NULL) - return NULL; - GNUNET_assert (notify != NULL); - GNUNET_assert (sock->write_buffer_size >= size); - - if ((sock->sock == -1) && - (sock->connect_task == GNUNET_SCHEDULER_NO_PREREQUISITE_TASK)) - { -#if DEBUG_NETWORK - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Transmission request fails, connection failed.\n"); -#endif - notify (notify_cls, 0, NULL); - return &sock->nth; - } - GNUNET_assert (sock->write_buffer_off <= sock->write_buffer_size); - GNUNET_assert (sock->write_buffer_pos <= sock->write_buffer_size); - GNUNET_assert (sock->write_buffer_pos <= sock->write_buffer_off); - sock->nth.notify_ready = notify; - sock->nth.notify_ready_cls = notify_cls; - sock->nth.sh = sock; - sock->nth.notify_size = size; - sock->nth.transmit_timeout = GNUNET_TIME_relative_to_absolute (timeout); - sock->nth.timeout_task = GNUNET_SCHEDULER_add_delayed (sock->sched, - GNUNET_NO, - GNUNET_SCHEDULER_PRIORITY_KEEP, - GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, - timeout, - &transmit_timeout, - sock); - if (sock->write_task == GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) - sock->write_task = GNUNET_SCHEDULER_add_delayed (sock->sched, - GNUNET_NO, - GNUNET_SCHEDULER_PRIORITY_KEEP, - sock->connect_task, - GNUNET_TIME_UNIT_ZERO, - &transmit_ready, sock); - return &sock->nth; -} - - -/** - * Cancel the specified transmission-ready - * notification. - */ -void -GNUNET_NETWORK_notify_transmit_ready_cancel (struct - GNUNET_NETWORK_TransmitHandle *h) -{ - GNUNET_assert (h->notify_ready != NULL); - GNUNET_SCHEDULER_cancel (h->sh->sched, h->timeout_task); - h->timeout_task = GNUNET_SCHEDULER_NO_PREREQUISITE_TASK; - h->notify_ready = NULL; -} - - -#if 0 /* keep Emacsens' auto-indent happy */ -{ -#endif -#ifdef __cplusplus -} -#endif +/* + This file is part of GNUnet. + (C) 2009 Christian Grothoff (and other contributing authors) + + GNUnet 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, or (at your + option) any later version. + + GNUnet 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 GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file util/network.c + * @brief basic, low-level networking interface + * @author Nils Durner + */ + +#include "platform.h" +#include "gnunet_disk_lib.h" +#include "disk.h" +#include "gnunet_container_lib.h" + +#define DEBUG_SOCK GNUNET_NO + +#ifndef INVALID_SOCKET +#define INVALID_SOCKET -1 +#endif + +struct GNUNET_NETWORK_Handle +{ + int fd; +}; + +struct GNUNET_NETWORK_FDSet +{ + /* socket descriptors */ + int nsds; + fd_set sds; +#ifdef WINDOWS + /* handles */ + struct GNUNET_CONTAINER_SList *handles; +#endif +}; + +#ifndef FD_COPY +#define FD_COPY(s, d) (memcpy ((d), (s), sizeof (fd_set))) +#endif + + + +/** + * Set if a socket should use blocking or non-blocking IO. + * @param fd socket + * @param doBlock blocking mode + * @return GNUNET_OK on success, GNUNET_SYSERR on error + */ +static int +socket_set_blocking (struct GNUNET_NETWORK_Handle *fd, + int doBlock) +{ +#if MINGW + u_long mode; + mode = !doBlock; + if (ioctlsocket (fd->fd, FIONBIO, &mode) == SOCKET_ERROR) + { + SetErrnoFromWinsockError (WSAGetLastError ()); + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "ioctlsocket"); + return GNUNET_SYSERR; + } + return GNUNET_OK; + +#else + /* not MINGW */ + int flags = fcntl (fd->fd, F_GETFL); + if (flags == -1) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "fcntl"); + return GNUNET_SYSERR; + } + if (doBlock) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + if (0 != fcntl (fd->fd, F_SETFL, flags)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "fcntl"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +#endif +} + + +#ifndef MINGW +/** + * Make a socket non-inheritable to child processes + * + * @param h the socket to make non-inheritable + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + * @warning Not implemented on Windows + */ +static int +socket_set_inheritable (const struct GNUNET_NETWORK_Handle + *h) +{ + int i; + + i = fcntl (h->fd, F_GETFD); + if (i == (i | FD_CLOEXEC)) + return GNUNET_OK; + return (fcntl (h->fd, F_SETFD, i | FD_CLOEXEC) == 0) + ? GNUNET_OK : GNUNET_SYSERR; +} +#endif + + + +#ifdef DARWIN +/** + * The MSG_NOSIGNAL equivalent on Mac OS X + * + * @param h the socket to make non-delaying + */ +static void +socket_set_nosigpipe (const struct GNUNET_NETWORK_Handle + *h) +{ + int value = 1; + setsockopt (h->fd, SOL_SOCKET, SO_NOSIGPIPE, &value, sizeof(value)); +} +#endif + + + +/** + * Disable delays when sending data via the socket. + * (GNUnet makes sure that messages are as big as + * possible already). + * + * @param h the socket to make non-delaying + */ +static void +socket_set_nodelay (const struct GNUNET_NETWORK_Handle + *h) +{ + int value = 1; + setsockopt (h->fd, IPPROTO_TCP, TCP_NODELAY, &value, sizeof(value)); +} + + + +/** + * accept a new connection on a socket + * + * @param desc bound socket + * @param address address of the connecting peer, may be NULL + * @param address_len length of address + * @return client socket + */ +struct GNUNET_NETWORK_Handle * +GNUNET_NETWORK_socket_accept (const struct GNUNET_NETWORK_Handle *desc, + struct sockaddr *address, + socklen_t * address_len) +{ + struct GNUNET_NETWORK_Handle *ret; + + ret = GNUNET_malloc (sizeof (struct GNUNET_NETWORK_Handle)); + ret->fd = accept (desc->fd, address, address_len); + if (ret->fd == INVALID_SOCKET) + { +#ifdef MINGW + SetErrnoFromWinsockError (WSAGetLastError ()); +#endif + GNUNET_free (ret); + return NULL; + } +#ifndef MINGW + if (ret->fd >= FD_SETSIZE) + { + close (ret->fd); + GNUNET_free (ret); + errno = EMFILE; + return NULL; + } +#endif + if (GNUNET_SYSERR == socket_set_blocking (ret, GNUNET_NO)) + { + /* we might want to treat this one as fatal... */ + GNUNET_break (0); + GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (ret)); + return NULL; + } +#ifndef MINGW + if (GNUNET_OK != socket_set_inheritable (ret)) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "socket_set_inheritable"); +#endif +#ifdef DARWIN + socket_set_nosigpipe (ret); +#endif + socket_set_nodelay (ret); + return ret; +} + +/** + * Bind to a connected socket + * @param desc socket + * @param address address to be bound + * @param address_len length of address + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + */ +int +GNUNET_NETWORK_socket_bind (struct GNUNET_NETWORK_Handle *desc, + const struct sockaddr *address, + socklen_t address_len) +{ + int ret; + + ret = bind (desc->fd, address, address_len); +#ifdef MINGW + if (SOCKET_ERROR == ret) + SetErrnoFromWinsockError (WSAGetLastError ()); +#endif + return ret == 0 ? GNUNET_OK : GNUNET_SYSERR; +} + + +/** + * Close a socket + * @param desc socket + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + */ +int +GNUNET_NETWORK_socket_close (struct GNUNET_NETWORK_Handle *desc) +{ + int ret; + int eno; + +#ifdef MINGW + ret = closesocket (desc->fd); + SetErrnoFromWinsockError (WSAGetLastError ()); +#else + ret = close (desc->fd); +#endif + eno = errno; + GNUNET_free (desc); + errno = eno; + return (ret == 0) ? GNUNET_OK : GNUNET_SYSERR; +} + +/** + * Connect a socket + * @param desc socket + * @param address peer address + * @param address_len length of address + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + */ +int +GNUNET_NETWORK_socket_connect (const struct GNUNET_NETWORK_Handle *desc, + const struct sockaddr *address, + socklen_t address_len) +{ + int ret; + + ret = connect (desc->fd, address, address_len); +#ifdef MINGW + if (SOCKET_ERROR == ret) + { + SetErrnoFromWinsockError (WSAGetLastError ()); + if (errno == EWOULDBLOCK) + errno = EINPROGRESS; + } +#endif + return ret == 0 ? GNUNET_OK : GNUNET_SYSERR; +} + +/** + * Get socket options + * + * @param desc socket + * @param level protocol level of the option + * @param optname identifier of the option + * @param optval options + * @param optlen length of optval + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + */ +int +GNUNET_NETWORK_socket_getsockopt (const struct GNUNET_NETWORK_Handle *desc, + int level, int optname, void *optval, + socklen_t * optlen) +{ + int ret; + + ret = getsockopt (desc->fd, level, optname, optval, optlen); +#ifdef MINGW + if (ret == 0 && level == SOL_SOCKET && optname == SO_ERROR) + *((int *) optval) = GetErrnoFromWinsockError (*((int *) optval)); + else if (SOCKET_ERROR == ret) + SetErrnoFromWinsockError (WSAGetLastError ()); +#endif + return ret == 0 ? GNUNET_OK : GNUNET_SYSERR; +} + +/** + * Listen on a socket + * @param desc socket + * @param backlog length of the listen queue + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + */ +int +GNUNET_NETWORK_socket_listen (const struct GNUNET_NETWORK_Handle *desc, + int backlog) +{ + int ret; + + ret = listen (desc->fd, backlog); +#ifdef MINGW + if (SOCKET_ERROR == ret) + SetErrnoFromWinsockError (WSAGetLastError ()); +#endif + + return ret == 0 ? GNUNET_OK : GNUNET_SYSERR; +} + +/** + * Read data from a connected socket (always non-blocking). + * @param desc socket + * @param buffer buffer + * @param length length of buffer + */ +ssize_t +GNUNET_NETWORK_socket_recv (const struct GNUNET_NETWORK_Handle * desc, + void *buffer, size_t length) +{ + int ret; + int flags; + + flags = 0; +#ifdef MSG_DONTWAIT + flags |= MSG_DONTWAIT; +#endif + ret = recv (desc->fd, buffer, length, flags); +#ifdef MINGW + if (SOCKET_ERROR == ret) + SetErrnoFromWinsockError (WSAGetLastError ()); +#endif + + return ret; +} + +/** + * Send data (always non-blocking). + * + * @param desc socket + * @param buffer data to send + * @param length size of the buffer + * @return number of bytes sent, GNUNET_SYSERR on error + */ +ssize_t +GNUNET_NETWORK_socket_send (const struct GNUNET_NETWORK_Handle * desc, + const void *buffer, size_t length) +{ + int ret; + int flags; + + flags = 0; +#ifdef MSG_DONTWAIT + flags |= MSG_DONTWAIT; +#endif +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + ret = send (desc->fd, buffer, length, flags); +#ifdef MINGW + if (SOCKET_ERROR == ret) + SetErrnoFromWinsockError (WSAGetLastError ()); +#endif + + return ret; +} + + +/** + * Send data to a particular destination (always non-blocking). + * This function only works for UDP sockets. + * + * @param desc socket + * @param message data to send + * @param length size of the data + * @param dest_addr destination address + * @param dest_len length of address + * @return number of bytes sent, GNUNET_SYSERR on error + */ +ssize_t +GNUNET_NETWORK_socket_sendto (const struct GNUNET_NETWORK_Handle * desc, + const void *message, size_t length, + const struct sockaddr * dest_addr, + socklen_t dest_len) +{ + int ret; + int flags; + + flags = 0; +#ifdef MSG_DONTWAIT + flags |= MSG_DONTWAIT; +#endif +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + ret = sendto (desc->fd, message, length, flags, dest_addr, dest_len); +#ifdef MINGW + if (SOCKET_ERROR == ret) + SetErrnoFromWinsockError (WSAGetLastError ()); +#endif + + return ret; +} + +/** + * Set socket option + * @param fd socket + * @param level protocol level of the option + * @param option_name option identifier + * @param option_value value to set + * @param option_len size of option_value + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + */ +int +GNUNET_NETWORK_socket_setsockopt (struct GNUNET_NETWORK_Handle *fd, + int level, int option_name, + const void *option_value, + socklen_t option_len) +{ + int ret; + + ret = setsockopt (fd->fd, level, option_name, option_value, option_len); +#ifdef MINGW + if (SOCKET_ERROR == ret) + SetErrnoFromWinsockError (WSAGetLastError ()); +#endif + + return ret == 0 ? GNUNET_OK : GNUNET_SYSERR; +} + + + +/** + * Create a new socket. Configure it for non-blocking IO and + * mark it as non-inheritable to child processes (set the + * close-on-exec flag). + * + * @param domain domain of the socket + * @param type socket type + * @param protocol network protocol + * @return new socket, NULL on error + */ +struct GNUNET_NETWORK_Handle * +GNUNET_NETWORK_socket_create (int domain, int type, int protocol) +{ + struct GNUNET_NETWORK_Handle *ret; + + ret = GNUNET_malloc (sizeof (struct GNUNET_NETWORK_Handle)); + ret->fd = socket (domain, type, protocol); + if (INVALID_SOCKET == ret->fd) + { +#ifdef MINGW + SetErrnoFromWinsockError (WSAGetLastError ()); +#endif + GNUNET_free (ret); + return NULL; + } +#ifndef MINGW + if (ret->fd >= FD_SETSIZE) + { + close (ret->fd); + GNUNET_free (ret); + errno = EMFILE; + return NULL; + } +#endif + + if (GNUNET_SYSERR == socket_set_blocking (ret, GNUNET_NO)) + { + /* we might want to treat this one as fatal... */ + GNUNET_break (0); + GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (ret)); + return NULL; + } +#ifndef MINGW + if (GNUNET_OK != socket_set_inheritable (ret)) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "socket_set_inheritable"); +#endif +#ifdef DARWIN + socket_set_nosigpipe (ret); +#endif + if (type == SOCK_STREAM) + socket_set_nodelay (ret); + + return ret; +} + +/** + * Shut down socket operations + * @param desc socket + * @param how type of shutdown + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + */ +int +GNUNET_NETWORK_socket_shutdown (struct GNUNET_NETWORK_Handle *desc, + int how) +{ + int ret; + + ret = shutdown (desc->fd, how); +#ifdef MINGW + if (ret != 0) + SetErrnoFromWinsockError (WSAGetLastError ()); +#endif + + return ret == 0 ? GNUNET_OK : GNUNET_SYSERR; +} + + +/** + * Reset FD set + * @param fds fd set + */ +void +GNUNET_NETWORK_fdset_zero (struct GNUNET_NETWORK_FDSet *fds) +{ + FD_ZERO (&fds->sds); + fds->nsds = 0; +#ifdef MINGW + GNUNET_CONTAINER_slist_clear (fds->handles); +#endif +} + + +/** + * Add a socket to the FD set + * @param fds fd set + * @param desc socket to add + */ +void +GNUNET_NETWORK_fdset_set (struct GNUNET_NETWORK_FDSet *fds, + const struct GNUNET_NETWORK_Handle *desc) +{ + FD_SET (desc->fd, &fds->sds); + + if (desc->fd + 1 > fds->nsds) + fds->nsds = desc->fd + 1; +} + + +/** + * Check whether a socket is part of the fd set + * @param fds fd set + * @param desc socket + * @return 0 if the FD is not set + */ +int +GNUNET_NETWORK_fdset_isset (const struct GNUNET_NETWORK_FDSet *fds, + const struct GNUNET_NETWORK_Handle *desc) +{ + return FD_ISSET (desc->fd, &fds->sds); +} + + +/** + * Add one fd set to another + * @param dst the fd set to add to + * @param src the fd set to add from + */ +void +GNUNET_NETWORK_fdset_add (struct GNUNET_NETWORK_FDSet *dst, + const struct GNUNET_NETWORK_FDSet *src) +{ + int nfds; + + for (nfds = src->nsds; nfds > 0; nfds--) + if (FD_ISSET (nfds, &src->sds)) + { + FD_SET (nfds, &dst->sds); + if (nfds + 1 > dst->nsds) + dst->nsds = nfds + 1; + } +} + + +/** + * Copy one fd set to another + * @param to destination + * @param from source + */ +void +GNUNET_NETWORK_fdset_copy (struct GNUNET_NETWORK_FDSet *to, + const struct GNUNET_NETWORK_FDSet *from) +{ + FD_COPY (&from->sds, &to->sds); + to->nsds = from->nsds; +#ifdef MINGW + struct GNUNET_CONTAINER_SList_Iterator *iter; + + + GNUNET_CONTAINER_slist_clear (to->handles); + + for (iter = GNUNET_CONTAINER_slist_begin (from->handles); + GNUNET_CONTAINER_slist_end (iter) != GNUNET_YES; GNUNET_CONTAINER_slist_next (iter)) + { + void *handle; + size_t len; + + handle = GNUNET_CONTAINER_slist_get (iter, &len); + GNUNET_CONTAINER_slist_add (to->handles, GNUNET_CONTAINER_SLIST_DISPOSITION_TRANSIENT, handle, len); + } +#endif +} + + +/** + * Copy a native fd set + * @param to destination + * @param from native source set + * @param nfds the biggest socket number in from + 1 + */ +void +GNUNET_NETWORK_fdset_copy_native (struct GNUNET_NETWORK_FDSet *to, + const fd_set * from, int nfds) +{ + FD_COPY (from, &to->sds); + to->nsds = nfds; +} + + +/** + * Add a file handle to the fd set + * @param fds fd set + * @param h the file handle to add + */ +void +GNUNET_NETWORK_fdset_handle_set (struct GNUNET_NETWORK_FDSet *fds, + const struct GNUNET_DISK_FileHandle *h) +{ +#ifdef MINGW + HANDLE hw; + + GNUNET_DISK_internal_file_handle_ (h, &hw, sizeof (HANDLE)); + GNUNET_CONTAINER_slist_add (fds->handles, GNUNET_NO, &hw, sizeof (HANDLE)); +#else + int fd; + + GNUNET_DISK_internal_file_handle_ (h, &fd, sizeof (int)); + FD_SET (fd, &fds->sds); + if (fd + 1 > fds->nsds) + fds->nsds = fd + 1; +#endif +} + +/** + * Check if a file handle is part of an fd set + * @param fds fd set + * @param h file handle + * @return GNUNET_YES if the file handle is part of the set + */ +int +GNUNET_NETWORK_fdset_handle_isset (const struct GNUNET_NETWORK_FDSet *fds, + const struct GNUNET_DISK_FileHandle *h) +{ +#ifdef MINGW + return GNUNET_CONTAINER_slist_contains (fds->handles, h->h, sizeof (HANDLE)); +#else + return FD_ISSET (h->fd, &fds->sds); +#endif +} + +/** + * Checks if two fd sets overlap + * @param fds1 first fd set + * @param fds2 second fd set + * @return GNUNET_YES if they do overlap, GNUNET_NO otherwise + */ +int +GNUNET_NETWORK_fdset_overlap (const struct GNUNET_NETWORK_FDSet *fds1, + const struct GNUNET_NETWORK_FDSet *fds2) +{ + int nfds; + + nfds = fds1->nsds; + if (nfds < fds2->nsds) + nfds = fds2->nsds; + + for (; nfds >= 0; nfds--) + if (FD_ISSET (nfds, &fds1->sds) && FD_ISSET (nfds, &fds2->sds)) + return GNUNET_YES; + + return GNUNET_NO; +} + +/** + * Creates an fd set + * @return a new fd set + */ +struct GNUNET_NETWORK_FDSet * +GNUNET_NETWORK_fdset_create () +{ + struct GNUNET_NETWORK_FDSet *fds; + + fds = GNUNET_malloc (sizeof (struct GNUNET_NETWORK_FDSet)); +#ifdef MINGW + fds->handles = GNUNET_CONTAINER_slist_create (); +#endif + GNUNET_NETWORK_fdset_zero (fds); + return fds; +} + + +/** + * Releases the associated memory of an fd set + * @param fds fd set + */ +void +GNUNET_NETWORK_fdset_destroy (struct GNUNET_NETWORK_FDSet *fds) +{ +#ifdef MINGW + GNUNET_CONTAINER_slist_destroy (fds->handles); +#endif + GNUNET_free (fds); +} + + +/** + * Check if sockets meet certain conditions + * @param rfds set of sockets to be checked for readability + * @param wfds set of sockets to be checked for writability + * @param efds set of sockets to be checked for exceptions + * @param timeout relative value when to return + * @return number of selected sockets, GNUNET_SYSERR on error + */ +int +GNUNET_NETWORK_socket_select (struct GNUNET_NETWORK_FDSet *rfds, + struct GNUNET_NETWORK_FDSet *wfds, + struct GNUNET_NETWORK_FDSet *efds, + const struct GNUNET_TIME_Relative timeout) +{ + int nfds; + + nfds = 0; + if (NULL != rfds) + nfds = rfds->nsds; + if (NULL != wfds) + nfds = GNUNET_MAX (nfds, wfds->nsds); + if (NULL != efds) + nfds = GNUNET_MAX (nfds, efds->nsds); + +#ifndef MINGW + struct timeval tv; + + tv.tv_sec = timeout.value / GNUNET_TIME_UNIT_SECONDS.value; + tv.tv_usec = 1000 * (timeout.value - (tv.tv_sec * GNUNET_TIME_UNIT_SECONDS.value)); + if ( (nfds == 0) && + (timeout.value == GNUNET_TIME_UNIT_FOREVER_REL.value) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Fatal internal logic error, process hangs in `%s' (abort with CTRL-C)!\n"), + "select"); + GNUNET_break (0); + } + return select (nfds + 1, + (rfds != NULL) ? &rfds->sds : NULL, + (wfds != NULL) ? &wfds->sds : NULL, + (efds != NULL) ? &efds->sds : NULL, + (timeout.value == GNUNET_TIME_UNIT_FOREVER_REL.value) + ? NULL + : &tv); +#else + DWORD limit; + fd_set sock_read, sock_write, sock_except; + fd_set aread, awrite, aexcept; + int i; + struct timeval tvslice; + int retcode; + DWORD ms_total; + +#define SAFE_FD_ISSET(fd, set) (set != NULL && FD_ISSET(fd, set)) + + /* calculate how long we need to wait in milliseconds */ + if (timeout.value == GNUNET_TIME_UNIT_FOREVER_REL.value) + ms_total = INFINITE; + else + ms_total = timeout.value / GNUNET_TIME_UNIT_MILLISECONDS.value; + + /* select() may be used as a portable way to sleep */ + if (!(rfds || wfds || efds)) + { + Sleep (ms_total); + return 0; + } + + if (rfds) + sock_read = rfds->sds; + else + FD_ZERO(&sock_read); + + if (wfds) + sock_write = wfds->sds; + else + FD_ZERO(&sock_write); + + if (efds) + sock_except = efds->sds; + else + FD_ZERO(&sock_except); + + /* multiplex between winsock select() and waiting on the handles */ + + FD_ZERO (&aread); + FD_ZERO (&awrite); + FD_ZERO (&aexcept); + + limit = GetTickCount () + ms_total; + do + { + retcode = 0; + + if (nfds > 0) + { + /* overwrite the zero'd sets here; the select call + * will clear those that are not active */ + + FD_COPY (&sock_read, &aread); + FD_COPY (&sock_write, &awrite); + FD_COPY (&sock_except, &aexcept); + + tvslice.tv_sec = 0; + tvslice.tv_usec = 100000; + + if ((retcode = + select (nfds + 1, &aread, &awrite, &aexcept, + &tvslice)) == SOCKET_ERROR) + { + SetErrnoFromWinsockError (WSAGetLastError ()); + if (errno == ENOTSOCK) + errno = EBADF; + +#if DEBUG_SOCK + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "select"); +#endif + + goto select_loop_end; + } + } + + /* Poll read pipes */ + if (rfds) + { + struct GNUNET_CONTAINER_SList_Iterator *i; + int on_next; + + on_next = GNUNET_NO; + for (i = GNUNET_CONTAINER_slist_begin (rfds->handles); + GNUNET_CONTAINER_slist_end (i) != GNUNET_YES; + on_next || GNUNET_CONTAINER_slist_next (i)) + { + HANDLE h; + DWORD dwBytes; + + h = *(HANDLE *) GNUNET_CONTAINER_slist_get (i, NULL); + on_next = GNUNET_NO; + + if (!PeekNamedPipe (h, NULL, 0, NULL, &dwBytes, NULL)) + { + GNUNET_CONTAINER_slist_erase (i); + on_next = GNUNET_YES; + + retcode = -1; + SetErrnoFromWinError (GetLastError ()); +#if DEBUG_SOCK + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "PeekNamedPipe"); +#endif + goto select_loop_end; + } + else if (dwBytes) + { + retcode++; + } + else + { + GNUNET_CONTAINER_slist_erase (i); + on_next = GNUNET_YES; + } + } + } + + /* Poll for faulty pipes */ + if (efds) + { + struct GNUNET_CONTAINER_SList_Iterator *i; + int on_next; + + on_next = GNUNET_NO; + for (i = GNUNET_CONTAINER_slist_begin (efds->handles); + GNUNET_CONTAINER_slist_end (i) != GNUNET_YES; + on_next || GNUNET_CONTAINER_slist_next (i)) + { + HANDLE h; + DWORD dwBytes; + + h = *(HANDLE *) GNUNET_CONTAINER_slist_get (i, NULL); + + if (PeekNamedPipe (h, NULL, 0, NULL, &dwBytes, NULL)) + { + GNUNET_CONTAINER_slist_erase (i); + on_next = GNUNET_YES; + + retcode++; + } + else + on_next = GNUNET_NO; + } + } + + /* FIXME */ + if (wfds) + GNUNET_assert (GNUNET_CONTAINER_slist_count (wfds->handles) == 0); + + /* Check for closed sockets */ + for (i = 0; i < nfds; i++) + { + if (SAFE_FD_ISSET (i, &sock_read)) + { + struct sockaddr addr; + int len; + + if (getpeername (i, &addr, &len) == SOCKET_ERROR) + { + int err, len; + + len = sizeof (err); + if (getsockopt + (i, SOL_SOCKET, SO_ERROR, (char *) &err, &len) == 0 + && err == WSAENOTCONN) + { + if (!SAFE_FD_ISSET (i, &aread)) + { + FD_SET (i, &aread); + retcode++; + } + } + } + } + } + + select_loop_end:; + } + while (retcode == 0 && (ms_total == INFINITE || GetTickCount () < limit)); + + if (retcode != -1) + { + if (rfds) + { + GNUNET_NETWORK_fdset_zero (rfds); + GNUNET_NETWORK_fdset_copy_native (rfds, &aread, retcode); + } + + if (wfds) + { + GNUNET_NETWORK_fdset_zero (wfds); + GNUNET_NETWORK_fdset_copy_native (wfds, &awrite, retcode); + } + + if (efds) + { + GNUNET_NETWORK_fdset_zero (efds); + GNUNET_NETWORK_fdset_copy_native (efds, &aexcept, retcode); + } + } + + return retcode; +#endif +} + + +/* end of network.c */