# HG changeset patch # User Igor Sysoev # Date 1167782105 0 # Node ID e1ede83911ef63a7d5a3a671b1ca80957e98d46c # Parent 6e7a20529f536c2846606285a1e9d807eac353ef ssl_session_cache diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- 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 * diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h +++ b/src/http/modules/ngx_http_ssl_module.h @@ -13,25 +13,56 @@ #include +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;