nginx openssl 的集成代码前奏

6090阅读 0评论2013-03-05 jueduiyingxiong
分类:LINUX


说明:这里只是提供一种思路,提取代码数据之前的一个过程


nginx.conf配置

upstream tomcat_server {
          server 127.0.0.1:8080;
    }

    server {
        listen 443;
        ssl on;
        ssl_certificate  /usr/local/nginx/conf/api.bz.crt;
        ssl_certificate_key  /usr/local/nginx/conf/api.bz_nopass.key;
        server_name  jueduiyingxiong.oicp.net;
        index  index.html index.htm index.jsp default.jsp index.do default.do;
        root /data0/htdocs/www;

        if (-d $request_filename)
        {
           rewrite ^/(.*)([^/])$ permanent;
        }

        #charset koi8-r;

        #access_log  logs/host.access.log  main;


        location / {
            proxy_pass  
        }


先总体了解吧:
一、ngx_event_openssl.c //事件模块

1、模块:
ngx_module_t  ngx_openssl_module = {
    NGX_MODULE_V1,
    &ngx_openssl_module_ctx,               /* module context */
    ngx_openssl_commands,                  /* module directives */
    NGX_CORE_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    ngx_openssl_exit,                      /* exit master */
    NGX_MODULE_V1_PADDING
};

可以看出:
type 为 NGX_CORE_MODULE 为core模块
主进程退出的时候走ngx_openssl_exit:
static void ngx_openssl_exit(ngx_cycle_t *cycle)
{
    EVP_cleanup();
    ENGINE_cleanup();
}

对应的命令有:ngx_openssl_commands
static ngx_command_t  ngx_openssl_commands[] = {
    { ngx_string("ssl_engine"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_openssl_engine,
      0,
      0,
      NULL },
      ngx_null_command
};
当调用set的时候,如果定义了“ssl_engine” 那么会调用:

static char *ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_openssl_conf_t *oscf = conf;

    ENGINE     *engine;
    ngx_str_t  *value;

    if (oscf->engine) {
        return "is duplicate";
    }

    oscf->engine = 1;

    value = cf->args->elts;

    engine = ENGINE_by_id((const char *) value[1].data);

    if (engine == NULL) {
        ngx_ssl_error(NGX_LOG_WARN, cf->log, 0,
                      "ENGINE_by_id(\"%V\") failed", &value[1]);
        return NGX_CONF_ERROR;
    }

    if (ENGINE_set_default(engine, ENGINE_METHOD_ALL) == 0) {
        ngx_ssl_error(NGX_LOG_WARN, cf->log, 0,
                      "ENGINE_set_default(\"%V\", ENGINE_METHOD_ALL) failed",
                      &value[1]);

        ENGINE_free(engine);

        return NGX_CONF_ERROR;
    }

    ENGINE_free(engine);

    return NGX_CONF_OK;
}

2、上下文信息:
static ngx_core_module_t  ngx_openssl_module_ctx = {
    ngx_string("openssl"),
    ngx_openssl_create_conf,
    NULL
};

说明:显然是核心模块,创建conf配置时走:
static void * ngx_openssl_create_conf(ngx_cycle_t *cycle)
{
    ngx_openssl_conf_t  *oscf;

    oscf = ngx_pcalloc(cycle->pool, sizeof(ngx_openssl_conf_t));
    if (oscf == NULL) {
        return NULL;
    }

    /*
     * set by ngx_pcalloc():
     *
     *     oscf->engine = 0;
     */

    return oscf;
}

3、只是分配这个结构的内存:
typedef struct {
    ngx_uint_t  engine;   /* unsigned  engine:1; */
} ngx_openssl_conf_t;

总结:
openssl 事件 支持配置:ssl_engine;其他函数使用于其他类内部


二、ngx_http_ssl_module.c //ssl模块

1、模块:
ngx_module_t  ngx_http_ssl_module = {
    NGX_MODULE_V1,
    &ngx_http_ssl_module_ctx,              /* module context */
    ngx_http_ssl_commands,                 /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};
可以看出:NGX_HTTP_MODULE 作为http模块集成;

二、上下文信息:
static ngx_http_module_t  ngx_http_ssl_module_ctx = {
    ngx_http_ssl_add_variables,            /* preconfiguration */
    NULL,                                  /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    ngx_http_ssl_create_srv_conf,          /* create server configuration */
    ngx_http_ssl_merge_srv_conf,           /* merge server configuration */

    NULL,                                  /* create location configuration */
    NULL                                   /* merge location configuration */
};

11看出,在读取配置文件前: 保存对应的ngx_http_ssl_vars[] 处理方式(http里面的处理方式)
static ngx_int_t ngx_http_ssl_add_variables(ngx_conf_t *cf)
{
    ngx_http_variable_t  *var, *v;

    for (v = ngx_http_ssl_vars; v->name.len; v++) {
        var = ngx_http_add_variable(cf, &v->name, v->flags);
        if (var == NULL) {
            return NGX_ERROR;
        }
        var->get_handler = v->get_handler;
        var->data = v->data;
    }

    return NGX_OK;
}

22 create server 创建server配置的时候:设置配置的其他基本信息
static void * ngx_http_ssl_create_srv_conf(ngx_conf_t *cf)
{
    ngx_http_ssl_srv_conf_t  *sscf;

    sscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssl_srv_conf_t));
    if (sscf == NULL) {
        return NULL;
    }
    sscf->enable = NGX_CONF_UNSET;
    sscf->prefer_server_ciphers = NGX_CONF_UNSET;
    sscf->verify = NGX_CONF_UNSET_UINT;
    sscf->verify_depth = NGX_CONF_UNSET_UINT;
    sscf->builtin_session_cache = NGX_CONF_UNSET;
    sscf->session_timeout = NGX_CONF_UNSET;

    return sscf;
}

