cURL / Mailing Lists / curl-library / Single Mail

curl-library

[PATCH] GnuTLS: make the connection phase non-blocking when multi interface is used

From: Jerome Vouillon <vouillon_at_pps.jussieu.fr>
Date: Thu, 15 Apr 2010 13:36:14 +0200

---
 lib/gtls.c    |  209 ++++++++++++++++++++++++++++++++++++++++----------------
 lib/gtls.h    |    4 +
 lib/http.c    |   14 +----
 lib/urldata.h |    1 +
 4 files changed, 155 insertions(+), 73 deletions(-)
diff --git a/lib/gtls.c b/lib/gtls.c
index b7fa3c9..1749eae 100644
--- a/lib/gtls.c
+++ b/lib/gtls.c
@@ -182,54 +182,76 @@ static void unload_file(gnutls_datum data) {
 }
 
 
-/* this function does a BLOCKING SSL/TLS (re-)handshake */
+/* this function does a SSL/TLS (re-)handshake */
 static CURLcode handshake(struct connectdata *conn,
-                          gnutls_session session,
                           int sockindex,
-                          bool duringconnect)
+                          bool duringconnect,
+                          bool nonblocking)
 {
   struct SessionHandle *data = conn->data;
+  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
+  gnutls_session session = conn->ssl[sockindex].session;
+  curl_socket_t sockfd = conn->sock[sockindex];
+  long timeout_ms;
   int rc;
-  if(!gtls_inited)
-    Curl_gtls_init();
-  do {
-    rc = gnutls_handshake(session);
+  int what;
 
-    if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) {
-      long timeout_ms = Curl_timeleft(conn, NULL, duringconnect);
+  while(1) {
+    /* check allowed time left */
+    timeout_ms = Curl_timeleft(conn, NULL, duringconnect);
 
-      if(timeout_ms < 0) {
-        /* a precaution, no need to continue if time already is up */
-        failf(data, "SSL connection timeout");
-        return CURLE_OPERATION_TIMEDOUT;
-      }
+    if(timeout_ms < 0) {
+      /* no need to continue if time already is up */
+      failf(data, "SSL connection timeout");
+      return CURLE_OPERATION_TIMEDOUT;
+    }
 
-      rc = Curl_socket_ready(conn->sock[sockindex],
-                       conn->sock[sockindex], (int)timeout_ms);
-      if(rc > 0)
-        /* reabable or writable, go loop*/
-        continue;
-      else if(0 == rc) {
-        /* timeout */
-        failf(data, "SSL connection timeout");
-        return CURLE_OPERATION_TIMEDOUT;
-      }
-      else {
-        /* anything that gets here is fatally bad */
+    /* 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) {
+
+      curl_socket_t writefd = ssl_connect_2_writing==
+        connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+      curl_socket_t readfd = ssl_connect_2_reading==
+        connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+
+      what = Curl_socket_ready(readfd, writefd,
+                               nonblocking?0:(int)timeout_ms);
+      if(what < 0) {
+        /* fatal error */
         failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
         return CURLE_SSL_CONNECT_ERROR;
       }
+      else if(0 == what) {
+        if(nonblocking) {
+          return CURLE_OK;
+        }
+        else {
+          /* timeout */
+          failf(data, "SSL connection timeout");
+          return CURLE_OPERATION_TIMEDOUT;
+        }
+      }
+      /* socket is readable or writable */
     }
-    else
-      break;
-  } while(1);
 
-  if(rc < 0) {
-    failf(data, "gnutls_handshake() failed: %s", gnutls_strerror(rc));
-    return CURLE_SSL_CONNECT_ERROR;
-  }
+    rc = gnutls_handshake(session);
 
-  return CURLE_OK;
+    if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) {
+      connssl->connecting_state =
+        gnutls_record_get_direction(session)?
+        ssl_connect_2_writing:ssl_connect_2_reading;
+      if(nonblocking) {
+        return CURLE_OK;
+      }
+    } else if (rc < 0) {
+      failf(data, "gnutls_handshake() failed: %s", gnutls_strerror(rc));
+    } else {
+      /* Reset our connect state machine */
+      connssl->connecting_state = ssl_connect_1;
+      return CURLE_OK;
+    }
+  }
 }
 
 static gnutls_x509_crt_fmt do_file_type(const char *type)
