- Factor close_unusable_connections() out of
[privoxy.git] / gateway.c
index e43bcbd..7e79831 100644 (file)
--- a/gateway.c
+++ b/gateway.c
@@ -1,4 +1,4 @@
-const char gateway_rcs[] = "$Id: gateway.c,v 1.28 2008/10/09 18:21:41 fabiankeil Exp $";
+const char gateway_rcs[] = "$Id: gateway.c,v 1.35 2008/10/17 17:12:01 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/gateway.c,v $
@@ -34,6 +34,32 @@ const char gateway_rcs[] = "$Id: gateway.c,v 1.28 2008/10/09 18:21:41 fabiankeil
  *
  * Revisions   :
  *    $Log: gateway.c,v $
+ *    Revision 1.35  2008/10/17 17:12:01  fabiankeil
+ *    In socket_is_still_usable(), use select()
+ *    and FD_ISSET() if poll() isn't available.
+ *
+ *    Revision 1.34  2008/10/17 17:07:13  fabiankeil
+ *    Add preliminary timeout support.
+ *
+ *    Revision 1.33  2008/10/16 16:34:21  fabiankeil
+ *    Fix two gcc44 warnings.
+ *
+ *    Revision 1.32  2008/10/16 16:27:22  fabiankeil
+ *    Fix compiler warning.
+ *
+ *    Revision 1.31  2008/10/16 07:31:11  fabiankeil
+ *    - Factor socket_is_still_usable() out of get_reusable_connection().
+ *    - If poll() isn't available, show a warning and assume the socket
+ *      is still usable.
+ *
+ *    Revision 1.30  2008/10/13 17:31:03  fabiankeil
+ *    If a remembered connection is no longer usable and
+ *    has been marked closed, don't bother checking if the
+ *    destination matches.
+ *
+ *    Revision 1.29  2008/10/11 16:59:41  fabiankeil
+ *    Add missing dots for two log messages.
+ *
  *    Revision 1.28  2008/10/09 18:21:41  fabiankeil
  *    Flush work-in-progress changes to keep outgoing connections
  *    alive where possible. Incomplete and mostly #ifdef'd out.
@@ -198,10 +224,13 @@ const char gateway_rcs[] = "$Id: gateway.c,v 1.28 2008/10/09 18:21:41 fabiankeil
 #include "gateway.h"
 #include "miscutil.h"
 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
-#ifdef __GLIBC__
+#ifdef HAVE_POLL
+#ifdef __GLIBC__ 
 #include <sys/poll.h>
-#endif /* __GLIBC__ */
+#else
 #include <poll.h>
+#endif /* def __GLIBC__ */
+#endif /* HAVE_POLL */
 #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
 
 const char gateway_h_rcs[] = GATEWAY_H_VERSION;
@@ -256,6 +285,8 @@ static const char socks_userid[] = "anonymous";
 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
 
 #define MAX_REUSABLE_CONNECTIONS 100
