cURL / Mailing Lists / curl-library / Single Mail

curl-library

[PATCH] SSL session reuse with locking, plus test, plus documentation

From: Alejandro Alvarez <aalvarez_at_cern.ch>
Date: Tue, 20 Sep 2011 17:43:54 +0200

---
 docs/libcurl/curl_share_setopt.3 |    5 +
 lib/share.c                      |   24 ++++-
 lib/share.h                      |    4 +
 lib/sslgen.c                     |   49 +++++++-
 lib/sslgen.h                     |    2 +
 lib/url.c                        |    9 ++
 tests/libtest/lib586.c           |  246 ++++++++++++++++++++++++++++++++++++++
 7 files changed, 331 insertions(+), 8 deletions(-)
 create mode 100644 tests/libtest/lib586.c
diff --git a/docs/libcurl/curl_share_setopt.3 b/docs/libcurl/curl_share_setopt.3
index 5fff33d..295423a 100644
--- a/docs/libcurl/curl_share_setopt.3
+++ b/docs/libcurl/curl_share_setopt.3
@@ -64,6 +64,11 @@ Cached DNS hosts will be shared across the easy handles using this shared
 object. Note that when you use the multi interface, all easy handles added to
 the same multi handle will share DNS cache by default without this having to
 be used!
+.IP CURL_LOCK_DATA_SSL_SESSION
+SSL session IDs will be shared accross the easy handles using this shared
+object. This will reduce the time spent in the SSL handshake when reconnecting
+to the same server. Note SSL session IDs are reused within the same easy handle
+by default.
 .RE
 .IP CURLSHOPT_UNSHARE
 This option does the opposite of \fICURLSHOPT_SHARE\fP. It specifies that
diff --git a/lib/share.c b/lib/share.c
index a3db4de..a3eae16 100644
--- a/lib/share.c
+++ b/lib/share.c
@@ -25,6 +25,7 @@
 #include <curl/curl.h>
 #include "urldata.h"
 #include "share.h"
+#include "sslgen.h"
 #include "curl_memory.h"
 
 /* The last #include file should be: */
@@ -82,7 +83,16 @@ curl_share_setopt(CURLSH *sh, CURLSHoption option, ...)
       break;
 #endif   /* CURL_DISABLE_HTTP */
 
-    case CURL_LOCK_DATA_SSL_SESSION: /* not supported (yet) */
+    case CURL_LOCK_DATA_SSL_SESSION:
+      if(!share->sslsession) {
+        share->nsslsession = 8;
+        share->sslsession = calloc(share->nsslsession,
+                                   sizeof(struct curl_ssl_session));
+        if(!share->sslsession)
+          return CURLSHE_NOMEM;
+      }
+      break;
+
     case CURL_LOCK_DATA_CONNECT:     /* not supported (yet) */
 
     default:
@@ -112,6 +122,11 @@ curl_share_setopt(CURLSH *sh, CURLSHoption option, ...)
 #endif   /* CURL_DISABLE_HTTP */
 
     case CURL_LOCK_DATA_SSL_SESSION:
+      if(share->sslsession) {
+        free(share->sslsession);
+        share->sslsession = NULL;
+        share->nsslsession = 0;
+      }
       break;
 
     case CURL_LOCK_DATA_CONNECT:
@@ -148,6 +163,7 @@ CURLSHcode
 curl_share_cleanup(CURLSH *sh)
 {
   struct Curl_share *share = (struct Curl_share *)sh;
+  unsigned int i;
 
   if(share == NULL)
     return CURLSHE_INVALID;
@@ -170,6 +186,12 @@ curl_share_cleanup(CURLSH *sh)
   if(share->cookies)
     Curl_cookie_cleanup(share->cookies);
 
+  if(share->sslsession) {
+    for(i = 0; i < share->nsslsession; ++i)
+      Curl_ssl_kill_session(&(share->sslsession[i]));
+    free(share->sslsession);
+  }
+
   if(share->unlockfunc)
     share->unlockfunc(NULL, CURL_LOCK_DATA_SHARE, share->clientdata);
   free(share);
diff --git a/lib/share.h b/lib/share.h
index ea8e233..cf20000 100644
--- a/lib/share.h
+++ b/lib/share.h
@@ -26,6 +26,7 @@
 #include "setup.h"
 #include <curl/curl.h>
 #include "cookie.h"
+#include "urldata.h"
 
 /* SalfordC says "A structure member may not be volatile". Hence:
  */
@@ -46,6 +47,9 @@ struct Curl_share {
 
   struct curl_hash *hostcache;
   struct CookieInfo *cookies;
+
+  struct curl_ssl_session *sslsession;
+  unsigned int nsslsession;
 };
 
 CURLSHcode Curl_share_lock (struct SessionHandle *, curl_lock_data,
diff --git a/lib/sslgen.c b/lib/sslgen.c
index 005d82e..77c641b 100644
--- a/lib/sslgen.c
+++ b/lib/sslgen.c
@@ -62,6 +62,7 @@
 #include "url.h"
 #include "curl_memory.h"
 #include "progress.h"
+#include "share.h"
 /* The last #include file should be: */
 #include "memdebug.h"
 
@@ -236,6 +237,10 @@ int Curl_ssl_getsessionid(struct connectdata *conn,
     /* session ID re-use is disabled */
     return TRUE;
 
+  /* Lock for reading if shared */
+  if(data->share && data->share->sslsession == data->state.session)
+    Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SHARED);
+
   for(i=0; i< data->set.ssl.numsessions; i++) {
     check = &data->state.session[i];
     if(!check->sessionid)
@@ -254,13 +259,19 @@ int Curl_ssl_getsessionid(struct connectdata *conn,
     }
   }
   *ssl_sessionid = NULL;
+
+  /* Unlock for reading */
+  if(data->share && data->share->sslsession == data->state.session)
+    Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION);
+
+
   return TRUE;
 }
 
 /*
  * Kill a single session ID entry in the cache.
  */