@@ -243,31 +265,14 @@ static gnutls_x509_crt_fmt do_file_type(const char *type)
   return -1;
 }
 
-
-/*
- * This function is called after the TCP connect has completed. Setup the TLS
- * layer and do all necessary magic.
- */
-CURLcode
-Curl_gtls_connect(struct connectdata *conn,
-                  int sockindex)
-
+static CURLcode
+gtls_connect_step1(struct connectdata *conn,
+                   int sockindex)
 {
   static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 };
   struct SessionHandle *data = conn->data;
   gnutls_session session;
   int rc;
-  unsigned int cert_list_size;
-  const gnutls_datum *chainp;
-  unsigned int verify_status;
-  gnutls_x509_crt x509_cert,x509_issuer;
-  gnutls_datum issuerp;
-  char certbuf[256]; /* big enough? */
-  size_t size;
-  unsigned int algo;
-  unsigned int bits;
-  time_t certclock;
-  const char *ptr;
   void *ssl_sessionid;
   size_t ssl_idsize;
   bool sni = TRUE; /* default is SNI enabled */
@@ -411,10 +416,29 @@ Curl_gtls_connect(struct connectdata *conn,
     infof (data, "SSL re-using session ID\n");
   }
 
-  rc = handshake(conn, session, sockindex, TRUE);
-  if(rc)
-    /* handshake() sets its own error message with failf() */
-    return rc;
+  return CURLE_OK;
+}
+
+static CURLcode
+gtls_connect_step3(struct connectdata *conn,
+                   int sockindex)
+{
+  unsigned int cert_list_size;
+  const gnutls_datum *chainp;
+  unsigned int verify_status;
+  gnutls_x509_crt x509_cert,x509_issuer;
+  gnutls_datum issuerp;
+  char certbuf[256]; /* big enough? */
+  size_t size;
+  unsigned int algo;
+  unsigned int bits;
+  time_t certclock;
+  const char *ptr;
+  struct SessionHandle *data = conn->data;
+  gnutls_session session = conn->ssl[sockindex].session;
+  int rc;
+  int incache;
+  void *ssl_sessionid;
 
   /* This function will return the peer's raw certificate (chain) as sent by
      the peer. These certificates are in raw format (DER encoded for
@@ -623,20 +647,85 @@ Curl_gtls_connect(struct connectdata *conn,
       /* extract session ID to the allocated buffer */
       gnutls_session_get_data(session, connect_sessionid, &connect_idsize);
 
-      if(ssl_sessionid)
+      incache = !(Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL));
+      if (incache) {
         /* there was one before in the cache, so instead of risking that the
            previous one was rejected, we just kill that and store the new */
         Curl_ssl_delsessionid(conn, ssl_sessionid);
+      }
 
       /* store this session id */
       return Curl_ssl_addsessionid(conn, connect_sessionid, connect_idsize);
     }
+  }
+
+  return CURLE_OK;
+}
+
+
+/*
+ * This function is called after the TCP connect has completed. Setup the TLS
+ * layer and do all necessary magic.
+ */
+/* We use connssl->connecting_state to keep track of the connection status;
+   there are three states: 'ssl_connect_1' (not started yet or complete),
+   'ssl_connect_2_reading' (waiting for data from server), and
+   'ssl_connect_2_writing' (waiting to be able to write).
+ */
+static CURLcode
+gtls_connect_common(struct connectdata *conn,
+                    int sockindex,
+                    bool nonblocking,
+                    bool *done)
+{
+  int rc;
+  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
 
+  /* Initiate the connection, if not already done */
+  if(ssl_connect_1==connssl->connecting_state)
+    gtls_connect_step1 (conn, sockindex);
+
+  rc = handshake(conn, sockindex, TRUE, nonblocking);
+  if(rc)
+    /* handshake() sets its own error message with failf() */
+    return rc;
+
+  /* Finish connecting once the handshake is done */
+  if(ssl_connect_1==connssl->connecting_state) {
+    rc = gtls_connect_step3(conn, sockindex);
+    if(rc)
+      return rc;
   }
 
+  *done = ssl_connect_1==connssl->connecting_state;
+
   return CURLE_OK;
 }
 
