]> granicus.if.org Git - curl/commitdiff
Xavier Bouchoux made the SSL connection non-blocking for the multi interface
authorDaniel Stenberg <daniel@haxx.se>
Tue, 21 Mar 2006 21:54:44 +0000 (21:54 +0000)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 21 Mar 2006 21:54:44 +0000 (21:54 +0000)
(when using OpenSSL).

CHANGES
RELEASE-NOTES
lib/http.c
lib/http.h
lib/sslgen.c
lib/sslgen.h
lib/ssluse.c
lib/ssluse.h
lib/url.c
lib/urldata.h

diff --git a/CHANGES b/CHANGES
index c139ade729b21684387f109b477a50e867b498a3..9fa1ff56653b8c6969fa189105b62f22ec07d34f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -7,6 +7,9 @@
                                   Changelog
 
 Daniel (21 March 2006)
+- Xavier Bouchoux made the SSL connection non-blocking for the multi interface
+  (when using OpenSSL).
+
 - Tor Arntsen fixed the AIX Toolbox RPM spec
 
 Daniel (20 March 2006)
index 7ef61192685fc38d874aabb7ccd2fcca115fb563..780643ec3db698529bd5b5572eb09797b57b01ec 100644 (file)
@@ -11,7 +11,7 @@ Curl and libcurl 7.15.4
 
 This release includes the following changes:
 
- o 
+ o less blocking for the multi interface during SSL connect negotiation 
 
 This release includes the following bugfixes:
 
@@ -27,6 +27,6 @@ Other curl-related news since the previous public release:
 This release would not have looked like this without help, code, reports and
 advice from friends like these:
 
- Dan Fandrich, Ilja van Sprundel, David McCreedy, Tor Arntsen
+ Dan Fandrich, Ilja van Sprundel, David McCreedy, Tor Arntsen, Xavier Bouchoux
 
         Thanks! (and sorry if I forgot to mention someone)
index e15054f57e8bf79bb6e9118a2bb5ac88ed9d62f4..a7db903fd62bb68d4a35e2c45b442b9966281efb 100644 (file)
@@ -1371,13 +1371,6 @@ CURLcode Curl_http_connect(struct connectdata *conn, bool *done)
       return result;
   }
 
-  if(conn->protocol & PROT_HTTPS) {
-    /* perform SSL initialization for this socket */
-    result = Curl_ssl_connect(conn, FIRSTSOCKET);
-    if(result)
-      return result;
-  }
-
   if(!data->state.this_is_a_follow) {
     /* this is not a followed location, get the original host name */
     if (data->state.first_host)
@@ -1387,11 +1380,68 @@ CURLcode Curl_http_connect(struct connectdata *conn, bool *done)
     data->state.first_host = strdup(conn->host.name);
   }
 
-  *done = TRUE;
+  if(conn->protocol & PROT_HTTPS) {
+    /* perform SSL initialization */
+    if(data->state.used_interface == Curl_if_multi) {
+      result = Curl_https_connecting(conn, done);
+      if(result)
+        return result;
+    }
+    else {
+      /* BLOCKING */
+      result = Curl_ssl_connect(conn, FIRSTSOCKET);
+      if(result)
+        return result;
+      *done = TRUE;
+    }
+  }
+  else {
+    *done = TRUE;
+  }
 
   return CURLE_OK;
 }
 