+/* XXX: should be a config option. */
+#define CONNECTION_KEEP_ALIVE_TIMEOUT 3 * 60
 
 struct reusable_connection
 {
@@ -263,6 +294,7 @@ struct reusable_connection
    int       in_use;
    char      *host;
    int       port;
+   time_t    timestamp;
 
    int       forwarder_type;
    char      *gateway_host;
@@ -275,6 +307,8 @@ static struct reusable_connection reusable_connection[MAX_REUSABLE_CONNECTIONS];
 
 static int mark_connection_unused(jb_socket sfd);
 static void mark_connection_closed(struct reusable_connection *closed_connection);
+static int socket_is_still_usable(jb_socket sfd);
+
 
 /*********************************************************************
  *
@@ -367,6 +401,7 @@ void remember_connection(jb_socket sfd, const struct http_request *http,
    reusable_connection[slot].sfd = sfd;
    reusable_connection[slot].port = http->port;
    reusable_connection[slot].in_use = 0;
+   reusable_connection[slot].timestamp = time(NULL);
 
    assert(NULL != fwd);
    assert(reusable_connection[slot].gateway_host == NULL);
@@ -427,6 +462,7 @@ static void mark_connection_closed(struct reusable_connection *closed_connection
    closed_connection->sfd = JB_INVALID_SOCKET;
    freez(closed_connection->host);
    closed_connection->port = 0;
+   closed_connection->timestamp = 0;
    closed_connection->forwarder_type = SOCKS_NONE;
    freez(closed_connection->gateway_host);
    closed_connection->gateway_port = 0;
@@ -566,6 +602,122 @@ static int connection_destination_matches(const struct reusable_connection *conn
 
 }
 
+
+/*********************************************************************
+ *
+ * Function    :  close_unusable_connections
+ *
+ * Description :  Closes remembered connections that have timed
+ *                out or have been closed on the other side.
+ *
+ * Parameters  :  none
+ *
+ * Returns     :  void
+ *
+ *********************************************************************/
+static void close_unusable_connections(void)
+{
+   unsigned int slot = 0;
+
+   for (slot = 0; slot < SZ(reusable_connection); slot++)
+   {
+      if (!reusable_connection[slot].in_use
+         && (JB_INVALID_SOCKET != reusable_connection[slot].sfd))
+      {
+         time_t time_open = time(NULL) - reusable_connection[slot].timestamp;
+
+         if (CONNECTION_KEEP_ALIVE_TIMEOUT < time_open)
+         {
+            log_error(LOG_LEVEL_CONNECT,
+               "The connection to %s:%d in slot %d timed out. Closing.",
+               reusable_connection[slot].host, reusable_connection[slot].port,
+               slot);
+            close_socket(reusable_connection[slot].sfd);
+            mark_connection_closed(&reusable_connection[slot]);
+            continue;
+         }
+
+         if (!socket_is_still_usable(reusable_connection[slot].sfd))
+         {
+            log_error(LOG_LEVEL_CONNECT,
+               "Socket %d for %s:%d in slot %d is no longer usable. Closing.",
+               reusable_connection[slot].sfd, reusable_connection[slot].host,
+               reusable_connection[slot].port, slot);
+            close_socket(reusable_connection[slot].sfd);
+            mark_connection_closed(&reusable_connection[slot]);
+            continue;
+         }
+      }
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  socket_is_still_usable
+ *
+ * Description :  Decides whether or not an open socket is still usable.
+ *
+ * Parameters  :
+ *          1  :  sfd = The socket to check.
+ *
+ * Returns     :  TRUE for yes, otherwise FALSE.
+ *
+ *********************************************************************/
+static int socket_is_still_usable(jb_socket sfd)
+{
+#ifdef HAVE_POLL
+   int poll_result;
+   struct pollfd poll_fd[1];
+
+   memset(poll_fd, 0, sizeof(poll_fd));
+   poll_fd[0].fd = sfd;
+   poll_fd[0].events = POLLIN;
+
+   poll_result = poll(poll_fd, 1, 0);
+
+   if (-1 != poll_result)
+   {
+      return !(poll_fd[0].revents & POLLIN);
+   }
+   else
+   {
+      log_error(LOG_LEVEL_CONNECT, "Polling socket %d failed.", sfd);
+      return FALSE;
+   }
+#else
+   fd_set readable_fds;
+   struct timeval timeout;
+   int ret;
+   int socket_is_alive = 0;
+
+   memset(&timeout, '\0', sizeof(timeout));
+   /*   timeout.tv_usec = 150;*/
+   FD_ZERO(&readable_fds);
+   FD_SET(sfd, &readable_fds);
+
+   ret = select((int)sfd+1, &readable_fds, NULL, NULL, &timeout);
+   if (ret < 0)
+   {
+      log_error(LOG_LEVEL_ERROR, "select() failed!: %E");
+   }
+
+   /*
+    * XXX: I'm not sure why !FD_ISSET() works,
+    * but apparently it does.
+    */
+   socket_is_alive = !FD_ISSET(sfd, &readable_fds);
+
+   log_error(LOG_LEVEL_INFO,
+      "Detecting already dead sockets might not work correctly "
+      "on your platform. Anyway, socket %d appears to be %s.",
+      sfd, (socket_is_alive ? "still alive" : "already dead"));
+
+   return socket_is_alive;
+#endif /* def HAVE_POLL */
+}
+
+
 /*********************************************************************
  *
  * Function    :  get_reusable_connection
@@ -589,39 +741,13 @@ static jb_socket get_reusable_connection(const struct http_request *http,
 
    privoxy_mutex_lock(&connection_reuse_mutex);
 
+   close_unusable_connections();
+
    for (slot = 0; slot < SZ(reusable_connection); slot++)
    {
       if (!reusable_connection[slot].in_use
          && (JB_INVALID_SOCKET != reusable_connection[slot].sfd))
       {
-         int poll_result;
-         struct pollfd poll_fd[1];
-         memset(poll_fd, 0, sizeof(poll_fd));
-         poll_fd[0].fd = reusable_connection[slot].sfd;
-         poll_fd[0].events = POLLIN;
-
-         poll_result = poll(poll_fd, 1, 0);
-
-         if (-1 != poll_result)
-         {
-            if ((poll_fd[0].revents & POLLIN))
-            {
-               log_error(LOG_LEVEL_CONNECT,
-                  "Socket %d for %s:%d in slot %d is no longer usable. Closing.",
-                  reusable_connection[slot].sfd, reusable_connection[slot].host,
-                  reusable_connection[slot].port, slot);
-               mark_connection_closed(&reusable_connection[slot]);
-            }
-         }
-         else
-         {
-            log_error(LOG_LEVEL_CONNECT,
-               "Failed to poll socket %d for %s:%d in slot %d.",
-               reusable_connection[slot].sfd, reusable_connection[slot].host,
-               reusable_connection[slot].port, slot);
-         }
-
-
          if (connection_destination_matches(&reusable_connection[slot], http, fwd))
          {
             reusable_connection[slot].in_use = TRUE;
@@ -657,7 +783,7 @@ static jb_socket get_reusable_connection(const struct http_request *http,
 static int mark_connection_unused(jb_socket sfd)
 {
    unsigned int slot = 0;
-   unsigned int socket_found = FALSE;
+   int socket_found = FALSE;
 
    assert(sfd != JB_INVALID_SOCKET);
 
@@ -679,6 +805,7 @@ static int mark_connection_unused(jb_socket sfd)
          "Marking open socket %d for %s:%d in slot %d as unused.",
          sfd, reusable_connection[slot].host, reusable_connection[slot].port, slot);
       reusable_connection[slot].in_use = 0;
+      reusable_connection[slot].timestamp = time(NULL);
    }
 
    privoxy_mutex_unlock(&connection_reuse_mutex);
@@ -1146,8 +1273,8 @@ static jb_socket socks5_connect(const struct forward_spec *fwd,
    /* Using strncpy because we really want the nul byte padding. */
    strncpy(cbuf + client_pos, target_host, sizeof(cbuf) - client_pos);
    client_pos += (hostlen & 0xffu);
-   cbuf[client_pos++] = (char)((target_port >> 8) & 0xffu);
-   cbuf[client_pos++] = (char)((target_port     ) & 0xffu);
+   cbuf[client_pos++] = (char)((target_port >> 8) & 0xff);
+   cbuf[client_pos++] = (char)((target_port     ) & 0xff);
 
    if (write_socket(sfd, cbuf, client_pos))
    {