SSL session sharing support added

With locking, plus test, plus documentation
This commit is contained in:
Alejandro Alvarez 2011-09-20 17:43:54 +02:00 committed by Daniel Stenberg
parent ff5ba6e43d
commit 5793bc370c
7 changed files with 331 additions and 8 deletions

View File

@ -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

View File

@ -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);

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

246
tests/libtest/lib586.c Normal file
View File

@ -0,0 +1,246 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2011, 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
* 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;
}