-static int kill_session(struct curl_ssl_session *session)
+int Curl_ssl_kill_session(struct curl_ssl_session *session)
 {
   if(session->sessionid) {
     /* defensive check */
@@ -288,14 +299,23 @@ static int kill_session(struct curl_ssl_session *session)
 void Curl_ssl_delsessionid(struct connectdata *conn, void *ssl_sessionid)
 {
   int i;
-  for(i=0; i< conn->data->set.ssl.numsessions; i++) {
-    struct curl_ssl_session *check = &conn->data->state.session[i];
+  struct SessionHandle *data=conn->data;
+
+  if(data->share && data->share->sslsession == data->state.session)
+    Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION,
+                    CURL_LOCK_ACCESS_SINGLE);
+
+  for(i=0; i< data->set.ssl.numsessions; i++) {
+    struct curl_ssl_session *check = &data->state.session[i];
 
     if(check->sessionid == ssl_sessionid) {
-      kill_session(check);
+      Curl_ssl_kill_session(check);
       break;
     }
   }
+
+  if(data->share && data->share->sslsession == data->state.session)
+    Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION);
 }
 
 /*
@@ -325,6 +345,10 @@ CURLcode Curl_ssl_addsessionid(struct connectdata *conn,
   /* Now we should add the session ID and the host name to the cache, (remove
      the oldest if necessary) */
 
+  /* If using shared SSL session, lock! */
+  if(data->share && data->share->sslsession == data->state.session)
+    Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE);
+
   /* find an empty slot for us, or find the oldest */
   for(i=1; (i<data->set.ssl.numsessions) &&
         data->state.session[i].sessionid; i++) {
@@ -335,7 +359,7 @@ CURLcode Curl_ssl_addsessionid(struct connectdata *conn,
   }
   if(i == data->set.ssl.numsessions)
     /* cache is full, we must "kill" the oldest entry! */
-    kill_session(store);
+    Curl_ssl_kill_session(store);
   else
     store = &data->state.session[i]; /* use this slot */
 
@@ -349,6 +373,11 @@ CURLcode Curl_ssl_addsessionid(struct connectdata *conn,
   store->name = clone_host;               /* clone host name */
   store->remote_port = conn->remote_port; /* port number */
 
+
+  /* Unlock */
+  if(data->share && data->share->sslsession == data->state.session)
+    Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION);
+
   if(!Curl_clone_ssl_config(&conn->ssl_config, &store->ssl_config)) {
     store->sessionid = NULL; /* let caller free sessionid */
     free(clone_host);
@@ -363,14 +392,20 @@ void Curl_ssl_close_all(struct SessionHandle *data)
 {
   long i;
   /* kill the session ID cache */
-  if(data->state.session) {
+  if(data->state.session &&
+     !(data->share && data->share->sslsession == data->state.session)) {
+
+    Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE);
+
     for(i=0; i< data->set.ssl.numsessions; i++)
       /* the single-killer function handles empty table slots */
-      kill_session(&data->state.session[i]);
+      Curl_ssl_kill_session(&data->state.session[i]);
 
     /* free the cache data */
     free(data->state.session);
     data->state.session = NULL;
+
+    Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION);
   }
 
   curlssl_close_all(data);
diff --git a/lib/sslgen.h b/lib/sslgen.h
index ec8fe50..73164fd 100644
--- a/lib/sslgen.h
+++ b/lib/sslgen.h
@@ -64,6 +64,8 @@ int Curl_ssl_getsessionid(struct connectdata *conn,
 CURLcode Curl_ssl_addsessionid(struct connectdata *conn,
                                void *ssl_sessionid,
                                size_t idsize);
+/* Kill a single session ID entry in the cache */
+int Curl_ssl_kill_session(struct curl_ssl_session *session);
 /* delete a session from the cache */
 void Curl_ssl_delsessionid(struct connectdata *conn, void *ssl_sessionid);
 
diff --git a/lib/url.c b/lib/url.c
index 19f5912..f4233ff 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -2083,6 +2083,11 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
       if(data->share->cookies == data->cookies)
         data->cookies = NULL;
 
+      if(data->share->sslsession == data->state.session) {
+        data->state.session = NULL;
+        data->set.ssl.numsessions = 0;
+      }
+
       data->share->dirty--;
 
       Curl_share_unlock(data, CURL_LOCK_DATA_SHARE);
@@ -2114,6 +2119,10 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
         data->cookies = data->share->cookies;
       }
 #endif   /* CURL_DISABLE_HTTP */
+      if(data->share->sslsession) {
+        data->set.ssl.numsessions = data->share->nsslsession;
+        data->state.session = data->share->sslsession;
+      }
       Curl_share_unlock(data, CURL_LOCK_DATA_SHARE);
 
     }
diff --git a/tests/libtest/lib586.c b/tests/libtest/lib586.c
new file mode 100644
index 0000000..2cf04fe
--- /dev/null
+++ b/tests/libtest/lib586.c
@@ -0,0 +1,246 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel_at_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
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+#include "test.h"
+
+#include <curl/mprintf.h>
+
+#include "memdebug.h"
+
+#define THREADS 2
+
+/* struct containing data of a thread */
+struct Tdata {
+  CURLSH *share;
+  char *url;
+};
+
+struct userdata {
+  char *text;
+  int counter;
+};
+
+/* lock callback */
+static void my_lock(CURL *handle, curl_lock_data data, curl_lock_access laccess,
+          void *useptr )
+{
+  const char *what;
+  struct userdata *user = (struct userdata *)useptr;
+
+  (void)handle;
+  (void)laccess;
+
+  switch ( data ) {
+    case CURL_LOCK_DATA_SHARE:
+      what = "share";
+      break;
+    case CURL_LOCK_DATA_DNS:
+      what = "dns";
+      break;
+    case CURL_LOCK_DATA_COOKIE:
+      what = "cookie";
+      break;
+    case CURL_LOCK_DATA_SSL_SESSION:
+      what = "ssl_session";
+      break;
+    default:
+      fprintf(stderr, "lock: no such data: %d\n", (int)data);
+      return;
+  }
+  printf("lock:   %-6s [%s]: %d\n", what, user->text, user->counter);
+  user->counter++;
+}
+
+/* unlock callback */
+static void my_unlock(CURL *handle, curl_lock_data data, void *useptr )
+{
+  const char *what;
+  struct userdata *user = (struct userdata *)useptr;
+  (void)handle;
+  switch ( data ) {
+    case CURL_LOCK_DATA_SHARE:
+      what = "share";
+      break;
+    case CURL_LOCK_DATA_DNS:
+      what = "dns";
+      break;
+    case CURL_LOCK_DATA_COOKIE:
+      what = "cookie";
+      break;
+    case CURL_LOCK_DATA_SSL_SESSION:
+      what = "ssl_session";
+      break;
+    default:
+      fprintf(stderr, "unlock: no such data: %d\n", (int)data);
+      return;
+  }
+  printf("unlock: %-6s [%s]: %d\n", what, user->text, user->counter);
+  user->counter++;
+}
+
+/* the dummy thread function */
+static void *fire(void *ptr)
+{
+  CURLcode code;
+  struct Tdata *tdata = (struct Tdata*)ptr;
+  CURL *curl;
+  int i=0;
+
+  if ((curl = curl_easy_init()) == NULL) {
+    fprintf(stderr, "curl_easy_init() failed\n");
+    return NULL;
+  }
+
+  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+  curl_easy_setopt(curl, CURLOPT_VERBOSE,    1L);
+  curl_easy_setopt(curl, CURLOPT_URL,        tdata->url);
+  printf( "CURLOPT_SHARE\n" );
+  curl_easy_setopt(curl, CURLOPT_SHARE, tdata->share);
+
+  printf( "PERFORM\n" );
+  code = curl_easy_perform(curl);
+  if( code != CURLE_OK ) {
+    fprintf(stderr, "perform url '%s' repeat %d failed, curlcode %d\n",
+            tdata->url, i, (int)code);
+  }
+
+  printf( "CLEANUP\n" );
+  curl_easy_cleanup(curl);
+
+  return NULL;
+}
+
+/* test function */
+int test(char *URL)
+{
+  int res;
+  CURLSHcode scode = CURLSHE_OK;
+  char *url;
+  struct Tdata tdata;
+  CURL *curl;
+  CURLSH *share;
+  int i;
+  struct userdata user;
+
+  user.text = (char *)"Pigs in space";
+  user.counter = 0;
+
+  printf( "GLOBAL_INIT\n" );
+  if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
+    fprintf(stderr, "curl_global_init() failed\n");
+    return TEST_ERR_MAJOR_BAD;
+  }
+
+  /* prepare share */
+  printf( "SHARE_INIT\n" );
+  if ((share = curl_share_init()) == NULL) {
+    fprintf(stderr, "curl_share_init() failed\n");
+    curl_global_cleanup();
+    return TEST_ERR_MAJOR_BAD;
+  }
+
+  if ( CURLSHE_OK == scode ) {
+    printf( "CURLSHOPT_LOCKFUNC\n" );
+    scode = curl_share_setopt( share, CURLSHOPT_LOCKFUNC, my_lock);
+  }
+  if ( CURLSHE_OK == scode ) {
+    printf( "CURLSHOPT_UNLOCKFUNC\n" );
+    scode = curl_share_setopt( share, CURLSHOPT_UNLOCKFUNC, my_unlock);
+  }
+  if ( CURLSHE_OK == scode ) {
+    printf( "CURLSHOPT_USERDATA\n" );
+    scode = curl_share_setopt( share, CURLSHOPT_USERDATA, &user);
+  }
+  if ( CURLSHE_OK == scode ) {
+    printf( "CURL_LOCK_DATA_SSL_SESSION\n" );
+    scode = curl_share_setopt( share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
+  }
+
+  if ( CURLSHE_OK != scode ) {
+    fprintf(stderr, "curl_share_setopt() failed\n");
+    curl_share_cleanup(share);
+    curl_global_cleanup();
+    return TEST_ERR_MAJOR_BAD;
+  }
+
+
+  res = 0;
+
+  /* start treads */
+  for (i=1; i<=THREADS; i++ ) {
+
+    /* set thread data */
+    tdata.url   = URL;
+    tdata.share = share;
+
+    /* simulate thread, direct call of "thread" function */
+    printf( "*** run %d\n",i );
+    fire( &tdata );
+  }
+
+
+  /* fetch a another one */
+  printf( "*** run %d\n", i );
+  if ((curl = curl_easy_init()) == NULL) {
+    fprintf(stderr, "curl_easy_init() failed\n");
+    curl_share_cleanup(share);
+    curl_global_cleanup();
+    return TEST_ERR_MAJOR_BAD;
+  }
+
+  url = URL;
+  test_setopt( curl, CURLOPT_URL,        url );
+  printf( "CURLOPT_SHARE\n" );
+  test_setopt( curl, CURLOPT_SHARE,      share );
+
+  printf( "PERFORM\n" );
+  curl_easy_perform( curl );
+
+  /* try to free share, expect to fail because share is in use*/
+  printf( "try SHARE_CLEANUP...\n" );
+  scode = curl_share_cleanup( share );
+  if ( scode==CURLSHE_OK )
+  {
+    fprintf(stderr, "curl_share_cleanup succeed but error expected\n");
+    share = NULL;
+  } else {
+    printf( "SHARE_CLEANUP failed, correct\n" );
+  }
+
+test_cleanup:
+
+  /* clean up last handle */
+  printf( "CLEANUP\n" );
+  curl_easy_cleanup( curl );
+
+  /* free share */
+  printf( "SHARE_CLEANUP\n" );
+  scode = curl_share_cleanup( share );
+  if ( scode!=CURLSHE_OK )
+    fprintf(stderr, "curl_share_cleanup failed, code errno %d\n",
+            (int)scode);
+
+  printf( "GLOBAL_CLEANUP\n" );
+  curl_global_cleanup();
+
+  return res;
+}
+
-- 
1.7.0.4
--------------060508010308030505040608
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
--------------060508010308030505040608--
Received on 2001-09-17