IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    nginx模块开发(55)—SSL模块研究之session_cache(下)

    cjhust发表于 2016-05-24 17:52:07
    love 0

    4、源码分析(nginx部分)

    ssl_session_cache使用相关

    main()->ngx_ssl_init()

    函数功能:初始化openssl相关配置,并对每种功能建立索引。

    nginx模块开发(54)—SSL模块研究之session_cache - cjhust - 我一直在努力

    ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)

    函数功能:创建server级别的ctx、检验证书、密码(cipher)和对ssl->ctx挂共享内存的钩子函数等。

    nginx模块开发(54)—SSL模块研究之session_cache - cjhust - 我一直在努力
     
    nginx模块开发(54)—SSL模块研究之session_cache - cjhust - 我一直在努力
     
    nginx模块开发(54)—SSL模块研究之session_cache - cjhust - 我一直在努力
     
    nginx模块开发(54)—SSL模块研究之session_cache - cjhust - 我一直在努力
     核心函数:

    ssl->ctx = SSL_CTX_new(SSLv23_method());

    SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_server_conf_index, conf);

    SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1);

    ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx, ssize_t builtin_session_cache, ngx_shm_zone_t *shm_zone, time_t timeout)

    函数功能:在merge server的时候调用该函数。

    ngx_int_t
    ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx,
        ssize_t builtin_session_cache, ngx_shm_zone_t *shm_zone, time_t timeout)
    {
        long  cache_mode;
    
        //对ctx设置超时
        SSL_CTX_set_timeout(ssl->ctx, (long) timeout);
    
        if (ngx_ssl_session_id_context(ssl, sess_ctx) != NGX_OK) {
            return NGX_ERROR;
        }
    
         。。。
    
        cache_mode = SSL_SESS_CACHE_SERVER;
    
        SSL_CTX_set_session_cache_mode(ssl->ctx, cache_mode);
    
        if (shm_zone) {
            SSL_CTX_sess_set_new_cb(ssl->ctx, ngx_ssl_new_session);
            SSL_CTX_sess_set_get_cb(ssl->ctx, ngx_ssl_get_cached_session);
            SSL_CTX_sess_set_remove_cb(ssl->ctx, ngx_ssl_remove_session);
    
            if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_session_cache_index, shm_zone)
                == 0)
            {
                ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                              "SSL_CTX_set_ex_data() failed");
                return NGX_ERROR;
            }
        }
    
        return NGX_OK;
    }
    

    核心函数:

    SSL_CTX_set_timeout(ssl->ctx, (long) timeout);

    SSL_CTX_sess_set_new_cb(ssl->ctx, ngx_ssl_new_session);

    SSL_CTX_sess_set_get_cb(ssl->ctx, ngx_ssl_get_cached_session);

    SSL_CTX_sess_set_remove_cb(ssl->ctx, ngx_ssl_remove_session);

    ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess)

    函数功能:在SSL握手结束后被ngx_ssl_handshake->ssl3_accept->ssl_update_cache中调用。

    static int
    ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess)
    {
        。。。
        //计算session的长度
        len = i2d_SSL_SESSION(sess, NULL);
    
        /* do not cache too big session */
    
        if (len > (int) NGX_SSL_MAX_SESSION_SIZE) {
            return 0;
        }
    
        //将session存入buf中
        p = buf;
        i2d_SSL_SESSION(sess, &p);
    
        c = ngx_ssl_get_connection(ssl_conn);
    
        //得到共享内存地址
        ssl_ctx = c->ssl->session_ctx;
        shm_zone = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_cache_index);
    
        cache = shm_zone->data;
        shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
    
        ngx_shmtx_lock(&shpool->mutex);
    
        /* drop one or two expired sessions */
        ngx_ssl_expire_sessions(cache, shpool, 1);
    
        cached_sess = ngx_slab_alloc_locked(shpool, len);
    
        if (cached_sess == NULL) {
    
            /* drop the oldest non-expired session and try once more */
    
            ngx_ssl_expire_sessions(cache, shpool, 0);
    
            cached_sess = ngx_slab_alloc_locked(shpool, len);
    
            if (cached_sess == NULL) {
                sess_id = NULL;
                goto failed;
            }
        }
    
        //key
        sess_id = ngx_slab_alloc_locked(shpool, sizeof(ngx_ssl_sess_id_t));
    
        if (sess_id == NULL) {
        }
        session_id = (u_char *) SSL_SESSION_get_id(sess, &session_id_length);
        id = sess_id->sess_id;
    
        ngx_memcpy(cached_sess, buf, len);
        ngx_memcpy(id, session_id, session_id_length);
    
        hash = ngx_crc32_short(session_id, session_id_length);
    
        //红黑树存储的节点
        sess_id->node.key = hash;
        sess_id->node.data = (u_char) session_id_length;
        sess_id->id = id;
        sess_id->len = len;
        sess_id->session = cached_sess;
    
        sess_id->expire = ngx_time() + SSL_CTX_get_timeout(ssl_ctx);
    
        ngx_queue_insert_head(&cache->expire_queue, &sess_id->queue);
    
        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 (sess_id) {
            ngx_slab_free_locked(shpool, sess_id);
        }
    
        ngx_shmtx_unlock(&shpool->mutex);
    
        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
                      "could not allocate new session%s", shpool->log_ctx);
    
        return 0;
    }

    ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn…)

    函数功能:在接收到client hello后(即ssl3_get_client_hello)被ngx_ssl_handshake->ssl3_accept->ssl_get_prev_session调用。

    static ngx_ssl_session_t *
    ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn,
    #if OPENSSL_VERSION_NUMngx_ssl_get_cached_sessionBER >= 0x10100003L
        const
    #endif
        u_char *id, int len, int *copy)
    {
    。。。
    
        hash = ngx_crc32_short((u_char *) (uintptr_t) id, (size_t) len);
        *copy = 0;
    
        c = ngx_ssl_get_connection(ssl_conn);
    
        //得到共享内存地址
        shm_zone = SSL_CTX_get_ex_data(c->ssl->session_ctx,
                                       ngx_ssl_session_cache_index);
    
        cache = shm_zone->data;
    
        sess = NULL;
    
        shpool = (ngx_slab_pool_t *) 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;
            }
    
            /* hash == node->key */
    
            sess_id = (ngx_ssl_sess_id_t *) node;
    
            rc = ngx_memn2cmp((u_char *) (uintptr_t) id, sess_id->id,
                              (size_t) len, (size_t) node->data);
    
            if (rc == 0) {
    
                if (sess_id->expire > ngx_time()) {
                    ngx_memcpy(buf, sess_id->session, sess_id->len);
    
                    ngx_shmtx_unlock(&shpool->mutex);
    
                    p = buf;
                    sess = d2i_SSL_SESSION(NULL, &p, sess_id->len);
    
                    return sess;
                }
    
                ngx_queue_remove(&sess_id->queue);
    
                ngx_rbtree_delete(&cache->session_rbtree, node);
    
                ngx_slab_free_locked(shpool, sess_id->session);
    #if (NGX_PTR_SIZE == 4)
                ngx_slab_free_locked(shpool, sess_id->id);
    #endif
                ngx_slab_free_locked(shpool, sess_id);
    
                sess = NULL;
    
                goto done;
            }
    
            node = (rc < 0) ? node->left : node->right;
        }
    
    done:
    
        ngx_shmtx_unlock(&shpool->mutex);
    
        return sess;
    }

    ngx_ssl_remove_session(SSL_CTX *ssl, ngx_ssl_session_t *sess)

    函数功能:红黑树删除掉session id对应的cache。

    5、测试验证

    (1)浏览器访问#https://slice.b0.upaiyun.com/status

    nginx模块开发(54)—SSL模块研究之session_cache - cjhust - 我一直在努力
     备注:通过浏览器访问发现,session reused很多。

     

    (2)#curl https://slice.b0.upaiyun.com/status

    nginx模块开发(54)—SSL模块研究之session_cache - cjhust - 我一直在努力
     备注:通过curl访问发现,全是session new。

     

    (3)openssl访问

    #printf 'GET / HTTP/1.1\r\nhost: slice.b0.upaiyun.com\r\n\r\n' |openssl s_client -connect 127.0.0.1:443           (new_session)

    #printf 'GET / HTTP/1.1\r\nhost: slice.b0.upaiyun.com\r\n\r\n' |openssl s_client -connect 127.0.0.1:443 -reconnect  (reused_session,会访问server 5次,但server上只看到一次)

    #printf 'GET / HTTP/1.1\r\nhost: cjhust.b0.upaiyun.com\r\n\r\n' |openssl s_client -connect 127.0.0.1:443 -sess_in /tmp/session.txt -sess_out /tmp/session.txt -sslv3 (reused_session)

    nginx模块开发(54)—SSL模块研究之session_cache - cjhust - 我一直在努力

    备注:TLS版本会有session ticket扩展,会导致进入不到ngx_ssl_get_cached_sessio,因此参数中加上-sslv3(虽说不安全要淘汰了)。

    6、参考资料

    tengine开发从入门到精通https:

    http://tengine.taobao.org/book/chapter_12.html#https

     

    阮一峰对SSL/TLS的研究:

    http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html

    http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html

     

    nginx.org SSL模块:

    http://nginx.org/en/docs/http/ngx_http_ssl_module.html



沪ICP备19023445号-2号
友情链接