+CURLcode Curl_https_connecting(struct connectdata *conn, bool *done)
+{
+  CURLcode result;
+  curlassert(conn->protocol & PROT_HTTPS);
+
+  /* perform SSL initialization for this socket */
+  result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, done);
+  if(result)
+    return result;
+
+  return CURLE_OK;
+}
+
+#ifdef USE_SSLEAY
+CURLcode Curl_https_proto_fdset(struct connectdata *conn,
+                                fd_set *read_fd_set,
+                                fd_set *write_fd_set,
+                                int *max_fdp)
+{
+  if (conn->protocol & PROT_HTTPS) {
+    struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET];
+    curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
+
+    if (connssl->connecting_state == ssl_connect_2_writing) {
+      /* write mode */
+      FD_SET(sockfd, write_fd_set);
+      if((int)sockfd > *max_fdp)
+        *max_fdp = (int)sockfd;
+    }
+    else if (connssl->connecting_state == ssl_connect_2_reading) {
+      /* read mode */
+      FD_SET(sockfd, read_fd_set);
+      if((int)sockfd > *max_fdp)
+        *max_fdp = (int)sockfd;
+    }
+  }
+  return CURLE_OK;
+}
+#endif
+
 /*
  * Curl_http_done() gets called from Curl_done() after a single HTTP request
  * has been performed.
index 599d067c06dac311834e79ad2181e40d5fec808a..bc459f5d37208658a55117143a39812d228cecb4 100644 (file)
@@ -37,6 +37,11 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
 CURLcode Curl_http(struct connectdata *conn, bool *done);
 CURLcode Curl_http_done(struct connectdata *, CURLcode);
 CURLcode Curl_http_connect(struct connectdata *conn, bool *done);
+CURLcode Curl_https_connecting(struct connectdata *conn, bool *done);
+CURLcode Curl_https_proto_fdset(struct connectdata *conn,
+                                fd_set *read_fd_set,
+                                fd_set *write_fd_set,
+                                int *max_fdp);
 
 /* The following functions are defined in http_chunks.c */
 void Curl_httpchunk_init(struct connectdata *conn);
index d7d1259f3ec3b0f044069c16d9d26640b9f40d5a..a4c941050cef884f790ef55bca466193fa5fa680 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2005, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2006, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -215,6 +215,23 @@ Curl_ssl_connect(struct connectdata *conn, int sockindex)
 #endif /* USE_SSL */
 }
 
+CURLcode
+Curl_ssl_connect_nonblocking(struct connectdata *conn, int sockindex,
+                             bool *done)
+{
+#if defined(USE_SSL) && defined(USE_SSLEAY)
+  /* mark this is being ssl enabled from here on. */
+  conn->ssl[sockindex].use = TRUE;
+  return Curl_ossl_connect_nonblocking(conn, sockindex, done);
+
+#else
+  /* not implemented!
+     fallback to BLOCKING call. */
+  *done = TRUE;
+  return Curl_ssl_connect(conn, sockindex);
+#endif
+}
+
 #ifdef USE_SSL
 
 /*
index 3e1dfa0a6031503ba1ca92ad7fa0d26f94abb5ab..6b63c36885b128703c96962d78d95b5d811521a2 100644 (file)
@@ -32,6 +32,9 @@ void Curl_free_ssl_config(struct ssl_config_data* sslc);
 int Curl_ssl_init(void);
 void Curl_ssl_cleanup(void);
 CURLcode Curl_ssl_connect(struct connectdata *conn, int sockindex);
+CURLcode Curl_ssl_connect_nonblocking(struct connectdata *conn, 
+                                      int sockindex,
+                                      bool *done);
 void Curl_ssl_close(struct connectdata *conn);
 /* tell the SSL stuff to close down all open information regarding
    connections (and thus session ID caching etc) */