+CURLcode
+Curl_gtls_connect_nonblocking(struct connectdata *conn,
+                              int sockindex,
+                              bool *done)
+{
+  return gtls_connect_common(conn, sockindex, TRUE, done);
+}
+
+CURLcode
+Curl_gtls_connect(struct connectdata *conn,
+                  int sockindex)
+
+{
+  CURLcode retcode;
+  bool done = FALSE;
+
+  retcode = gtls_connect_common(conn, sockindex, FALSE, &done);
+  if(retcode)
+    return retcode;
+
+  DEBUGASSERT(done);
+
+  return CURLE_OK;
+}
 
 /* for documentation see Curl_ssl_send() in sslgen.h */
 ssize_t Curl_gtls_send(struct connectdata *conn,
@@ -769,7 +858,7 @@ ssize_t Curl_gtls_recv(struct connectdata *conn, /* connection data */
   if(ret == GNUTLS_E_REHANDSHAKE) {
     /* BLOCKING call, this is bad but a work-around for now. Fixing this "the
        proper way" takes a whole lot of work. */
-    CURLcode rc = handshake(conn, conn->ssl[num].session, num, FALSE);
+    CURLcode rc = handshake(conn, num, FALSE, FALSE);
     if(rc)
       /* handshake() writes error message on its own */
       *curlcode = rc;
diff --git a/lib/gtls.h b/lib/gtls.h
index 9fe618a..da3489b 100644
--- a/lib/gtls.h
+++ b/lib/gtls.h
@@ -27,6 +27,9 @@
 int Curl_gtls_init(void);
 int Curl_gtls_cleanup(void);
 CURLcode Curl_gtls_connect(struct connectdata *conn, int sockindex);
+CURLcode Curl_gtls_connect_nonblocking(struct connectdata *conn,
+                                       int sockindex,
+                                       bool *done);
 
 /* tell GnuTLS to close down all open information regarding connections (and
    thus session ID caching etc) */
@@ -52,6 +55,7 @@ int Curl_gtls_seed(struct SessionHandle *data);
 #define curlssl_init Curl_gtls_init
 #define curlssl_cleanup Curl_gtls_cleanup
 #define curlssl_connect Curl_gtls_connect
+#define curlssl_connect_nonblocking Curl_gtls_connect_nonblocking
 #define curlssl_session_free(x)  Curl_gtls_session_free(x)
 #define curlssl_close_all Curl_gtls_close_all
 #define curlssl_close Curl_gtls_close
diff --git a/lib/http.c b/lib/http.c
index 7ffc34d..f77e677 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -1817,7 +1817,7 @@ static CURLcode https_connecting(struct connectdata *conn, bool *done)
 }
 #endif
 
-#ifdef USE_SSLEAY
+#if defined(USE_SSLEAY) || defined(USE_GNUTLS)
 /* This function is OpenSSL-specific. It should be made to query the generic
    SSL layer instead. */
 static int https_getsock(struct connectdata *conn,
@@ -1844,17 +1844,6 @@ static int https_getsock(struct connectdata *conn,
   return CURLE_OK;
 }
 #else
-#ifdef USE_GNUTLS
-static int https_getsock(struct connectdata *conn,
-                         curl_socket_t *socks,
-                         int numsocks)
-{
-  (void)conn;
-  (void)socks;
-  (void)numsocks;
-  return GETSOCK_BLANK;
-}
-#else
 #ifdef USE_NSS
 static int https_getsock(struct connectdata *conn,
                          curl_socket_t *socks,
@@ -1879,7 +1868,6 @@ static int https_getsock(struct connectdata *conn,
 #endif
 #endif
 #endif
-#endif
 
 /*
  * Curl_http_done() gets called from Curl_done() after a single HTTP request
diff --git a/lib/urldata.h b/lib/urldata.h
index ad172d6..02233b6 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -231,6 +231,7 @@ struct ssl_connect_data {
 #ifdef USE_GNUTLS
   gnutls_session session;
   gnutls_certificate_credentials cred;
+  ssl_connect_state connecting_state;
 #endif /* USE_GNUTLS */
 #ifdef USE_NSS
   PRFileDesc *handle;
-- 
1.7.0
--HlL+5n6rz5pIUxbD
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette:  http://curl.haxx.se/mail/etiquette.html
--HlL+5n6rz5pIUxbD--
Received on 2001-09-17