ssl_session_cache

This commit is contained in:
Igor Sysoev 2007-01-02 23:55:05 +00:00
parent baf3d4d28f
commit 7e1f8df4d5
2 changed files with 579 additions and 14 deletions

View file

@ -18,6 +18,17 @@ typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c,
#define NGX_DEFLAUT_CIPHERS "ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP"
#define NGX_HTTP_SSL_MAX_SESSION_SIZE \
(4096 - offsetof(ngx_http_ssl_cached_sess_t, asn1))
#define NGX_HTTP_SSL_DFLT_BUILTIN_SCACHE -2
#define NGX_HTTP_SSL_NO_BUILTIN_SCACHE -3
static void ngx_http_ssl_expire_sessions(ngx_http_ssl_sesssion_cache_t *cache,
ngx_slab_pool_t *shpool, ngx_uint_t expire);
static ngx_int_t ngx_http_ssl_static_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_ssl_variable(ngx_http_request_t *r,
@ -28,6 +39,9 @@ static void *ngx_http_ssl_create_srv_conf(ngx_conf_t *cf);
static char *ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf,
void *parent, void *child);
static char *ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
#if !defined (SSL_OP_CIPHER_SERVER_PREFERENCE)
static char *ngx_http_ssl_nosupported(ngx_conf_t *cf, ngx_command_t *cmd,
@ -115,6 +129,13 @@ static ngx_command_t ngx_http_ssl_commands[] = {
ngx_http_ssl_nosupported, 0, 0, ngx_http_ssl_openssl097 },
#endif
{ ngx_string("ssl_session_cache"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE12,
ngx_http_ssl_session_cache,
NGX_HTTP_SRV_CONF_OFFSET,
0,
NULL },
{ ngx_string("ssl_session_timeout"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_sec_slot,
@ -181,6 +202,384 @@ static ngx_http_variable_t ngx_http_ssl_vars[] = {
static u_char ngx_http_session_id_ctx[] = "HTTP";
static ngx_int_t
ngx_http_ssl_session_cache_init(ngx_shm_zone_t *shm_zone)
{
ngx_slab_pool_t *shpool;
ngx_rbtree_node_t *sentinel;
ngx_http_ssl_sesssion_cache_t *cache;
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
cache = ngx_slab_alloc(shpool, sizeof(ngx_http_ssl_sesssion_cache_t));
if (cache == NULL) {
return NGX_ERROR;
}
cache->session_cache_head.prev = NULL;
cache->session_cache_head.next = &cache->session_cache_tail;
cache->session_cache_tail.prev = &cache->session_cache_head;
cache->session_cache_tail.next = NULL;
cache->session_rbtree = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_t));
if (cache->session_rbtree == NULL) {
return NGX_ERROR;
}
sentinel = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_node_t));
if (sentinel == NULL) {
return NGX_ERROR;
}
ngx_rbtree_sentinel_init(sentinel);
cache->session_rbtree->root = sentinel;
cache->session_rbtree->sentinel = sentinel;
cache->session_rbtree->insert = ngx_rbtree_insert_value;
shm_zone->data = cache;
return NGX_OK;
}
/*
* OpenSSL's i2d_SSL_SESSION() and d2i_SSL_SESSION are slow,
* so they are outside the code locked by shared pool mutex
*/
static int
ngx_http_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess)
{
int len;
u_char *p, *id;
uint32_t hash;
ngx_time_t *tp;
ngx_slab_pool_t *shpool;
ngx_connection_t *c;
ngx_http_request_t *r;
ngx_http_ssl_sess_id_t *sess_id;
ngx_http_ssl_srv_conf_t *sscf;
ngx_http_ssl_cached_sess_t *cached_sess;
ngx_http_ssl_sesssion_cache_t *cache;
u_char buf[NGX_HTTP_SSL_MAX_SESSION_SIZE];
len = i2d_SSL_SESSION(sess, NULL);
/* do not cache too big session */
if (len > (int) NGX_HTTP_SSL_MAX_SESSION_SIZE) {
return 0;
}
c = ngx_ssl_get_connection(ssl_conn);
r = c->data;
p = buf;
i2d_SSL_SESSION(sess, &p);
sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);
cache = sscf->shm_zone->data;
shpool = (ngx_slab_pool_t *) sscf->shm_zone->shm.addr;
ngx_shmtx_lock(&shpool->mutex);
/* drop one or two expired sessions */
ngx_http_ssl_expire_sessions(cache, shpool, 1);
cached_sess = ngx_slab_alloc_locked(shpool,
offsetof(ngx_http_ssl_cached_sess_t, asn1) + len);
if (cached_sess == NULL) {
/* drop the oldest non-expired session and try once more */
ngx_http_ssl_expire_sessions(cache, shpool, 0);
cached_sess = ngx_slab_alloc_locked(shpool,
offsetof(ngx_http_ssl_cached_sess_t, asn1) + len);
if (cached_sess == NULL) {
id = NULL;
goto failed;
}
}
id = ngx_slab_alloc_locked(shpool, sess->session_id_length);
if (id == NULL) {
goto failed;
}
sess_id = ngx_slab_alloc_locked(shpool, sizeof(ngx_http_ssl_sess_id_t));
if (sess_id == NULL) {
goto failed;
}
ngx_memcpy(&cached_sess->asn1[0], buf, len);
ngx_memcpy(id, sess->session_id, sess->session_id_length);
hash = ngx_crc32_short(sess->session_id, sess->session_id_length);
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http ssl new session: %08XD:%d:%d",
hash, sess->session_id_length, len);
sess_id->node.key = hash;
sess_id->node.data = (u_char) sess->session_id_length;
sess_id->id = id;
sess_id->len = len;
sess_id->session = cached_sess;
tp = ngx_timeofday();
cached_sess->expire = tp->sec + sscf->session_timeout;
cached_sess->sess_id = sess_id;
cached_sess->next = cache->session_cache_head.next;
cached_sess->next->prev = cached_sess;
cached_sess->prev = &cache->session_cache_head;
cache->session_cache_head.next = cached_sess;
ngx_rbtree_insert(cache->session_rbtree, &sess_id->node);
ngx_shmtx_unlock(&shpool->mutex);
return 0;
failed:
if (cached_sess) {
ngx_slab_free_locked(shpool, cached_sess);
}
if (id) {
ngx_slab_free_locked(shpool, id);
}
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
"could not add new SSL session to the session cache");
return 0;
}
static ngx_ssl_session_t *
ngx_http_ssl_get_session(ngx_ssl_conn_t *ssl_conn, u_char *id, int len,
int *copy)
{
#if OPENSSL_VERSION_NUMBER >= 0x00908000
const
#endif
u_char *p;
uint32_t hash;
ngx_time_t *tp;
ngx_slab_pool_t *shpool;
ngx_connection_t *c;
ngx_rbtree_node_t *node, *sentinel;
ngx_ssl_session_t *sess;
ngx_http_request_t *r;
ngx_http_ssl_sess_id_t *sess_id;
ngx_http_ssl_srv_conf_t *sscf;
ngx_http_ssl_cached_sess_t *cached_sess;
ngx_http_ssl_sesssion_cache_t *cache;
u_char buf[NGX_HTTP_SSL_MAX_SESSION_SIZE];
c = ngx_ssl_get_connection(ssl_conn);
r = c->data;
sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);
hash = ngx_crc32_short(id, len);
*copy = 0;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http ssl get session: %08XD:%d", hash, len);
cache = sscf->shm_zone->data;
if (cache->session_rbtree == NULL) {
return NULL;
}
sess = NULL;
shpool = (ngx_slab_pool_t *) sscf->shm_zone->shm.addr;
ngx_shmtx_lock(&shpool->mutex);
node = cache->session_rbtree->root;
sentinel = cache->session_rbtree->sentinel;
while (node != sentinel) {
if (hash < node->key) {
node = node->left;
continue;
}
if (hash > node->key) {
node = node->right;
continue;
}
if (hash == node->key && (u_char) len == node->data) {
sess_id = (ngx_http_ssl_sess_id_t *) node;
if (ngx_strncmp(id, sess_id->id, len) == 0) {
cached_sess = sess_id->session;
tp = ngx_timeofday();
if (cached_sess->expire > tp->sec) {
ngx_memcpy(buf, &cached_sess->asn1[0], sess_id->len);
ngx_shmtx_unlock(&shpool->mutex);
p = buf;
sess = d2i_SSL_SESSION(NULL, &p, sess_id->len);
return sess;
}
cached_sess->next->prev = cached_sess->prev;
cached_sess->prev->next = cached_sess->next;
ngx_rbtree_delete(cache->session_rbtree, node);
ngx_slab_free_locked(shpool, cached_sess);
ngx_slab_free_locked(shpool, sess_id->id);
ngx_slab_free_locked(shpool, sess_id);
sess = NULL;
break;
}
}
node = node->right;
}
ngx_shmtx_unlock(&shpool->mutex);
return sess;
}
static void
ngx_http_ssl_remove_session(SSL_CTX *ssl, ngx_ssl_session_t *sess)
{
u_char *id, len;
uint32_t hash;
ngx_slab_pool_t *shpool;
ngx_rbtree_node_t *node, *sentinel;
ngx_http_ssl_sess_id_t *sess_id;
ngx_http_ssl_srv_conf_t *sscf;
ngx_http_ssl_cached_sess_t *cached_sess;
ngx_http_ssl_sesssion_cache_t *cache;
sscf = ngx_ssl_get_server_conf(ssl);
cache = sscf->shm_zone->data;
id = sess->session_id;
len = (u_char) sess->session_id_length;
hash = ngx_crc32_short(id, (size_t) len);
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
"http ssl remove session: %08XD:%d", hash, len);
shpool = (ngx_slab_pool_t *) sscf->shm_zone->shm.addr;
ngx_shmtx_lock(&shpool->mutex);
node = cache->session_rbtree->root;
sentinel = cache->session_rbtree->sentinel;
while (node != sentinel) {
if (hash < node->key) {
node = node->left;
continue;
}
if (hash > node->key) {
node = node->right;
continue;
}
if (hash == node->key && len == node->data) {
sess_id = (ngx_http_ssl_sess_id_t *) node;
if (ngx_strncmp(id, sess_id->id, (size_t) len) == 0) {
cached_sess = sess_id->session;
cached_sess->next->prev = cached_sess->prev;
cached_sess->prev->next = cached_sess->next;
ngx_rbtree_delete(cache->session_rbtree, node);
ngx_slab_free_locked(shpool, cached_sess);
ngx_slab_free_locked(shpool, sess_id->id);
ngx_slab_free_locked(shpool, sess_id);
break;
}
}
node = node->right;
}
ngx_shmtx_unlock(&shpool->mutex);
}
static void
ngx_http_ssl_expire_sessions(ngx_http_ssl_sesssion_cache_t *cache,
ngx_slab_pool_t *shpool, ngx_uint_t n)
{
ngx_time_t *tp;
ngx_http_ssl_sess_id_t *sess_id;
ngx_http_ssl_cached_sess_t *sess;
tp = ngx_timeofday();
while (n < 3) {
sess = cache->session_cache_tail.prev;
if (sess == &cache->session_cache_head) {
return;
}
if (n++ != 0 && sess->expire > tp->sec) {
break;
}
sess->next->prev = sess->prev;
sess->prev->next = sess->next;
sess_id = sess->sess_id;
ngx_rbtree_delete(cache->session_rbtree, &sess_id->node);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
"expire session: %08Xi", sess_id->node.key);
ngx_slab_free_locked(shpool, sess);
ngx_slab_free_locked(shpool, sess_id->id);
ngx_slab_free_locked(shpool, sess_id);
}
}
static ngx_int_t
ngx_http_ssl_static_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
@ -276,13 +675,15 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf)
* sscf->client_certificate.data = NULL;
* sscf->ciphers.len = 0;
* sscf->ciphers.data = NULL;
* sscf->shm_zone = NULL;
*/
sscf->enable = NGX_CONF_UNSET;
sscf->session_timeout = NGX_CONF_UNSET;
sscf->verify = NGX_CONF_UNSET;
sscf->verify_depth = NGX_CONF_UNSET;
sscf->prefer_server_ciphers = NGX_CONF_UNSET;
sscf->builtin_session_cache = NGX_CONF_UNSET;
sscf->session_timeout = NGX_CONF_UNSET;
return sscf;
}
@ -294,6 +695,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_http_ssl_srv_conf_t *prev = parent;
ngx_http_ssl_srv_conf_t *conf = child;
long cache_mode;
ngx_pool_cleanup_t *cln;
ngx_conf_merge_value(conf->enable, prev->enable, 0);
@ -380,17 +782,149 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
return NGX_CONF_ERROR;
}
SSL_CTX_set_session_cache_mode(conf->ssl.ctx, SSL_SESS_CACHE_SERVER);
ngx_conf_merge_value(conf->builtin_session_cache,
prev->builtin_session_cache,
NGX_HTTP_SSL_DFLT_BUILTIN_SCACHE);
if (conf->shm_zone == NULL) {
conf->shm_zone = prev->shm_zone;
}
cache_mode = SSL_SESS_CACHE_SERVER;
if (conf->shm_zone
&& conf->builtin_session_cache == NGX_HTTP_SSL_NO_BUILTIN_SCACHE)
{
cache_mode |= SSL_SESS_CACHE_NO_INTERNAL;
}
SSL_CTX_set_session_cache_mode(conf->ssl.ctx, cache_mode);
SSL_CTX_set_session_id_context(conf->ssl.ctx, ngx_http_session_id_ctx,
sizeof(ngx_http_session_id_ctx) - 1);
if (conf->builtin_session_cache != NGX_HTTP_SSL_NO_BUILTIN_SCACHE) {
if (conf->builtin_session_cache != NGX_HTTP_SSL_DFLT_BUILTIN_SCACHE) {
SSL_CTX_sess_set_cache_size(conf->ssl.ctx,
conf->builtin_session_cache);
}
SSL_CTX_set_timeout(conf->ssl.ctx, conf->session_timeout);
}
if (conf->shm_zone) {
SSL_CTX_sess_set_new_cb(conf->ssl.ctx, ngx_http_ssl_new_session);
SSL_CTX_sess_set_get_cb(conf->ssl.ctx, ngx_http_ssl_get_session);
SSL_CTX_sess_set_remove_cb(conf->ssl.ctx, ngx_http_ssl_remove_session);
}
return NGX_CONF_OK;
}
static char *
ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_ssl_srv_conf_t *sscf = conf;
size_t len;
ngx_str_t *value, name, size;
ngx_int_t n;
ngx_uint_t i, j;
value = cf->args->elts;
for (i = 1; i < cf->args->nelts; i++) {
if (ngx_strcmp(value[i].data, "builtin") == 0) {
sscf->builtin_session_cache = NGX_HTTP_SSL_DFLT_BUILTIN_SCACHE;
continue;
}
if (value[i].len > sizeof("builtin:") - 1
&& ngx_strncmp(value[i].data, "builtin:", sizeof("builtin:") - 1)
== 0)
{
n = ngx_atoi(value[i].data + sizeof("builtin:") - 1,
value[i].len - (sizeof("builtin:") - 1));
if (n == NGX_ERROR) {
goto invalid;
}
sscf->builtin_session_cache = n;
continue;
}
if (value[i].len > sizeof("shared:") - 1
&& ngx_strncmp(value[i].data, "shared:", sizeof("shared:") - 1)
== 0)
{
len = 0;
for (j = sizeof("shared:") - 1; j < value[i].len; j++) {
if (value[i].data[j] == ':') {
break;
}
len++;
}
if (len == 0) {
goto invalid;
}
name.len = len;
name.data = value[i].data + sizeof("shared:") - 1;
size.len = value[i].len - j - 1;
size.data = name.data + len + 1;
n = ngx_parse_size(&size);
if (n == NGX_ERROR) {
goto invalid;
}
if (n < (ngx_int_t) (8 * ngx_pagesize)) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"session cache \"%V\" to small",
&value[i]);
return NGX_CONF_ERROR;
}
sscf->shm_zone = ngx_shared_memory_add(cf, &name, n,
&ngx_http_ssl_module);
if (sscf->shm_zone == NULL) {
return NGX_CONF_ERROR;
}
sscf->shm_zone->init = ngx_http_ssl_session_cache_init;
continue;
}
goto invalid;
}
if (sscf->shm_zone && sscf->builtin_session_cache == NGX_CONF_UNSET) {
sscf->builtin_session_cache = NGX_HTTP_SSL_NO_BUILTIN_SCACHE;
}
return NGX_CONF_OK;
invalid:
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid session cache \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
#if !defined (SSL_OP_CIPHER_SERVER_PREFERENCE)
static char *