index 0ec216a3f47fbe8aa11d27eb95ed93132ad4e3c9..7be5a7cad3c94ba734bdd19ca980e6e83bfb04e8 100644 (file)
@@ -1116,23 +1116,21 @@ static void ssl_tls_trace(int direction, int ssl_ver, int content_type,
 
 #ifdef USE_SSLEAY
 /* ====================================================== */
-CURLcode
-Curl_ossl_connect(struct connectdata *conn,
+
+static CURLcode
+Curl_ossl_connect_step1(struct connectdata *conn,
                   int sockindex)
 {
   CURLcode retcode = CURLE_OK;
 
   struct SessionHandle *data = conn->data;
-  int err;
-  long lerr;
-  int what;
-  char * str;
   SSL_METHOD_QUAL SSL_METHOD *req_method=NULL;
   void *ssl_sessionid=NULL;
-  ASN1_TIME *certdate;
   curl_socket_t sockfd = conn->sock[sockindex];
   struct ssl_connect_data *connssl = &conn->ssl[sockindex];
 
+  curlassert(ssl_connect_1 == connssl->connecting_state);
+
   if(!ssl_seeded || data->set.ssl.random_file || data->set.ssl.egdsocket) {
     /* Make funny stuff to get random input */
     random_the_seed(data);
@@ -1297,134 +1295,150 @@ Curl_ossl_connect(struct connectdata *conn,
      return CURLE_SSL_CONNECT_ERROR;
   }
 
-  while(1) {
-    int writefd;
-    int readfd;
-    long timeout_ms;
-    long has_passed;
-
-    /* Find out if any timeout is set. If not, use 300 seconds.
-       Otherwise, figure out the most strict timeout of the two possible one
-       and then how much time that has elapsed to know how much time we
-       allow for the connect call */
-    if(data->set.timeout || data->set.connecttimeout) {
-
-      /* get the most strict timeout of the ones converted to milliseconds */
-      if(data->set.timeout &&
-         (data->set.timeout>data->set.connecttimeout))
-        timeout_ms = data->set.timeout*1000;
-      else
-        timeout_ms = data->set.connecttimeout*1000;
-    }
-    else
-      /* no particular time-out has been set */
-      timeout_ms= DEFAULT_CONNECT_TIMEOUT;
+  connssl->connecting_state = ssl_connect_2;
+  return CURLE_OK;
+}
 
-    /* Evaluate in milliseconds how much time that has passed */
-    has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle);
+static CURLcode
+Curl_ossl_connect_step2(struct connectdata *conn,
+                  int sockindex, long* timeout_ms)
+{
+  struct SessionHandle *data = conn->data;
+  int err;
+  long has_passed;
+  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
 
-    /* subtract the passed time */
-    timeout_ms -= has_passed;
+  curlassert(ssl_connect_2 == connssl->connecting_state
+             || ssl_connect_2_reading == connssl->connecting_state
+             || ssl_connect_2_writing == connssl->connecting_state);
 
-    if(timeout_ms < 0) {
-      /* a precaution, no need to continue if time already is up */
-      failf(data, "SSL connection timeout");
-      return CURLE_OPERATION_TIMEOUTED;
-    }
+  /* Find out if any timeout is set. If not, use 300 seconds.
+     Otherwise, figure out the most strict timeout of the two possible one
+     and then how much time that has elapsed to know how much time we
+     allow for the connect call */
+  if(data->set.timeout || data->set.connecttimeout) {
 
-    readfd = CURL_SOCKET_BAD;
-    writefd = CURL_SOCKET_BAD;
+    /* get the most strict timeout of the ones converted to milliseconds */
+    if(data->set.timeout &&
+       (data->set.timeout>data->set.connecttimeout))
+      *timeout_ms = data->set.timeout*1000;
+    else
+      *timeout_ms = data->set.connecttimeout*1000;
+  }
+  else
+    /* no particular time-out has been set */
+    *timeout_ms= DEFAULT_CONNECT_TIMEOUT;
 
-    err = SSL_connect(connssl->handle);
+  /* Evaluate in milliseconds how much time that has passed */
+  has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle);
 
-    /* 1  is fine
-       0  is "not successful but was shut down controlled"
-       <0 is "handshake was not successful, because a fatal error occurred" */
-    if(1 != err) {
-      int detail = SSL_get_error(connssl->handle, err);
+  /* subtract the passed time */
+  *timeout_ms -= has_passed;
 
-      if(SSL_ERROR_WANT_READ == detail)
-        readfd = sockfd;
-      else if(SSL_ERROR_WANT_WRITE == detail)
-        writefd = sockfd;
-      else {
-        /* untreated error */
-        unsigned long errdetail;
-        char error_buffer[120]; /* OpenSSL documents that this must be at least
-                                   120 bytes long. */
-        CURLcode rc;
-        const char *cert_problem = NULL;
-
-        errdetail = ERR_get_error(); /* Gets the earliest error code from the
-                                        thread's error queue and removes the
-                                        entry. */
-
-        switch(errdetail) {
-        case 0x1407E086:
-          /* 1407E086:
-             SSL routines:
-             SSL2_SET_CERTIFICATE:
-             certificate verify failed */
-          /* fall-through */
-        case 0x14090086:
-          /* 14090086:
-             SSL routines:
-             SSL3_GET_SERVER_CERTIFICATE:
-             certificate verify failed */
-          cert_problem = "SSL certificate problem, verify that the CA cert is"
-                         " OK. Details:\n";
-          rc = CURLE_SSL_CACERT;
-          break;
-        default:
-          rc = CURLE_SSL_CONNECT_ERROR;
-          break;
-        }
+  if(*timeout_ms < 0) {
+    /* a precaution, no need to continue if time already is up */
+    failf(data, "SSL connection timeout");
+    return CURLE_OPERATION_TIMEOUTED;
+  }
 
-          /* detail is already set to the SSL error above */
+  err = SSL_connect(connssl->handle);
 
-        /* If we e.g. use SSLv2 request-method and the server doesn't like us
-         * (RST connection etc.), OpenSSL gives no explanation whatsoever and
-         * the SO_ERROR is also lost.
-         */
-        if (CURLE_SSL_CONNECT_ERROR == rc && errdetail == 0) {
-          failf(data, "Unknown SSL protocol error in connection to %s:%d ",
-                conn->host.name, conn->port);
-          return rc;
-        }
-        /* Could be a CERT problem */
+  /* 1  is fine
+     0  is "not successful but was shut down controlled"
+     <0 is "handshake was not successful, because a fatal error occurred" */
+  if(1 != err) {
+    int detail = SSL_get_error(connssl->handle, err);
 
-        SSL_strerror(errdetail, error_buffer, sizeof(error_buffer));
-        failf(data, "%s%s", cert_problem ? cert_problem : "", error_buffer);
-        return rc;
-      }
+    if(SSL_ERROR_WANT_READ == detail) {
+      connssl->connecting_state = ssl_connect_2_reading;
+      return CURLE_OK;
     }
-    else
-      /* we have been connected fine, get out of the connect loop */
-      break;
-
-    while(1) {
-      what = Curl_select(readfd, writefd, (int)timeout_ms);
-      if(what > 0)
-        /* reabable or writable, go loop in the outer loop */
+    else if(SSL_ERROR_WANT_WRITE == detail) {
+      connssl->connecting_state = ssl_connect_2_writing;
+      return CURLE_OK;
+    }
+    else {
+      /* untreated error */
+      unsigned long errdetail;
+      char error_buffer[120]; /* OpenSSL documents that this must be at least
+                                 120 bytes long. */
+      CURLcode rc;
+      const char *cert_problem = NULL;
+
+      connssl->connecting_state = ssl_connect_2; /* the connection failed,
+                                                    we're not waiting for
+                                                    anything else. */
+
+      errdetail = ERR_get_error(); /* Gets the earliest error code from the
+                                      thread's error queue and removes the
+                                      entry. */
+
+      switch(errdetail) {
+      case 0x1407E086:
+        /* 1407E086:
+           SSL routines:
+           SSL2_SET_CERTIFICATE:
+           certificate verify failed */
+        /* fall-through */
+      case 0x14090086:
+        /* 14090086:
+           SSL routines:
+           SSL3_GET_SERVER_CERTIFICATE:
+           certificate verify failed */
+        cert_problem = "SSL certificate problem, verify that the CA cert is"
+          " OK. Details:\n";
+        rc = CURLE_SSL_CACERT;
+        break;
+      default:
+        rc = CURLE_SSL_CONNECT_ERROR;
         break;
-      else if(0 == what) {
-        /* timeout */
-        failf(data, "SSL connection timeout");
-        return CURLE_OPERATION_TIMEDOUT;
       }
-      else {
-        /* anything that gets here is fatally bad */
-        failf(data, "select on SSL socket, errno: %d", Curl_ourerrno());
-        return CURLE_SSL_CONNECT_ERROR;
+
+      /* detail is already set to the SSL error above */
+
+      /* If we e.g. use SSLv2 request-method and the server doesn't like us
+       * (RST connection etc.), OpenSSL gives no explanation whatsoever and
+       * the SO_ERROR is also lost.
+       */
+      if (CURLE_SSL_CONNECT_ERROR == rc && errdetail == 0) {
+        failf(data, "Unknown SSL protocol error in connection to %s:%d ",
+              conn->host.name, conn->port);
+        return rc;
       }
-    } /* while()-loop for the select() */
-  } /* while()-loop for the SSL_connect() */
+      /* Could be a CERT problem */
+
+      SSL_strerror(errdetail, error_buffer, sizeof(error_buffer));
+      failf(data, "%s%s", cert_problem ? cert_problem : "", error_buffer);
+      return rc;
+    }
+  }
+  else {
+    /* we have been connected fine, we're not waiting for anything else. */
+    connssl->connecting_state = ssl_connect_3;
+
+    /* Informational message */
+    infof (data, "SSL connection using %s\n",
+           SSL_get_cipher(connssl->handle));
+
+    return CURLE_OK;
+  }
+}
+
+static CURLcode
+Curl_ossl_connect_step3(struct connectdata *conn,
+                  int sockindex)
+{
+  CURLcode retcode = CURLE_OK;
+  char * str;
+  long lerr;
+  ASN1_TIME *certdate;
+  void *ssl_sessionid=NULL;
+  struct SessionHandle *data = conn->data;
+  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
 
-  /* Informational message */
-  infof (data, "SSL connection using %s\n",
-         SSL_get_cipher(connssl->handle));
+  curlassert(ssl_connect_3 == connssl->connecting_state);
 
-  if(!ssl_sessionid) {
+  if(Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)) {
     /* Since this is not a cached session ID, then we want to stach this one
        in the cache! */
     SSL_SESSION *ssl_sessionid;
@@ -1529,9 +1543,114 @@ Curl_ossl_connect(struct connectdata *conn,
 
   X509_free(connssl->server_cert);
   connssl->server_cert = NULL;
+  connssl->connecting_state = ssl_connect_done;
   return retcode;
 }
 
+static CURLcode
+Curl_ossl_connect_common(struct connectdata *conn,
+                         int sockindex,
+                         bool nonblocking,
+                         bool *done)
+{
+  CURLcode retcode;
+  struct SessionHandle *data = conn->data;
+  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
+  curl_socket_t sockfd = conn->sock[sockindex];
+  long timeout_ms;
+
+  if (ssl_connect_1==connssl->connecting_state) {
+    retcode = Curl_ossl_connect_step1(conn, sockindex);
+    if (retcode)
+      return retcode;
+  }
+
+  timeout_ms = 0;
+  while (ssl_connect_2 == connssl->connecting_state ||
+         ssl_connect_2_reading == connssl->connecting_state ||
+         ssl_connect_2_writing == connssl->connecting_state) {
+
+    /* if ssl is expecting something, check if it's available. */
+    if (connssl->connecting_state == ssl_connect_2_reading
+        || connssl->connecting_state == ssl_connect_2_writing) {
+
+      int writefd = ssl_connect_2_writing==
+        connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+      int readfd = ssl_connect_2_reading==
+        connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+
+      while(1) {
+        int what = Curl_select(readfd, writefd, nonblocking?0:(int)timeout_ms);
+        if(what > 0)
+          /* reabable or writable, go loop in the outer loop */
+          break;
+        else if(0 == what) {
+          if (nonblocking) {
+            *done = FALSE;
+            return CURLE_OK;
+          }
+          else {
+            /* timeout */
+            failf(data, "SSL connection timeout");
+            return CURLE_OPERATION_TIMEDOUT;
+          }
+        }
+        else {
+          /* anything that gets here is fatally bad */
+          failf(data, "select on SSL socket, errno: %d", Curl_ourerrno());
+          return CURLE_SSL_CONNECT_ERROR;
+        }
+      } /* while()-loop for the select() */
+    }
+
+    /* get the timeout from step2 to avoid computing it twice. */
+    retcode = Curl_ossl_connect_step2(conn, sockindex, &timeout_ms);
+    if (retcode)
+      return retcode;
+
+  } /* repeat step2 until all transactions are done. */
+
+
+  if (ssl_connect_3==connssl->connecting_state) {
+    retcode = Curl_ossl_connect_step3(conn, sockindex);
+    if (retcode)
+      return retcode;
+  }
+
+  if (ssl_connect_done==connssl->connecting_state) {
+    *done = TRUE;
+  }
+  else {
+    *done = FALSE;
+  }
+
+  return CURLE_OK;
+}
+
+CURLcode
+Curl_ossl_connect_nonblocking(struct connectdata *conn,
+                              int sockindex,
+                              bool *done)
+{
+  return Curl_ossl_connect_common(conn, sockindex, TRUE, done);
+}
+
+CURLcode
+Curl_ossl_connect(struct connectdata *conn,
+                  int sockindex)
+{
+  CURLcode retcode;
+  bool done = FALSE;
+
+  retcode = Curl_ossl_connect_common(conn, sockindex, FALSE, &done);
+  if (retcode)
+    return retcode;
+
+  curlassert(done);
+
+  return CURLE_OK;
+}
+
 /* return number of sent (non-SSL) bytes */
 int Curl_ossl_send(struct connectdata *conn,
                    int sockindex,
index 00345317da23585c7e8620e2cff6acea751f2723..1bbc2cc1c21ea8600fd8e9501df2c4a092787a9a 100644 (file)
@@ -29,6 +29,9 @@
 
 #include "urldata.h"
 CURLcode Curl_ossl_connect(struct connectdata *conn, int sockindex);
+CURLcode Curl_ossl_connect_nonblocking(struct connectdata *conn, 
+                                       int sockindex, 
+                                       bool *done);
 void Curl_ossl_close(struct connectdata *conn); /* close a SSL connection */
 /* tell OpenSSL to close down all open information regarding connections (and
    thus session ID caching etc) */
index 9a715c9f0a2f09124521ec47019f1388538e5061..38a2a27da2a90dd81acbf2564b7cdf3801932a71 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -2990,6 +2990,8 @@ static CURLcode CreateConnection(struct SessionHandle *data,
     conn->curl_do_more = NULL;
     conn->curl_done = Curl_http_done;
     conn->curl_connect = Curl_http_connect;
+    conn->curl_connecting = Curl_https_connecting;
+    conn->curl_proto_fdset = Curl_https_proto_fdset;
 
 #else /* USE_SS */
     failf(data, LIBCURL_NAME
index 6cb3729b921eff96e04f6e257e938f2b04a6b44b..1418f3e9268c01aa9662aba799862ad56d1f333e 100644 (file)
@@ -128,13 +128,23 @@ struct krb4buffer {
   int eof_flag;
 };
 enum protection_level {
-    prot_clear,
-    prot_safe,
-    prot_confidential,
-    prot_private
+  prot_clear,
+  prot_safe,
+  prot_confidential,
+  prot_private
 };
 #endif
 
+/* enum for the nonblocking SSL connection state machine */
+typedef enum {
+  ssl_connect_1,
+  ssl_connect_2,
+  ssl_connect_2_reading,
+  ssl_connect_2_writing,
+  ssl_connect_3,
+  ssl_connect_done
+} ssl_connect_state;
+
 /* struct for data related to each SSL connection */
 struct ssl_connect_data {
   bool use;        /* use ssl encrypted communications TRUE/FALSE */
@@ -143,6 +153,7 @@ struct ssl_connect_data {
   SSL_CTX* ctx;
   SSL*     handle;
   X509*    server_cert;
+  ssl_connect_state connecting_state;
 #endif /* USE_SSLEAY */
 #ifdef USE_GNUTLS
   gnutls_session session;