native/common/jk_connect.c (1,180 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Description: Socket/Naming manipulation functions * Based on: Various Jserv files */ /** * @package jk_connect * @author Gal Shachor <shachor@il.ibm.com> * @author Mladen Turk <mturk@apache.org> */ #include "jk_connect.h" #include "jk_util.h" #ifdef HAVE_APR #include "apr_network_io.h" #include "apr_errno.h" #include "apr_general.h" #include "apr_pools.h" static apr_pool_t *jk_apr_pool = NULL; #endif #ifdef HAVE_SYS_FILIO_H /* FIONREAD on Solaris et al. */ #include <sys/filio.h> #endif #ifdef HAVE_POLL_H /* Use poll instead select */ #include <poll.h> #endif #if defined(WIN32) #define JK_IS_SOCKET_ERROR(x) ((x) == SOCKET_ERROR) #define JK_GET_SOCKET_ERRNO() errno = WSAGetLastError() - WSABASEERR #else #define JK_IS_SOCKET_ERROR(x) ((x) == -1) #define JK_GET_SOCKET_ERRNO() ((void)0) #endif /* WIN32 */ #ifndef INET6_ADDRSTRLEN /* Maximum size of an IPv6 address in ASCII */ #define INET6_ADDRSTRLEN 46 #endif /* 2 IPv6 adresses of length (INET6_ADDRSTRLEN-1) * each suffixed with a ":" and a port (5 digits) * plus " -> " plus terminating "\0" */ #define DUMP_SINFO_BUF_SZ (2 * (INET6_ADDRSTRLEN - 1 + 1 + 5) + 4 + 1) #ifndef IN6ADDRSZ #define IN6ADDRSZ 16 #endif #ifndef INT16SZ #define INT16SZ sizeof(short) #endif #if !defined(EAFNOSUPPORT) && defined(WSAEAFNOSUPPORT) #define EAFNOSUPPORT WSAEAFNOSUPPORT #endif /** Set socket to blocking * @param sd socket to manipulate * @return errno: fcntl returns -1 (!WIN32) * pseudo errno: ioctlsocket returns SOCKET_ERROR (WIN32) * 0: success */ static int soblock(jk_sock_t sd) { /* BeOS uses setsockopt at present for non blocking... */ #if defined (WIN32) u_long on = 0; if (JK_IS_SOCKET_ERROR(ioctlsocket(sd, FIONBIO, &on))) { JK_GET_SOCKET_ERRNO(); return errno; } #else int fd_flags; fd_flags = fcntl(sd, F_GETFL, 0); #if defined(O_NONBLOCK) fd_flags &= ~O_NONBLOCK; #elif defined(O_NDELAY) fd_flags &= ~O_NDELAY; #elif defined(FNDELAY) fd_flags &= ~FNDELAY; #else #error Please teach JK how to make sockets blocking on your platform. #endif if (fcntl(sd, F_SETFL, fd_flags) == -1) { return errno; } #endif /* WIN32 */ return 0; } /** Set socket to non-blocking * @param sd socket to manipulate * @return errno: fcntl returns -1 (!WIN32) * pseudo errno: ioctlsocket returns SOCKET_ERROR (WIN32) * 0: success */ static int sononblock(jk_sock_t sd) { #if defined (WIN32) u_long on = 1; if (JK_IS_SOCKET_ERROR(ioctlsocket(sd, FIONBIO, &on))) { JK_GET_SOCKET_ERRNO(); return errno; } #else int fd_flags; fd_flags = fcntl(sd, F_GETFL, 0); #if defined(O_NONBLOCK) fd_flags |= O_NONBLOCK; #elif defined(O_NDELAY) fd_flags |= O_NDELAY; #elif defined(FNDELAY) fd_flags |= FNDELAY; #else #error Please teach JK how to make sockets non-blocking on your platform. #endif if (fcntl(sd, F_SETFL, fd_flags) == -1) { return errno; } #endif /* WIN32 */ return 0; } #if defined (WIN32) /* WIN32 implementation */ /** Non-blocking socket connect * @param sd socket to connect * @param addr address to connect to * @param source optional source address * @param timeout connect timeout in seconds * (<=0: no timeout=blocking) * @param l log context * @return -1: some kind of error occured * SOCKET_ERROR: no timeout given and error * during blocking connect * 0: success */ static int nb_connect(jk_sock_t sd, jk_sockaddr_t *addr, jk_sockaddr_t *source, int timeout, jk_log_context_t *l) { int rc; char buf[64]; JK_TRACE_ENTER(l); if (source != NULL) { if (bind(sd, (const struct sockaddr *)&source->sa.sin, source->salen)) { JK_GET_SOCKET_ERRNO(); jk_log(l, JK_LOG_ERROR, "error during source bind on socket %d [%s] (errno=%d)", sd, jk_dump_hinfo(source, buf, sizeof(buf)), errno); } } if (timeout <= 0) { rc = connect(sd, (const struct sockaddr *)&addr->sa.sin, addr->salen); JK_TRACE_EXIT(l); return rc; } if ((rc = sononblock(sd))) { JK_TRACE_EXIT(l); return -1; } if (JK_IS_SOCKET_ERROR(connect(sd, (const struct sockaddr *)&addr->sa.sin, addr->salen))) { struct timeval tv; fd_set wfdset, efdset; if ((rc = WSAGetLastError()) != WSAEWOULDBLOCK) { soblock(sd); WSASetLastError(rc); JK_TRACE_EXIT(l); return -1; } /* wait for the connect to complete or timeout */ FD_ZERO(&wfdset); FD_SET(sd, &wfdset); FD_ZERO(&efdset); FD_SET(sd, &efdset); tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000; rc = select((int)sd + 1, NULL, &wfdset, &efdset, &tv); if (JK_IS_SOCKET_ERROR(rc) || rc == 0) { rc = WSAGetLastError(); soblock(sd); WSASetLastError(rc); JK_TRACE_EXIT(l); return -1; } /* Evaluate the efdset */ if (FD_ISSET(sd, &efdset)) { /* The connect failed. */ int rclen = (int)sizeof(rc); if (getsockopt(sd, SOL_SOCKET, SO_ERROR, (char*) &rc, &rclen)) rc = 0; soblock(sd); if (rc) WSASetLastError(rc); JK_TRACE_EXIT(l); return -1; } } soblock(sd); JK_TRACE_EXIT(l); return 0; } #elif defined(HAVE_POLL_H) /* POSIX implementation using poll(2) */ /** Non-blocking socket connect * @param sd socket to connect * @param addr address to connect to * @param source optional source address * @param timeout connect timeout in seconds * (<=0: no timeout=blocking) * @param l log context * @return -1: some kind of error occured * 0: success */ static int nb_connect(jk_sock_t sd, jk_sockaddr_t *addr, jk_sockaddr_t *source, int timeout, jk_log_context_t *l) { int rc = 0; char buf[64]; JK_TRACE_ENTER(l); if (source != NULL) { if (bind(sd, (const struct sockaddr *)&source->sa.sin, source->salen)) { JK_GET_SOCKET_ERRNO(); jk_log(l, JK_LOG_ERROR, "error during source bind on socket %d [%s] (errno=%d)", sd, jk_dump_hinfo(source, buf, sizeof(buf)), errno); } } if (timeout > 0) { if (sononblock(sd)) { JK_TRACE_EXIT(l); return -1; } } do { rc = connect(sd, (const struct sockaddr *)&addr->sa.sin, addr->salen); } while (rc == -1 && errno == EINTR); if ((rc == -1) && (errno == EINPROGRESS || errno == EALREADY) && (timeout > 0)) { struct pollfd pfd; socklen_t rclen = (socklen_t)sizeof(rc); pfd.fd = sd; pfd.events = POLLOUT; rc = poll(&pfd, 1, timeout); if (rc <= 0) { /* Save errno */ int err = errno; soblock(sd); errno = err; JK_TRACE_EXIT(l); return -1; } rc = 0; #ifdef SO_ERROR if (getsockopt(sd, SOL_SOCKET, SO_ERROR, (char *)&rc, &rclen) < 0 || rc) { if (rc) errno = rc; rc = -1; } #endif } soblock(sd); JK_TRACE_EXIT(l); return rc; } #else /* POSIX implementation using select(2) */ /** Non-blocking socket connect * @param sd socket to connect * @param addr address to connect to * @param source optional source address * @param timeout connect timeout in seconds * (<=0: no timeout=blocking) * @param l log context * @return -1: some kind of error occured * 0: success */ static int nb_connect(jk_sock_t sd, jk_sockaddr_t *addr, jk_sockaddr_t *source, int timeout, jk_log_context_t *l) { int rc = 0; char buf[64]; JK_TRACE_ENTER(l); if (source != NULL) { if (bind(sd, (const struct sockaddr *)&source->sa.sin, source->salen)) { JK_GET_SOCKET_ERRNO(); jk_log(l, JK_LOG_ERROR, "error during source bind on socket %d [%s] (errno=%d)", sd, jk_dump_hinfo(source, buf, sizeof(buf)), errno); } } if (timeout > 0) { if (sononblock(sd)) { JK_TRACE_EXIT(l); return -1; } } do { rc = connect(sd, (const struct sockaddr *)&addr->sa.sin, addr->salen); } while (rc == -1 && errno == EINTR); if ((rc == -1) && (errno == EINPROGRESS || errno == EALREADY) && (timeout > 0)) { fd_set wfdset; struct timeval tv; socklen_t rclen = (socklen_t)sizeof(rc); FD_ZERO(&wfdset); FD_SET(sd, &wfdset); tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000; rc = select(sd + 1, NULL, &wfdset, NULL, &tv); if (rc <= 0) { /* Save errno */ int err = errno; soblock(sd); errno = err; JK_TRACE_EXIT(l); return -1; } rc = 0; #ifdef SO_ERROR if (!FD_ISSET(sd, &wfdset) || (getsockopt(sd, SOL_SOCKET, SO_ERROR, (char *)&rc, &rclen) < 0) || rc) { if (rc) errno = rc; rc = -1; } #endif /* SO_ERROR */ } /* Not sure we can be already connected */ if (rc == -1 && errno == EISCONN) rc = 0; soblock(sd); JK_TRACE_EXIT(l); return rc; } #endif /** Clone a jk_sockaddr_t * @param out The source structure * @param in The target structure */ void jk_clone_sockaddr(jk_sockaddr_t *out, jk_sockaddr_t *in) { memcpy(out, in, sizeof(*in)); /* The ipaddr_ptr member points to memory inside the struct. * Do not copy the pointer but use the same offset relative * to the struct start */ out->ipaddr_ptr = (char *)out + ((char *)in->ipaddr_ptr - (char *)in); } /** Resolve the host IP * @param host host or ip address * @param port port * @param pool memory pool for allocation * @param prefer_ipv6 should IPv6 be preferred * @param l log context * @return JK_FALSE: some kind of error occured * JK_TRUE: success */ int jk_resolve(const char *host, int port, jk_sockaddr_t *saddr, void *pool, int prefer_ipv6, jk_log_context_t *l) { int family = JK_INET; struct in_addr iaddr; JK_TRACE_ENTER(l); memset(saddr, 0, sizeof(jk_sockaddr_t)); if (*host >= '0' && *host <= '9' && strspn(host, "0123456789.") == strlen(host)) { /* If we found only digits we use inet_addr() */ iaddr.s_addr = jk_inet_addr(host); memcpy(&(saddr->sa.sin.sin_addr), &iaddr, sizeof(struct in_addr)); } else { #ifdef HAVE_APR apr_sockaddr_t *remote_sa, *temp_sa; if (!jk_apr_pool) { if (apr_pool_create(&jk_apr_pool, (apr_pool_t *)pool) != APR_SUCCESS) { JK_TRACE_EXIT(l); return JK_FALSE; } } apr_pool_clear(jk_apr_pool); if (apr_sockaddr_info_get(&remote_sa, host, APR_UNSPEC, (apr_port_t)port, 0, jk_apr_pool) != APR_SUCCESS) { JK_TRACE_EXIT(l); return JK_FALSE; } /* Check if we have multiple address matches */ if (remote_sa->next) { /* Since we are only handling JK_INET (IPV4) address (in_addr_t) */ /* make sure we find one of those. */ temp_sa = remote_sa; #if APR_HAVE_IPV6 if (prefer_ipv6) { while ((NULL != temp_sa) && (APR_INET6 != temp_sa->family)) temp_sa = temp_sa->next; } #endif if (NULL != temp_sa) { remote_sa = temp_sa; } else { while ((NULL != temp_sa) && (APR_INET != temp_sa->family)) temp_sa = temp_sa->next; #if APR_HAVE_IPV6 if (NULL == temp_sa) { temp_sa = remote_sa; while ((NULL != temp_sa) && (APR_INET6 != temp_sa->family)) temp_sa = temp_sa->next; } #endif } /* if temp_sa is set, we have a valid address otherwise, just return */ if (NULL != temp_sa) { remote_sa = temp_sa; } else { JK_TRACE_EXIT(l); return JK_FALSE; } } if (remote_sa->family == APR_INET) { saddr->sa.sin = remote_sa->sa.sin; family = JK_INET; } #if APR_HAVE_IPV6 else { saddr->sa.sin6 = remote_sa->sa.sin6; family = JK_INET6; } #endif #else /* HAVE_APR */ /* Without APR go the classic way. */ #if defined(HAVE_GETADDRINFO) /* TODO: * 1. Check for numeric IPV6 addresses * 2. Do we need to set service name for getaddrinfo? */ struct addrinfo hints, *ai_list, *ai = NULL; int error; char pbuf[12]; char *pbufptr = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; #if JK_HAVE_IPV6 if (strchr(host, ':')) { /* If host name contains colon this must be IPV6 address. * Set prefer_ipv6 flag in this case if it wasn't set already */ prefer_ipv6 = 1; } if (prefer_ipv6) hints.ai_family = JK_INET6; else #endif hints.ai_family = JK_INET; if (port > 0) { snprintf(pbuf, sizeof(pbuf), "%d", port); pbufptr = pbuf; } error = getaddrinfo(host, pbufptr, &hints, &ai_list); #if JK_HAVE_IPV6 /* XXX: * Is the check for EAI_FAMILY/WSAEAFNOSUPPORT correct * way to retry the IPv4 address? */ if (error == EAI_FAMILY && prefer_ipv6) { hints.ai_family = JK_INET; error = getaddrinfo(host, pbufptr, &hints, &ai_list); } #endif if (error) { JK_TRACE_EXIT(l); errno = error; return JK_FALSE; } #if JK_HAVE_IPV6 if (prefer_ipv6) { ai = ai_list; while (ai) { if (ai->ai_family == JK_INET6) { /* ignore elements without required address info */ if((ai->ai_addr != NULL) && (ai->ai_addrlen > 0)) { family = JK_INET6; break; } } ai = ai->ai_next; } } #endif if (ai == NULL) { ai = ai_list; while (ai) { if (ai->ai_family == JK_INET) { /* ignore elements without required address info */ if((ai->ai_addr != NULL) && (ai->ai_addrlen > 0)) { family = JK_INET; break; } } ai = ai->ai_next; } } if (ai == NULL) { /* No address found * XXX: Use better error code? */ freeaddrinfo(ai_list); JK_TRACE_EXIT(l); errno = ENOENT; return JK_FALSE; } memcpy(&(saddr->sa), ai->ai_addr, ai->ai_addrlen); freeaddrinfo(ai_list); #else /* HAVE_GETADDRINFO */ struct hostent *hoste; /* XXX : WARNING : We should really use gethostbyname_r in multi-threaded env */ /* Fortunatly when APR is available, ie under Apache 2.0, we use it */ hoste = gethostbyname(host); if (!hoste) { JK_TRACE_EXIT(l); return JK_FALSE; } iaddr = *((struct in_addr *)hoste->h_addr_list[0]); memcpy(&(saddr->sa.sin.sin_addr), &iaddr, sizeof(struct in_addr)); #endif /* HAVE_GETADDRINFO */ #endif /* HAVE_APR */ } if (family == JK_INET) { saddr->ipaddr_ptr = &(saddr->sa.sin.sin_addr); saddr->ipaddr_len = (int)sizeof(struct in_addr); saddr->salen = (int)sizeof(struct sockaddr_in); } #if JK_HAVE_IPV6 else { saddr->ipaddr_ptr = &(saddr->sa.sin6.sin6_addr); saddr->ipaddr_len = (int)sizeof(struct in6_addr); saddr->salen = (int)sizeof(struct sockaddr_in6); } #endif saddr->sa.sin.sin_family = family; /* XXX IPv6: assumes sin_port and sin6_port at same offset */ saddr->sa.sin.sin_port = htons(port); saddr->port = port; saddr->family = family; JK_TRACE_EXIT(l); return JK_TRUE; } /** Connect to Tomcat * @param addr address to connect to * @param source source address to use * @param keepalive should we set SO_KEEPALIVE (if !=0) * @param timeout socket timeout in seconds * (<=0: no timeout=blocking) * @param connect_timeout connect timeout in seconds * @param sock_buf size of send and recv buffer * (<=0: use default) * @param l log context * @return JK_INVALID_SOCKET: some kind of error occured * created socket: success * @remark Cares about errno */ jk_sock_t jk_open_socket(jk_sockaddr_t *addr, jk_sockaddr_t *source, int keepalive, int timeout, int connect_timeout, int sock_buf, jk_log_context_t *l) { char buf[DUMP_SINFO_BUF_SZ]; jk_sock_t sd; int set = 1; int ret = 0; int flags = 0; #ifdef SO_LINGER struct linger li; #endif JK_TRACE_ENTER(l); errno = 0; #if defined(SOCK_CLOEXEC) && defined(USE_SOCK_CLOEXEC) flags |= SOCK_CLOEXEC; #endif sd = socket(addr->family, SOCK_STREAM | flags, 0); if (!IS_VALID_SOCKET(sd)) { JK_GET_SOCKET_ERRNO(); jk_log(l, JK_LOG_ERROR, "socket() failed (errno=%d)", errno); JK_TRACE_EXIT(l); return JK_INVALID_SOCKET; } #if defined(FD_CLOEXEC) && !defined(USE_SOCK_CLOEXEC) if ((flags = fcntl(sd, F_GETFD)) == -1) { JK_GET_SOCKET_ERRNO(); jk_log(l, JK_LOG_ERROR, "fcntl() failed (errno=%d)", errno); jk_close_socket(sd, l); JK_TRACE_EXIT(l); return JK_INVALID_SOCKET; } flags |= FD_CLOEXEC; if (fcntl(sd, F_SETFD, flags) == -1) { JK_GET_SOCKET_ERRNO(); jk_log(l, JK_LOG_ERROR, "fcntl() failed (errno=%d)", errno); jk_close_socket(sd, l); JK_TRACE_EXIT(l); return JK_INVALID_SOCKET; } #endif /* Disable Nagle algorithm */ if (setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, (const char*)&set, sizeof(set))) { JK_GET_SOCKET_ERRNO(); jk_log(l, JK_LOG_ERROR, "failed setting TCP_NODELAY (errno=%d)", errno); jk_close_socket(sd, l); JK_TRACE_EXIT(l); return JK_INVALID_SOCKET; } if (JK_IS_DEBUG_LEVEL(l)) jk_log(l, JK_LOG_DEBUG, "socket TCP_NODELAY set to On"); if (keepalive) { #if defined(WIN32) DWORD dw; struct tcp_keepalive ka = { 0 }, ks = { 0 }; if (timeout) ka.keepalivetime = timeout * 10000; else ka.keepalivetime = 60 * 10000; /* 10 minutes */ ka.keepaliveinterval = 1000; ka.onoff = 1; if (WSAIoctl(sd, SIO_KEEPALIVE_VALS, &ka, sizeof(ka), &ks, sizeof(ks), &dw, NULL, NULL)) { JK_GET_SOCKET_ERRNO(); jk_log(l, JK_LOG_ERROR, "failed setting SIO_KEEPALIVE_VALS (errno=%d)", errno); jk_close_socket(sd, l); JK_TRACE_EXIT(l); return JK_INVALID_SOCKET; } if (JK_IS_DEBUG_LEVEL(l)) jk_log(l, JK_LOG_DEBUG, "socket SO_KEEPALIVE set to %d seconds", ka.keepalivetime / 1000); #else set = 1; if (setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, (const char*)&set, sizeof(set))) { JK_GET_SOCKET_ERRNO(); jk_log(l, JK_LOG_ERROR, "failed setting SO_KEEPALIVE (errno=%d)", errno); jk_close_socket(sd, l); JK_TRACE_EXIT(l); return JK_INVALID_SOCKET; } if (JK_IS_DEBUG_LEVEL(l)) jk_log(l, JK_LOG_DEBUG, "socket SO_KEEPALIVE set to On"); #endif } if (sock_buf > 0) { set = sock_buf; /* Set socket send buffer size */ if (setsockopt(sd, SOL_SOCKET, SO_SNDBUF, (const char*)&set, sizeof(set))) { JK_GET_SOCKET_ERRNO(); jk_log(l, JK_LOG_ERROR, "failed setting SO_SNDBUF (errno=%d)", errno); jk_close_socket(sd, l); JK_TRACE_EXIT(l); return JK_INVALID_SOCKET; } set = sock_buf; /* Set socket receive buffer size */ if (setsockopt(sd, SOL_SOCKET, SO_RCVBUF, (const char*)&set, sizeof(set))) { JK_GET_SOCKET_ERRNO(); jk_log(l, JK_LOG_ERROR, "failed setting SO_RCVBUF (errno=%d)", errno); jk_close_socket(sd, l); JK_TRACE_EXIT(l); return JK_INVALID_SOCKET; } if (JK_IS_DEBUG_LEVEL(l)) jk_log(l, JK_LOG_DEBUG, "socket SO_SNDBUF and SO_RCVBUF set to %d", sock_buf); } if (timeout > 0) { #if defined(WIN32) int tmout = timeout * 1000; setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, (const char *) &tmout, sizeof(int)); setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO, (const char *) &tmout, sizeof(int)); JK_GET_SOCKET_ERRNO(); #elif defined(SO_RCVTIMEO) && defined(USE_SO_RCVTIMEO) && defined(SO_SNDTIMEO) && defined(USE_SO_SNDTIMEO) struct timeval tv; tv.tv_sec = timeout; tv.tv_usec = 0; setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, (const void *) &tv, sizeof(tv)); setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO, (const void *) &tv, sizeof(tv)); #endif if (JK_IS_DEBUG_LEVEL(l)) jk_log(l, JK_LOG_DEBUG, "timeout %d set for socket=%d", timeout, sd); } #ifdef SO_NOSIGPIPE /* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when * sending data to a dead peer. Possibly also existing and in use on other BSD * systems? */ set = 1; if (setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (const char *)&set, sizeof(int))) { JK_GET_SOCKET_ERRNO(); jk_log(l, JK_LOG_ERROR, "failed setting SO_NOSIGPIPE (errno=%d)", errno); jk_close_socket(sd, l); JK_TRACE_EXIT(l); return JK_INVALID_SOCKET; } #endif #ifdef SO_LINGER /* Make hard closesocket by disabling lingering */ li.l_linger = li.l_onoff = 0; if (setsockopt(sd, SOL_SOCKET, SO_LINGER, (const char*)&li, sizeof(li))) { JK_GET_SOCKET_ERRNO(); jk_log(l, JK_LOG_ERROR, "failed setting SO_LINGER (errno=%d)", errno); jk_close_socket(sd, l); JK_TRACE_EXIT(l); return JK_INVALID_SOCKET; } #endif /* Tries to connect to Tomcat (continues trying while error is EINTR) */ if (JK_IS_DEBUG_LEVEL(l)) jk_log(l, JK_LOG_DEBUG, "trying to connect socket %d to %s", sd, jk_dump_hinfo(addr, buf, sizeof(buf))); /* Need more infos for BSD 4.4 and Unix 98 defines, for now only iSeries when Unix98 is required at compile time */ #if (_XOPEN_SOURCE >= 520) && defined(AS400) ((struct sockaddr *)addr)->sa.sin.sa_len = sizeof(struct sockaddr_in); #endif ret = nb_connect(sd, addr, source, connect_timeout, l); #if defined(WIN32) if (JK_IS_SOCKET_ERROR(ret)) { JK_GET_SOCKET_ERRNO(); } #endif /* WIN32 */ /* Check if we are connected */ if (ret) { jk_log(l, JK_LOG_INFO, "connect to %s failed (errno=%d)", jk_dump_hinfo(addr, buf, sizeof(buf)), errno); jk_close_socket(sd, l); sd = JK_INVALID_SOCKET; } else { if (JK_IS_DEBUG_LEVEL(l)) jk_log(l, JK_LOG_DEBUG, "socket %d [%s] connected", sd, jk_dump_sinfo(sd, buf, sizeof(buf))); } JK_TRACE_EXIT(l); return sd; } /** Close the socket * @param sd socket to close * @param l log context * @return -1: some kind of error occured (!WIN32) * SOCKET_ERROR: some kind of error occured (WIN32) * 0: success * @remark Does not change errno */ int jk_close_socket(jk_sock_t sd, jk_log_context_t *l) { int rc; int save_errno; JK_TRACE_ENTER(l); if (!IS_VALID_SOCKET(sd)) { JK_TRACE_EXIT(l); return -1; } save_errno = errno; #if defined(WIN32) rc = closesocket(sd) ? -1 : 0; #else do { rc = close(sd); } while (JK_IS_SOCKET_ERROR(rc) && (errno == EINTR || errno == EAGAIN)); #endif JK_TRACE_EXIT(l); errno = save_errno; return rc; } #ifndef MAX_SECS_TO_LINGER #define MAX_SECS_TO_LINGER 2 #endif #ifndef MS_TO_LINGER #define MS_TO_LINGER 100 #endif #ifndef MS_TO_LINGER_LAST #define MS_TO_LINGER_LAST 20 #endif #ifndef MAX_READ_RETRY #define MAX_READ_RETRY 10 #endif #ifndef MAX_LINGER_BYTES #define MAX_LINGER_BYTES 32768 #endif #ifndef SHUT_WR #ifdef SD_SEND #define SHUT_WR SD_SEND #else #define SHUT_WR 0x01 #endif #endif #ifndef SHUT_RD #ifdef SD_RECEIVE #define SHUT_RD SD_RECEIVE #else #define SHUT_RD 0x00 #endif #endif /** Drain and close the socket * @param sd socket to close * @param l log context * @return -1: socket to close is invalid * -1: some kind of error occured (!WIN32) * SOCKET_ERROR: some kind of error occured (WIN32) * 0: success * @remark Does not change errno */ int jk_shutdown_socket(jk_sock_t sd, jk_log_context_t *l) { char dummy[512]; char buf[DUMP_SINFO_BUF_SZ]; char *sb = NULL; int rc = 0; size_t rd = 0; size_t rp = 0; int save_errno; int timeout = MS_TO_LINGER; time_t start = time(NULL); JK_TRACE_ENTER(l); if (!IS_VALID_SOCKET(sd)) { JK_TRACE_EXIT(l); return -1; } save_errno = errno; if (JK_IS_DEBUG_LEVEL(l)) { sb = jk_dump_sinfo(sd, buf, sizeof(buf)); jk_log(l, JK_LOG_DEBUG, "About to shutdown socket %d [%s]", sd, sb); } /* Shut down the socket for write, which will send a FIN * to the peer. */ if (shutdown(sd, SHUT_WR)) { rc = jk_close_socket(sd, l); if (JK_IS_DEBUG_LEVEL(l)) jk_log(l, JK_LOG_DEBUG, "Failed sending SHUT_WR for socket %d [%s]", sd, sb); errno = save_errno; JK_TRACE_EXIT(l); return rc; } do { rp = 0; if (jk_is_input_event(sd, timeout, l)) { /* Do a restartable read on the socket * draining out all the data currently in the socket buffer. */ int num = 0; do { num++; #if defined(WIN32) rc = recv(sd, &dummy[0], sizeof(dummy), 0); if (JK_IS_SOCKET_ERROR(rc)) JK_GET_SOCKET_ERRNO(); #else rc = read(sd, &dummy[0], sizeof(dummy)); #endif if (rc > 0) rp += rc; } while (JK_IS_SOCKET_ERROR(rc) && (errno == EINTR || errno == EAGAIN) && num < MAX_READ_RETRY); if (rc < 0) { /* Read failed. * Bail out from the loop. */ break; } } else { /* Error or timeout (reason is logged within jk_is_input_event) * Exit the drain loop */ break; } rd += rp; if (rp < sizeof(dummy)) { if (timeout > MS_TO_LINGER_LAST) { /* Try one last time with a short timeout */ timeout = MS_TO_LINGER_LAST; continue; } /* We have read less then size of buffer * It's a good chance there will be no more data * to read. */ if ((rc = sononblock(sd))) { rc = jk_close_socket(sd, l); if (JK_IS_DEBUG_LEVEL(l)) jk_log(l, JK_LOG_DEBUG, "error setting socket %d [%s] to nonblocking", sd, sb); errno = save_errno; JK_TRACE_EXIT(l); return rc; } if (JK_IS_DEBUG_LEVEL(l)) jk_log(l, JK_LOG_DEBUG, "shutting down the read side of socket %d [%s]", sd, sb); shutdown(sd, SHUT_RD); break; } timeout = MS_TO_LINGER; } while ((rd < MAX_LINGER_BYTES) && (difftime(time(NULL), start) < MAX_SECS_TO_LINGER)); rc = jk_close_socket(sd, l); if (JK_IS_DEBUG_LEVEL(l)) jk_log(l, JK_LOG_DEBUG, "Shutdown socket %d [%s] and read %d lingering bytes in %d sec.", sd, sb, rd, (int)difftime(time(NULL), start)); errno = save_errno; JK_TRACE_EXIT(l); return rc; } /** send a message * @param sd socket to use * @param b buffer containing the data * @param len length to send * @param l log context * @return negative errno: write returns a fatal -1 (!WIN32) * negative pseudo errno: send returns SOCKET_ERROR (WIN32) * JK_SOCKET_EOF: no bytes could be sent * >0: success, provided number of bytes send * @remark Always closes socket in case of error * @remark Cares about errno * @bug this fails on Unixes if len is too big for the underlying * protocol */ int jk_tcp_socket_sendfull(jk_sock_t sd, const unsigned char *b, int len, jk_log_context_t *l) { int sent = 0; int wr; JK_TRACE_ENTER(l); errno = 0; while (sent < len) { do { #if defined(WIN32) wr = send(sd, (const char*)(b + sent), len - sent, 0); if (JK_IS_SOCKET_ERROR(wr)) JK_GET_SOCKET_ERRNO(); #else wr = write(sd, b + sent, len - sent); #endif } while (JK_IS_SOCKET_ERROR(wr) && (errno == EINTR || errno == EAGAIN)); if (JK_IS_SOCKET_ERROR(wr)) { int err; jk_shutdown_socket(sd, l); err = (errno > 0) ? -errno : errno; JK_TRACE_EXIT(l); return err; } else if (wr == 0) { jk_shutdown_socket(sd, l); JK_TRACE_EXIT(l); return JK_SOCKET_EOF; } sent += wr; } JK_TRACE_EXIT(l); return sent; } /** receive a message * @param sd socket to use * @param b buffer to store the data * @param len length to receive * @param l log context * @return negative errno: read returns a fatal -1 (!WIN32) * negative pseudo errno: recv returns SOCKET_ERROR (WIN32) * JK_SOCKET_EOF: no bytes could be read * >0: success, requested number of bytes received * @remark Always closes socket in case of error * @remark Cares about errno */ int jk_tcp_socket_recvfull(jk_sock_t sd, unsigned char *b, int len, jk_log_context_t *l) { int rdlen = 0; int rd; JK_TRACE_ENTER(l); errno = 0; while (rdlen < len) { do { #if defined(WIN32) rd = recv(sd, (char *)b + rdlen, len - rdlen, 0); if (JK_IS_SOCKET_ERROR(rd)) JK_GET_SOCKET_ERRNO(); #else rd = read(sd, (char *)b + rdlen, len - rdlen); #endif } while (JK_IS_SOCKET_ERROR(rd) && errno == EINTR); if (JK_IS_SOCKET_ERROR(rd)) { int err = (errno > 0) ? -errno : errno; jk_shutdown_socket(sd, l); JK_TRACE_EXIT(l); return (err == 0) ? JK_SOCKET_EOF : err; } else if (rd == 0) { jk_shutdown_socket(sd, l); JK_TRACE_EXIT(l); return JK_SOCKET_EOF; } rdlen += rd; } JK_TRACE_EXIT(l); return rdlen; } /* const char * * inet_ntop4(src, dst, size) * format an IPv4 address, more or less like inet_ntoa() * return: * `dst' (as a const) * notes: * (1) uses no statics * (2) takes a u_char* not an in_addr as input * author: * Paul Vixie, 1996. */ static const char *inet_ntop4(const unsigned char *src, char *dst, size_t size) { const size_t MIN_SIZE = 16; /* space for 255.255.255.255\0 */ int n = 0; char *next = dst; if (size < MIN_SIZE) { errno = ENOSPC; return NULL; } do { unsigned char u = *src++; if (u > 99) { *next++ = '0' + u/100; u %= 100; *next++ = '0' + u/10; u %= 10; } else if (u > 9) { *next++ = '0' + u/10; u %= 10; } *next++ = '0' + u; *next++ = '.'; n++; } while (n < 4); *--next = '\0'; return dst; } #if JK_HAVE_IPV6 /* const char * * inet_ntop6(src, dst, size) * convert IPv6 binary address into presentation (printable) format * author: * Paul Vixie, 1996. */ static const char *inet_ntop6(const unsigned char *src, char *dst, size_t size) { /* * Note that int32_t and int16_t need only be "at least" large enough * to contain a value of the specified size. On some systems, like * Crays, there is no such thing as an integer variable with 16 bits. * Keep this in mind if you think this function should have been coded * to use pointer overlays. All the world's not a VAX. */ char tmp[INET6_ADDRSTRLEN], *tp; struct { int base, len; } best = {-1, 0}, cur = {-1, 0}; unsigned int words[IN6ADDRSZ / INT16SZ]; int i; const unsigned char *next_src, *src_end; unsigned int *next_dest; /* * Preprocess: * Copy the input (bytewise) array into a wordwise array. * Find the longest run of 0x00's in src[] for :: shorthanding. */ next_src = src; src_end = src + IN6ADDRSZ; next_dest = words; i = 0; do { unsigned int next_word = (unsigned int)*next_src++; next_word <<= 8; next_word |= (unsigned int)*next_src++; *next_dest++ = next_word; if (next_word == 0) { if (cur.base == -1) { cur.base = i; cur.len = 1; } else { cur.len++; } } else { if (cur.base != -1) { if (best.base == -1 || cur.len > best.len) { best = cur; } cur.base = -1; } } i++; } while (next_src < src_end); if (cur.base != -1) { if (best.base == -1 || cur.len > best.len) { best = cur; } } if (best.base != -1 && best.len < 2) { best.base = -1; } /* * Format the result. */ tp = tmp; for (i = 0; i < (IN6ADDRSZ / INT16SZ);) { /* Are we inside the best run of 0x00's? */ if (i == best.base) { *tp++ = ':'; i += best.len; continue; } /* Are we following an initial run of 0x00s or any real hex? */ if (i != 0) { *tp++ = ':'; } /* Is this address an encapsulated IPv4? */ if (i == 6 && best.base == 0 && (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) { if (!inet_ntop4(src+12, tp, sizeof tmp - (tp - tmp))) { return (NULL); } tp += strlen(tp); break; } tp += sprintf(tp, "%x", words[i]); i++; } /* Was it a trailing run of 0x00's? */ if (best.base != -1 && (best.base + best.len) == (IN6ADDRSZ / INT16SZ)) { *tp++ = ':'; } *tp++ = '\0'; /* * Check for overflow, copy, and we're done. */ if ((size_t)(tp - tmp) > size) { errno = ENOSPC; return NULL; } strcpy(dst, tmp); return dst; } #endif /** * dump a jk_sockaddr_t in A.B.C.D:P in ASCII buffer * */ char *jk_dump_hinfo(jk_sockaddr_t *saddr, char *buf, size_t size) { char pb[8]; if (saddr->ipaddr_ptr == NULL) { strcpy(buf, "UnresolvedIP"); } else { if (saddr->family == JK_INET) { inet_ntop4(saddr->ipaddr_ptr, buf, size); } #if JK_HAVE_IPV6 else { inet_ntop6(saddr->ipaddr_ptr, buf, size); } #endif } sprintf(pb, ":%d", saddr->port); strncat(buf, pb, size - strlen(buf) - 1); return buf; } char *jk_dump_sinfo(jk_sock_t sd, char *buf, size_t size) { struct sockaddr rsaddr; struct sockaddr lsaddr; socklen_t salen; salen = sizeof(struct sockaddr); if (getsockname(sd, &lsaddr, &salen) == 0) { salen = sizeof(struct sockaddr); if (getpeername(sd, &rsaddr, &salen) == 0) { char pb[8]; size_t ps; if (lsaddr.sa_family == JK_INET) { struct sockaddr_in *sa = (struct sockaddr_in *)&lsaddr; inet_ntop4((unsigned char *)&sa->sin_addr, buf, size); sprintf(pb, ":%d", (unsigned int)htons(sa->sin_port)); } #if JK_HAVE_IPV6 else { struct sockaddr_in6 *sa = (struct sockaddr_in6 *)&lsaddr; inet_ntop6((unsigned char *)&sa->sin6_addr, buf, size); sprintf(pb, ":%d", (unsigned int)htons(sa->sin6_port)); } #endif ps = strlen(buf); strncat(buf, pb, size - ps - 1); ps = strlen(buf); strncat(buf, " -> ", size - ps - 1); ps = strlen(buf); if (rsaddr.sa_family == JK_INET) { struct sockaddr_in *sa = (struct sockaddr_in *)&rsaddr; inet_ntop4((unsigned char *)&sa->sin_addr, buf + ps, size - ps); sprintf(pb, ":%d", (unsigned int)htons(sa->sin_port)); } #if JK_HAVE_IPV6 else { struct sockaddr_in6 *sa = (struct sockaddr_in6 *)&rsaddr; inet_ntop6((unsigned char *)&sa->sin6_addr, buf + ps, size - ps); sprintf(pb, ":%d", (unsigned int)htons(sa->sin6_port)); } #endif strncat(buf, pb, size - strlen(buf) - 1); return buf; } } snprintf(buf, size, "errno=%d", errno); return buf; } /** Wait for input event on socket until timeout * @param sd socket to use * @param timeout wait timeout in milliseconds * @param l log context * @return JK_FALSE: Timeout expired without something to read * JK_FALSE: Error during waiting * JK_TRUE: success * @remark Does not close socket in case of error * to allow for iterative waiting * @remark Cares about errno */ #ifdef HAVE_POLL int jk_is_input_event(jk_sock_t sd, int timeout, jk_log_context_t *l) { struct pollfd fds; int rc; int save_errno; char buf[DUMP_SINFO_BUF_SZ]; JK_TRACE_ENTER(l); errno = 0; fds.fd = sd; fds.events = POLLIN; fds.revents = 0; do { rc = poll(&fds, 1, timeout); } while (rc < 0 && errno == EINTR); if (rc == 0) { if (JK_IS_DEBUG_LEVEL(l)) { jk_log(l, JK_LOG_DEBUG, "timeout during poll on socket %d [%s] (timeout=%d)", sd, jk_dump_sinfo(sd, buf, sizeof(buf)), timeout); } /* Timeout. Set the errno to timeout */ errno = ETIMEDOUT; JK_TRACE_EXIT(l); return JK_FALSE; } else if (rc < 0) { save_errno = errno; if (JK_IS_DEBUG_LEVEL(l)) { jk_log(l, JK_LOG_DEBUG, "error during poll on socket %d [%s] (errno=%d)", sd, jk_dump_sinfo(sd, buf, sizeof(buf)), errno); } errno = save_errno; JK_TRACE_EXIT(l); return JK_FALSE; } if ((fds.revents & (POLLERR | POLLHUP))) { save_errno = fds.revents & (POLLERR | POLLHUP); if (JK_IS_DEBUG_LEVEL(l)) { jk_log(l, JK_LOG_DEBUG, "error event during poll on socket %d [%s] (event=%d)", sd, jk_dump_sinfo(sd, buf, sizeof(buf)), save_errno); } errno = save_errno; JK_TRACE_EXIT(l); return JK_FALSE; } errno = 0; JK_TRACE_EXIT(l); return JK_TRUE; } #else int jk_is_input_event(jk_sock_t sd, int timeout, jk_log_context_t *l) { fd_set rset; struct timeval tv; int rc; int save_errno; char buf[DUMP_SINFO_BUF_SZ]; JK_TRACE_ENTER(l); errno = 0; FD_ZERO(&rset); FD_SET(sd, &rset); tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000; do { rc = select((int)sd + 1, &rset, NULL, NULL, &tv); } while (rc < 0 && errno == EINTR); if (rc == 0) { if (JK_IS_DEBUG_LEVEL(l)) { jk_log(l, JK_LOG_DEBUG, "timeout during select on socket %d [%s] (timeout=%d)", sd, jk_dump_sinfo(sd, buf, sizeof(buf)), timeout); } /* Timeout. Set the errno to timeout */ #if defined(WIN32) errno = WSAETIMEDOUT - WSABASEERR; #else errno = ETIMEDOUT; #endif JK_TRACE_EXIT(l); return JK_FALSE; } else if (rc < 0) { save_errno = errno; if (JK_IS_DEBUG_LEVEL(l)) { jk_log(l, JK_LOG_DEBUG, "error during select on socket %d [%s] (errno=%d)", sd, jk_dump_sinfo(sd, buf, sizeof(buf)), errno); } errno = save_errno; JK_TRACE_EXIT(l); return JK_FALSE; } errno = 0; JK_TRACE_EXIT(l); return JK_TRUE; } #endif /** Test if a socket is still connected * @param sd socket to use * @param l log context * @return JK_FALSE: failure * JK_TRUE: success * @remark Always closes socket in case of error * @remark Cares about errno */ #ifdef HAVE_POLL int jk_is_socket_connected(jk_sock_t sd, jk_log_context_t *l) { struct pollfd fds; int rc; JK_TRACE_ENTER(l); errno = 0; fds.fd = sd; fds.events = POLLIN; do { rc = poll(&fds, 1, 0); } while (rc < 0 && errno == EINTR); if (rc == 0) { /* If we get a timeout, then we are still connected */ JK_TRACE_EXIT(l); return JK_TRUE; } else if (rc == 1 && fds.revents == POLLIN) { char buf; do { rc = (int)recvfrom(sd, &buf, 1, MSG_PEEK, NULL, NULL); } while (rc < 0 && errno == EINTR); if (rc == 1) { /* There is at least one byte to read. */ JK_TRACE_EXIT(l); return JK_TRUE; } } jk_shutdown_socket(sd, l); JK_TRACE_EXIT(l); return JK_FALSE; } #else int jk_is_socket_connected(jk_sock_t sd, jk_log_context_t *l) { fd_set fd; struct timeval tv; int rc; JK_TRACE_ENTER(l); errno = 0; FD_ZERO(&fd); FD_SET(sd, &fd); /* Initially test the socket without any blocking. */ tv.tv_sec = 0; tv.tv_usec = 0; do { rc = select((int)sd + 1, &fd, NULL, NULL, &tv); JK_GET_SOCKET_ERRNO(); /* Wait one microsecond on next select, if EINTR */ tv.tv_sec = 0; tv.tv_usec = 1; } while (JK_IS_SOCKET_ERROR(rc) && errno == EINTR); errno = 0; if (rc == 0) { /* If we get a timeout, then we are still connected */ JK_TRACE_EXIT(l); return JK_TRUE; } else if (rc == 1) { #if defined(WIN32) u_long nr; rc = ioctlsocket(sd, FIONREAD, &nr); if (rc == 0) { if (WSAGetLastError() == 0) errno = 0; else JK_GET_SOCKET_ERRNO(); } #else int nr; rc = ioctl(sd, FIONREAD, (void*)&nr); #endif if (rc == 0 && nr != 0) { JK_TRACE_EXIT(l); return JK_TRUE; } JK_GET_SOCKET_ERRNO(); } jk_shutdown_socket(sd, l); JK_TRACE_EXIT(l); return JK_FALSE; } #endif