changeset 973:e1ede83911ef

ssl_session_cache
author Igor Sysoev <igor@sysoev.ru>
date Tue, 02 Jan 2007 23:55:05 +0000
parents 6e7a20529f53
children 8dfb3aa75de2
files src/http/modules/ngx_http_ssl_module.c src/http/modules/ngx_http_ssl_module.h
diffstat 2 files changed, 579 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/src/http/modules/ngx_http_ssl_module.c
+++ b/src/http/modules/ngx_http_ssl_module.c
@@ -18,6 +18,17 @@ typedef ngx_int_t (*ngx_ssl_variable_han
 #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_con
 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_comma
       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,
@@ -182,6 +203,384 @@ static u_char ngx_http_session_id_ctx[] 
 
 
 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 
      *     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 *
     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 *
         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);
 
-    SSL_CTX_set_timeout(conf->ssl.ctx, conf->session_timeout);
+    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 *
--- a/src/http/modules/ngx_http_ssl_module.h
+++ b/src/http/modules/ngx_http_ssl_module.h
@@ -13,25 +13,56 @@
 #include <ngx_http.h>
 
 
+typedef struct ngx_http_ssl_cached_sess_s  ngx_http_ssl_cached_sess_t;
+
+
 typedef struct {
-    ngx_flag_t      enable;
+    ngx_rbtree_node_t               node;
+    u_char                         *id;
+    size_t                          len;
+    ngx_http_ssl_cached_sess_t     *session;
+} ngx_http_ssl_sess_id_t;
 
-    ngx_ssl_t       ssl;
 
-    ngx_flag_t      prefer_server_ciphers;
+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];
+};
 
-    ngx_uint_t      protocols;
 
-    ngx_int_t       verify;
-    ngx_int_t       verify_depth;
+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;
+
 
-    time_t          session_timeout;
+typedef struct {
+    ngx_flag_t                      enable;
+
+    ngx_ssl_t                       ssl;
+
+    ngx_flag_t                      prefer_server_ciphers;
 
-    ngx_str_t       certificate;
-    ngx_str_t       certificate_key;
-    ngx_str_t       client_certificate;
+    ngx_uint_t                      protocols;
+
+    ngx_int_t                       verify;
+    ngx_int_t                       verify_depth;
+
+    ssize_t                         builtin_session_cache;
 
-    ngx_str_t       ciphers;
+    time_t                          session_timeout;
+
+    ngx_str_t                       certificate;
+    ngx_str_t                       certificate_key;
+    ngx_str_t                       client_certificate;
+
+    ngx_str_t                       ciphers;
+
+    ngx_shm_zone_t                 *shm_zone;
 } ngx_http_ssl_srv_conf_t;