blob: 85bdfc67048ef886d56f44a90d2aa773f6e22e68 [file] [log] [blame]
/*
* 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>
* @version $Revision$
*/
#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
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
#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 */
/* our compiler cant deal with char* <-> const char* ... */
#if defined(NETWARE) && !defined(__NOVELL_LIBC__)
typedef char* SET_TYPE;
#else
typedef const char* SET_TYPE;
#endif
static int soblock(jk_sock_t sd)
{
/* BeOS uses setsockopt at present for non blocking... */
#ifndef WIN32
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;
}
#else
u_long on = 0;
if (ioctlsocket(sd, FIONBIO, &on) == SOCKET_ERROR) {
errno = WSAGetLastError() - WSABASEERR;
return errno;
}
#endif /* WIN32 */
return 0;
}
static int sononblock(jk_sock_t sd)
{
#ifndef WIN32
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;
}
#else
u_long on = 1;
if (ioctlsocket(sd, FIONBIO, &on) == SOCKET_ERROR) {
errno = WSAGetLastError() - WSABASEERR;
return errno;
}
#endif /* WIN32 */
return 0;
}
#if defined (WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
/* WIN32 implementation */
static int nb_connect(jk_sock_t sock, struct sockaddr *addr, int timeout)
{
int rc;
if (timeout <= 0)
return connect(sock, addr, sizeof(struct sockaddr_in));
if ((rc = sononblock(sock)))
return -1;
if (connect(sock, addr, sizeof(struct sockaddr_in)) == SOCKET_ERROR) {
struct timeval tv;
fd_set wfdset, efdset;
if ((rc = WSAGetLastError()) != WSAEWOULDBLOCK) {
soblock(sock);
WSASetLastError(rc);
return -1;
}
/* wait for the connect to complete or timeout */
FD_ZERO(&wfdset);
FD_SET(sock, &wfdset);
FD_ZERO(&efdset);
FD_SET(sock, &efdset);
tv.tv_sec = timeout;
tv.tv_usec = 0;
rc = select((int)sock + 1, NULL, &wfdset, &efdset, &tv);
if (rc == SOCKET_ERROR || rc == 0) {
rc = WSAGetLastError();
soblock(sock);
WSASetLastError(rc);
return -1;
}
/* Evaluate the efdset */
if (FD_ISSET(sock, &efdset)) {
/* The connect failed. */
int rclen = (int)sizeof(rc);
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*) &rc, &rclen))
rc = 0;
soblock(sock);
if (rc)
WSASetLastError(rc);
return -1;
}
}
soblock(sock);
return 0;
}
#elif !defined(NETWARE)
/* POSIX implementation */
static int nb_connect(jk_sock_t sock, struct sockaddr *addr, int timeout)
{
int rc = 0;
if (timeout > 0) {
if (sononblock(sock))
return -1;
}
do {
rc = connect(sock, addr, sizeof(struct sockaddr_in));
} 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(sock, &wfdset);
tv.tv_sec = timeout;
tv.tv_usec = 0;
rc = select(sock + 1, NULL, &wfdset, NULL, &tv);
if (rc <= 0) {
/* Save errno */
int err = errno;
soblock(sock);
errno = err;
return -1;
}
rc = 0;
#ifdef SO_ERROR
if (!FD_ISSET(sock, &wfdset) ||
(getsockopt(sock, 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(sock);
return rc;
}
#else
/* NETWARE implementation - blocking for now */
static int nb_connect(jk_sock_t sock, struct sockaddr *addr, int timeout)
{
return connect(sock, addr, sizeof(struct sockaddr_in));
}
#endif
#ifdef AS400_UTF8
/*
* i5/OS V5R4 need EBCDIC for its runtime calls but APR/APACHE works in UTF
*/
in_addr_t jk_inet_addr(const char * addrstr)
{
in_addr_t addr;
char *ptr;
ptr = (char *)malloc(strlen(addrstr) + 1);
jk_ascii2ebcdic((char *)addrstr, ptr);
addr = inet_addr(ptr);
free(ptr);
return(addr);
}
#endif
/** resolve the host IP */
int jk_resolve(const char *host, int port, struct sockaddr_in *rc)
{
int x;
struct in_addr laddr;
memset(rc, 0, sizeof(struct sockaddr_in));
rc->sin_port = htons((short)port);
rc->sin_family = AF_INET;
/* Check if we only have digits in the string */
for (x = 0; host[x] != '\0'; x++) {
if (!isdigit(host[x]) && host[x] != '.') {
break;
}
}
/* If we found also characters we shoud make name to IP resolution */
if (host[x] != '\0') {
#ifdef HAVE_APR
apr_sockaddr_t *remote_sa, *temp_sa;
char *remote_ipaddr;
if (!jk_apr_pool) {
if (apr_pool_create(&jk_apr_pool, NULL) != APR_SUCCESS)
return JK_FALSE;
}
if (apr_sockaddr_info_get
(&remote_sa, host, APR_UNSPEC, (apr_port_t) port, 0, jk_apr_pool)
!= APR_SUCCESS)
return JK_FALSE;
/* Since we are only handling AF_INET (IPV4) address (in_addr_t) */
/* make sure we find one of those. */
temp_sa = remote_sa;
while ((NULL != temp_sa) && (AF_INET != temp_sa->family))
temp_sa = temp_sa->next;
/* if temp_sa is set, we have a valid address otherwise, just return */
if (NULL != temp_sa)
remote_sa = temp_sa;
else
return JK_FALSE;
apr_sockaddr_ip_get(&remote_ipaddr, remote_sa);
laddr.s_addr = jk_inet_addr(remote_ipaddr);
#else /* HAVE_APR */
/* 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 */
#if defined(NETWARE) && !defined(__NOVELL_LIBC__)
struct hostent *hoste = gethostbyname((char*)host);
#else
struct hostent *hoste = gethostbyname(host);
#endif
if (!hoste) {
return JK_FALSE;
}
laddr = *((struct in_addr *)hoste->h_addr_list[0]);
#endif /* HAVE_APR */
}
else {
/* If we found only digits we use inet_addr() */
laddr.s_addr = jk_inet_addr(host);
}
memcpy(&(rc->sin_addr), &laddr, sizeof(laddr));
return JK_TRUE;
}
/** connect to Tomcat */
jk_sock_t jk_open_socket(struct sockaddr_in *addr, int keepalive,
int timeout, int sock_buf, jk_logger_t *l)
{
char buf[32];
jk_sock_t sock;
int set = 1;
int ret = 0;
#ifdef SO_LINGER
struct linger li;
#endif
JK_TRACE_ENTER(l);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (!IS_VALID_SOCKET(sock)) {
JK_GET_SOCKET_ERRNO();
jk_log(l, JK_LOG_ERROR,
"socket() failed (errno=%d)", errno);
JK_TRACE_EXIT(l);
return JK_INVALID_SOCKET;
;
}
/* Disable Nagle algorithm */
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (SET_TYPE)&set,
sizeof(set))) {
jk_log(l, JK_LOG_ERROR,
"failed setting TCP_NODELAY (errno=%d)", errno);
jk_close_socket(sock);
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) {
set = 1;
if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (SET_TYPE)&set,
sizeof(set))) {
jk_log(l, JK_LOG_ERROR,
"failed setting SO_KEEPALIVE (errno=%d)", errno);
jk_close_socket(sock);
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");
}
if (sock_buf > 0) {
set = sock_buf;
/* Set socket send buffer size */
if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (SET_TYPE)&set,
sizeof(set))) {
JK_GET_SOCKET_ERRNO();
jk_log(l, JK_LOG_ERROR,
"failed setting SO_SNDBUF (errno=%d)", errno);
jk_close_socket(sock);
JK_TRACE_EXIT(l);
return JK_INVALID_SOCKET;
}
set = sock_buf;
/* Set socket receive buffer size */
if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (SET_TYPE)&set,
sizeof(set))) {
JK_GET_SOCKET_ERRNO();
jk_log(l, JK_LOG_ERROR,
"failed setting SO_RCVBUF (errno=%d)", errno);
jk_close_socket(sock);
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) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
int tmout = timeout * 1000;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
(const char *) &tmout, sizeof(int));
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,
(const char *) &tmout, sizeof(int));
#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(sock, SOL_SOCKET, SO_RCVTIMEO,
(const void *) &tv, sizeof(tv));
setsockopt(sock, 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, sock);
}
#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(sock, 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(sock);
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(sock, SOL_SOCKET, SO_LINGER, (SET_TYPE)&li,
sizeof(li))) {
JK_GET_SOCKET_ERRNO();
jk_log(l, JK_LOG_ERROR,
"failed setting SO_LINGER (errno=%d)", errno);
jk_close_socket(sock);
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", sock,
jk_dump_hinfo(addr, buf));
/* Need more infos for BSD 4.4 and Unix 98 defines, for now only
iSeries when Unix98 is required at compil time */
#if (_XOPEN_SOURCE >= 520) && defined(AS400)
((struct sockaddr *)addr)->sa_len = sizeof(struct sockaddr_in);
#endif
ret = nb_connect(sock, (struct sockaddr *)addr, timeout);
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
if (ret == SOCKET_ERROR) {
errno = WSAGetLastError() - WSABASEERR;
}
#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), errno);
jk_close_socket(sock);
sock = JK_INVALID_SOCKET;
}
else {
if (JK_IS_DEBUG_LEVEL(l))
jk_log(l, JK_LOG_DEBUG, "socket %d connected to %s",
sock, jk_dump_hinfo(addr, buf));
}
JK_TRACE_EXIT(l);
return sock;
}
/** close the socket */
int jk_close_socket(jk_sock_t s)
{
if (IS_VALID_SOCKET(s))
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
return closesocket(s) ? -1 : 0;
#else
return close(s);
#endif
return -1;
}
#ifndef MAX_SECS_TO_LINGER
#define MAX_SECS_TO_LINGER 16
#endif
#define SECONDS_TO_LINGER 1
#ifndef SHUT_WR
#ifdef SD_SEND
#define SHUT_WR SD_SEND
#else
#define SHUT_WR 0x01
#endif
#endif
int jk_shutdown_socket(jk_sock_t s)
{
unsigned char dummy[512];
int nbytes;
int ttl = 0;
int rc = 0;
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
int tmout = SECONDS_TO_LINGER * 1000;
#elif defined(SO_RCVTIMEO) && defined(USE_SO_RCVTIMEO)
struct timeval tv;
#endif
if (!IS_VALID_SOCKET(s))
return -1;
/* Shut down the socket for write, which will send a FIN
* to the peer.
*/
if (shutdown(s, SHUT_WR)) {
return jk_close_socket(s);
}
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO,
(const char *) &tmout, sizeof(int)) == 0)
rc = 1;
#elif defined(SO_RCVTIMEO) && defined(USE_SO_RCVTIMEO)
tv.tv_sec = SECONDS_TO_LINGER;
tv.tv_usec = 0;
if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO,
(const void *) &tv, sizeof(tv)))
rc = 1;
#endif
/* Read all data from the peer until we reach "end-of-file" (FIN
* from peer) or we've exceeded our overall timeout. If the client does
* not send us bytes within 16 second, close the connection.
*/
while (rc) {
nbytes = jk_tcp_socket_recvfull(s, dummy, sizeof(dummy));
if (nbytes <= 0)
break;
ttl += SECONDS_TO_LINGER;
if (ttl > MAX_SECS_TO_LINGER)
break;
}
return jk_close_socket(s);
}
/** send a long message
* @param sd opened socket.
* @param b buffer containing the data.
* @param len length to send.
* @return -2: send returned 0 ? what this that ?
* -3: send failed.
* >0: total size send.
* @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)
{
int sent = 0;
int wr;
while (sent < len) {
do {
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
wr = send(sd, (const char*)(b + sent),
len - sent, 0);
if (wr == SOCKET_ERROR)
errno = WSAGetLastError() - WSABASEERR;
#else
wr = write(sd, b + sent, len - sent);
#endif
} while (wr == -1 && (errno == EINTR || errno == EAGAIN));
if (wr == -1)
return (errno > 0) ? -errno : errno;
else if (wr == 0)
return JK_SOCKET_EOF;
sent += wr;
}
return sent;
}
/** receive len bytes. Used in ajp_common.
* @param sd opened socket.
* @param b buffer to store the data.
* @param len length to receive.
* @return <0: receive failed or connection closed.
* >0: length of the received data.
*/
int jk_tcp_socket_recvfull(jk_sock_t sd, unsigned char *b, int len)
{
int rdlen = 0;
int rd;
while (rdlen < len) {
do {
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
rd = recv(sd, (char *)b + rdlen,
len - rdlen, 0);
/* Assuming SOCKET_ERROR is -1 on NETWARE too */
if (rd == SOCKET_ERROR)
errno = WSAGetLastError() - WSABASEERR;
#else
rd = read(sd, (char *)b + rdlen, len - rdlen);
#endif
} while (rd == -1 && (errno == EINTR || errno == EAGAIN));
if (rd == -1)
return (errno > 0) ? -errno : errno;
else if (rd == 0)
return JK_SOCKET_EOF;
rdlen += rd;
}
return rdlen;
}
/**
* dump a sockaddr_in in A.B.C.D:P in ASCII buffer
*
*/
char *jk_dump_hinfo(struct sockaddr_in *saddr, char *buf)
{
unsigned long laddr = (unsigned long)htonl(saddr->sin_addr.s_addr);
unsigned short lport = (unsigned short)htons(saddr->sin_port);
sprintf(buf, "%d.%d.%d.%d:%d",
(int)(laddr >> 24), (int)((laddr >> 16) & 0xff),
(int)((laddr >> 8) & 0xff), (int)(laddr & 0xff), (int)lport);
return buf;
}
int jk_is_socket_connected(jk_sock_t sock)
{
fd_set fd;
struct timeval tv;
int rc;
FD_ZERO(&fd);
FD_SET(sock, &fd);
/* Initially test the socket without any blocking.
*/
tv.tv_sec = 0;
tv.tv_usec = 0;
do {
rc = select((int)sock + 1, &fd, NULL, NULL, &tv);
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
errno = WSAGetLastError() - WSABASEERR;
#endif
/* Wait one microsecond on next select, if EINTR */
tv.tv_usec = 1;
} while (rc == -1 && errno == EINTR);
if (rc == 0) {
/* If we get a timeout, then we are still connected */
return 1;
}
else if (rc == 1) {
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
u_long nr;
if (ioctlsocket(sock, FIONREAD, &nr) == 0) {
if (WSAGetLastError() == 0)
errno = 0;
else
errno = WSAGetLastError() - WSABASEERR;
return nr == 0 ? 0 : 1;
}
errno = WSAGetLastError() - WSABASEERR;
#else
int nr;
if (ioctl(sock, FIONREAD, (void*)&nr) == 0) {
return nr == 0 ? 0 : 1;
}
#endif
}
return 0;
}