View file

@ -13,6 +13,33 @@
#include <ngx_http.h>
typedef struct ngx_http_ssl_cached_sess_s ngx_http_ssl_cached_sess_t;
typedef struct {
ngx_rbtree_node_t node;
u_char *id;
size_t len;
ngx_http_ssl_cached_sess_t *session;
} ngx_http_ssl_sess_id_t;
struct ngx_http_ssl_cached_sess_s {
ngx_http_ssl_cached_sess_t *prev;
ngx_http_ssl_cached_sess_t *next;
time_t expire;
ngx_http_ssl_sess_id_t *sess_id;
u_char asn1[1];
};
typedef struct {
ngx_rbtree_t *session_rbtree;
ngx_http_ssl_cached_sess_t session_cache_head;
ngx_http_ssl_cached_sess_t session_cache_tail;
} ngx_http_ssl_sesssion_cache_t;
typedef struct {
ngx_flag_t enable;
@ -25,6 +52,8 @@ typedef struct {
ngx_int_t verify;
ngx_int_t verify_depth;
ssize_t builtin_session_cache;
time_t session_timeout;
ngx_str_t certificate;
@ -32,6 +61,8 @@ typedef struct {
ngx_str_t client_certificate;
ngx_str_t ciphers;
ngx_shm_zone_t *shm_zone;
} ngx_http_ssl_srv_conf_t;