33merge server 合并配置:
ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) //把多个配置合并为相应父的配置

2、指令 只是部分需要配置的保留,可以看出,只是字符的拷贝和设置而已
static ngx_command_t  ngx_http_ssl_commands[] = {
    { ngx_string("ssl"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
      ngx_http_ssl_enable,
      NGX_HTTP_SRV_CONF_OFFSET,
      offsetof(ngx_http_ssl_srv_conf_t, enable),
      NULL },

    { ngx_string("ssl_certificate"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_str_slot,
      NGX_HTTP_SRV_CONF_OFFSET,
      offsetof(ngx_http_ssl_srv_conf_t, certificate),
      NULL },

    { ngx_string("ssl_certificate_key"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_str_slot,
      NGX_HTTP_SRV_CONF_OFFSET,
      offsetof(ngx_http_ssl_srv_conf_t, certificate_key),
      NULL },
};


三、、、、下面来看实际调用流程(初始化的过程)

1、初始化调用(服务器启动):
ngx_int_t ngx_ssl_init(ngx_log_t *log)

2、在下面函数内部
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
调用配置信息所对应的函数
module->create_conf(cycle);
ngx_openssl_create_conf(ngx_cycle_t *cycle)
oscf = ngx_pcalloc(cycle->pool, sizeof(ngx_openssl_conf_t));

module->create_srv_conf(cf);
ngx_http_ssl_create_srv_conf(ngx_conf_t *cf)
sscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssl_srv_conf_t));

if (module->preconfiguration(cf) != NGX_OK) { //调用先前配置
ngx_http_ssl_add_variables(ngx_conf_t *cf) //保存配置信息
  for (v = ngx_http_ssl_vars; v->name.len; v++) {

3、指令调度
rv = cmd->set(cf, cmd, conf);// 调度指令的钩子 set

ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)

//ups上会调用多次
mconf = module->create_srv_conf(cf);
ngx_http_ssl_create_srv_conf(ngx_conf_t *cf)

rv = cmd->set(cf, cmd, conf);// 调度指令的钩子 set
ngx_http_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ssl

rv = module->merge_srv_conf(cf, saved.srv_conf[ctx_index],cscfp[s]->ctx->srv_conf[ctx_index]);
ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) 里面创建了ssl协议

ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
#if (NGX_HTTP_SSL) 为1
    ngx_conf_merge_value(conf->upstream.ssl_session_reuse,
                              prev->upstream.ssl_session_reuse, 1);
#endif
   conf->upstream.ssl = prev->upstream.ssl;

4、监听的时候对服务器监听
ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t
ngx_http_init_listening(ngx_conf_t *cf, ngx_http_conf_port_t *port)
if (ngx_http_add_addrs(cf, hport, addr) != NGX_OK) {
#if (NGX_HTTP_SSL)
        addrs[i].conf.ssl = addr[i].opt.ssl;
#endif


四、接收 处理(尚未有证书)
events = epoll_wait(ep, event_list, (int) nevents, timer); events 1
rev->handler = 0x4958a8 c->fd = 9
s = accept4 lc->fd = 9 s = 10

events = epoll_wait events = 2
rev->handler = 0x4958a8 c->fd = 9
s = accept4(lc->fd s = 11

rev->handler = 0x439127 c->fd = 10

开始调用ssl
ngx_http_init_request(ngx_event_t *rev)
#if (NGX_HTTP_SSL)
    {
    ngx_http_ssl_srv_conf_t  *sscf;
建立ssl的上下文套接口
  if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER)
上面函数内部实现:
sc->connection = SSL_new(ssl->ctx);
if (SSL_set_fd(sc->connection, c->fd) == 0) {
  SSL_set_accept_state(sc->connection);
 if (SSL_set_ex_data(sc->connection, ngx_ssl_connection_index, c) == 0) {
rev->handler = ngx_http_ssl_handshake;
r->main_filter_need_in_memory = 1;

rev->handler = 0x439913 进入
第一次读取数据做验证https 的加密版本
 n = recv(c->fd, (char *) buf, 1, MSG_PEEK); c->fd = 10 n = 1
if (n == 1) {
        if (buf[0] & 0x80 /* SSLv2 */ || buf[0] == 0x16 /* SSLv3/TLSv1 */) {

做握手操作
 rc = ngx_ssl_handshake(c); rc = -1
 n = SSL_do_handshake(c->ssl->connection);

握手时回调,通过调试可以看到有下列函数在调用,就是握手内部的调用了
ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, int ret)
  c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); n = 0 SSL_get_ex_data(ssl_conn, ngx_ssl_connection_index)
{
"ssl23_accept() "
ssl23_accept()
ssl3_accept()
ssl3_accept()
ssl3_accept()
ssl3_accept()
"ssl3_accept() "
ssl3_accept()
"ssl3_accept() "
ssl23_get_client_hello()
ssl23_accept()
}

进行握手失败的处理
ngx_http_ssl_handshake_handler(c);
 }
    ngx_http_close_request(r, NGX_HTTP_BAD_REQUEST);

 ngx_http_close_connection(ngx_connection_t *c)

if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
 mode = SSL_get_shutdown(c->ssl->connection);
SSL_set_quiet_shutdown(c->ssl->connection, 1);
 SSL_set_shutdown(c->ssl->connection, mode);

 ngx_close_connection(c);
ngx_del_conn(c, NGX_CLOSE_EVENT);
返回了

//从新请求,走原来的路,但是不是调用两次了,后面调试就是一次了呢(可能是第一次请求会走两次)
有认证////////////////
rev->handler = 0x4958a8 c->fd = 9
accept4 s = 10
rev->handler = 0x439127 c->fd = 10
rev->handler = ngx_http_ssl_handshake;
rev->handler = 0x439913
 n = recv(c->fd, (char *) buf, 1, MSG_PEEK); c->fd = 10 buf = "\026" //说明是https请求
rc = ngx_ssl_handshake(c); rc = 0
 n = SSL_do_handshake(c->ssl->connection); n = 1
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);

//握手成功后的设置
c->ssl->handshaked = 1;
c->recv = ngx_ssl_recv;
c->send = ngx_ssl_write;
c->recv_chain = ngx_ssl_recv_chain;
c->send_chain = ngx_ssl_send_chain;

握手处理 和常规处理一样,读取请求头和处理等操作
 ngx_http_ssl_handshake_handler(c);
 c->ssl->no_wait_shutdown = 1;
 c->read->handler = ngx_http_process_request_line;
ngx_http_process_request_line(c->read);
n = ngx_http_read_request_header(r);
ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size)

这里就是为什么要在初始化的时候设置一个读取标识了(看博文“nginx openssl 的集成代码流程”)
第一次
 n = SSL_read(c->ssl->connection, buf, size); n = 1 buf = 0x77c430 "G"
c->ssl->last = ngx_ssl_handle_recv(c, n);

第二次n = SSL_read(c->ssl->connection, buf, size); n = 536buf = 0x77c431 "ET / HTTP/1.1\r\nAccept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/
 x-sh ...

第三次n = SSL_read(c->ssl->connection, buf, size); n = -1  uf = 0x77c649 "" //表示读取完毕,这要根据读取字节大小来确定循环次数

这里点一下:如果有错,第一次握手的时候会发生错误情况是由于有数据正在写,所有会放到下个循环处理;这是根据内核处理方式来确定循环的。
sslerr = SSL_get_error(c->ssl->connection, n);

合成后的结果
r->header_in->last = 0x77c430 "GET / HTTP/1.1\r\nAccept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-s ...

处理完成后获取发送的信息发送给客户端
ngx_ssl_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
 n = SSL_write(c->ssl->connection, data, size);data = 0x784440 "HTTP/1.1 200 OK\r\nServer: nginx/1.0.12\r\nDate: Sun, 03 Mar 2013 07:42:02 GMT\r
 \nContent- ... size = 382 //发送完成后,客户端返回

发送完成后等待超时,或其他错误,就走下面的步骤
 ngx_http_finalize_request(r, rc);
 ngx_http_finalize_connection(r);
 ngx_ssl_free_buffer(c);c->fd = 11


其他细节
//一般请求,如何判断
wev->handler = 0x43c459
 if (sscf->enable || addr_conf->ssl) { //正常  sscf->enable = 0

初始化:
ngx_http_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
设置:enable 为 1

如果是http端口为443 请求
如果非安全协议,第一次recv就得到 buf = "G"
ngx_http_read_request_header(ngx_http_request_t *r)、

发送的内容就是:
400 Bad Request
The plain HTTP request was sent to HTTPS port
nginx/1.0.12

//成功的时候,第一次do——handel 握手的时候返回-1表示可读,进入下一次循环,下次循环的时候直接进入读取状态
 c->ssl->handler = ngx_http_ssl_handshake_handler;
rev->handler = 0x498572

这次重新握手 ngx_ssl_handshake(ngx_connection_t *c)
ngx_http_ssl_handshake_handler(ngx_connection_t *c) 处理握手
这次循环读取
  n = SSL_read(c->ssl->connection, buf, size);

请求完毕后关闭流程:

//非https 请求端口是这样关闭的:
ngx_int_t ngx_ssl_shutdown(ngx_connection_t *c)
{
    int        n, sslerr, mode;
    ngx_err_t  err;

    if (c->timedout) { //超时走
        mode = SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN;
        SSL_set_quiet_shutdown(c->ssl->connection, 1);

    } else {
        mode = SSL_get_shutdown(c->ssl->connection);
    }

    SSL_set_shutdown(c->ssl->connection, mode);

    ngx_ssl_clear_error(c->log);

    n = SSL_shutdown(c->ssl->connection);

    sslerr = 0;

    if (n == 1 || sslerr == 0 || sslerr == SSL_ERROR_ZERO_RETURN) {
        SSL_free(c->ssl->connection);
        c->ssl = NULL;

        return NGX_OK;
    }
    return NGX_ERROR;
}

//没有CA的时候
ngx_int_t ngx_ssl_shutdown(ngx_connection_t *c)
{
    int        n, sslerr, mode;
    ngx_err_t  err;

    if (c->timedout) { //超时
        mode = SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN;
        SSL_set_quiet_shutdown(c->ssl->connection, 1);

    } else {
        mode = SSL_get_shutdown(c->ssl->connection);

        if (c->ssl->no_wait_shutdown) {
            mode |= SSL_RECEIVED_SHUTDOWN;
        }

        if (c->ssl->no_send_shutdown) {
            mode |= SSL_SENT_SHUTDOWN;
        }

        if (c->ssl->no_wait_shutdown && c->ssl->no_send_shutdown) {
            SSL_set_quiet_shutdown(c->ssl->connection, 1);
        }
    }

    SSL_set_shutdown(c->ssl->connection, mode);

    ngx_ssl_clear_error(c->log);

    n = SSL_shutdown(c->ssl->connection);

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_shutdown: %d", n);

    sslerr = 0;

    if (n == 1 || sslerr == 0 || sslerr == SSL_ERROR_ZERO_RETURN) {
        SSL_free(c->ssl->connection);
        c->ssl = NULL;

        return NGX_OK;
    }

    return NGX_ERROR;
}

//有认证的时候
ngx_ssl_shutdown(ngx_connection_t *c)
{
    int        n, sslerr, mode;
    ngx_err_t  err;

    if (c->timedout) { //超时,可能是读取或写入超时导致
        mode = SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN;
        SSL_set_quiet_shutdown(c->ssl->connection, 1);

    } else {
        mode = SSL_get_shutdown(c->ssl->connection);

        if (c->ssl->no_wait_shutdown) {
            mode |= SSL_RECEIVED_SHUTDOWN;
        }

        if (c->ssl->no_send_shutdown) {
            mode |= SSL_SENT_SHUTDOWN;
        }

        if (c->ssl->no_wait_shutdown && c->ssl->no_send_shutdown) {
            SSL_set_quiet_shutdown(c->ssl->connection, 1);
        }
    }

    SSL_set_shutdown(c->ssl->connection, mode);

    ngx_ssl_clear_error(c->log);

    n = SSL_shutdown(c->ssl->connection);

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_shutdown: %d", n);

    sslerr = 0;

    /* SSL_shutdown() never returns -1, on error it returns 0 */
    if (n == 1 || sslerr == 0 || sslerr == SSL_ERROR_ZERO_RETURN) {
        SSL_free(c->ssl->connection);
        c->ssl = NULL;

        return NGX_OK;
    }

    return NGX_ERROR;
}

总结:
上面只是给了一个分析,openssl的一个流程,详细流程看博文“nginx openssl 的集成代码流程”

上一篇:SSL单向/双向认证详解(其实是握手过程的详解)
下一篇:vim中多标签和多窗口的使用