add content for chapter 12(http ssl)

This commit is contained in:
Xiaochen Wang 2013-10-16 18:10:05 +08:00
parent 86cddec321
commit 456f52402a
3 changed files with 361 additions and 2 deletions

View File

@ -40,7 +40,7 @@ nginx由于出色的性能在世界范围内受到了越来越多人的关注
2012/11/09|增加第12章(请求体的读取和丢弃解析)
2012/11/24|更新第2章(ngx_buf_t的部分字段以及其他一些书写错误和表达)
2012/12/18|更新第11章(解析http块)
2012/12/10|增加第3章的内容
2012/12/10|增加第3章的内容
2012/12/28|补充和完善了第3章的内容
2013/01/25|增加了第2章(nginx的配置系统)
2013/02/18|增加了第2章(nginx的模块化体系结构, nginx的请求处理)
@ -49,6 +49,7 @@ nginx由于出色的性能在世界范围内受到了越来越多人的关注
2013/04/16|完成第9章第1节(源码目录结构、configure原理)
2013/09/30|完成第12章部分内容(多阶段执行链各个阶段解析)
2013/10/11|完成第12章部分内容(filter解析)
2013/10/11|完成第12章部分内容(ssl解析)
版权申明
++++++++++++

View File

@ -2962,3 +2962,360 @@ ngx_http_run_posted_requests函数的调用点后面会做说明。
...
}
nginx对ssl处理
-----------------------
nginx支持ssl简介
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
nginx-1.2.0编译时默认是不支持ssl协议的需要通过编译指令来开启对其支持
.. code:: c
./configure --with-http_ssl_module
在nginx源码中ssl相关代码用宏定义变量NGX_HTTP_SSL来控制是否开启。这给我们查找和阅读ssl相关代码带来了方便如下:
.. code:: c
#if NGX_HTTP_SSL
/* http ssl code */
#endif
ssl协议工作在tcp协议与http协议之间。nginx在支持ssl协议时需要注意三点其他时候只要正常处理http协议即可:
1.tcp连接建立时在tcp连接上建立ssl连接
2.tcp数据接收后将收到的数据解密并将解密后的数据交由正常http协议处理流程
3.tcp数据发送前对(http)数据进行加密,然后再发送
以下章节将分别介绍这三点。
ssl连接建立(ssl握手)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
对ssl连接建立的准备
+++++++++++++++++++++++
更具ssl协议规定在正式发起数据收发前需要建立ssl连接连接建立过程既ssl握手。nginx在创建和初始化http请求阶段的同时为tcp连接建立做准备主要流程在ngx_http_init_request函数中实现:
.c
.. code:: c
static void
ngx_http_init_request(ngx_event_t *rev)
{
...
#if (NGX_HTTP_SSL)
{
ngx_http_ssl_srv_conf_t *sscf;
sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);
if (sscf->enable || addr_conf->ssl) {
/* c->ssl不为空时表示请求复用长连接(已经建立过ssl连接) */
if (c->ssl == NULL) {
c->log->action = "SSL handshaking";
/*
* nginx.conf中开启ssl协议(listen 443 ssl;)
* 却没用设置服务器证书(ssl_certificate <certificate_path>;)
*/
if (addr_conf->ssl && sscf->ssl.ctx == NULL) {
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"no \"ssl_certificate\" is defined "
"in server listening on SSL port");
ngx_http_close_connection(c);
return;
}
/*
* 创建ngx_ssl_connection_t并初始化
* openssl库中关于ssl连接的初始化
*/
if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER)
!= NGX_OK)
{
ngx_http_close_connection(c);
return;
}
rev->handler = ngx_http_ssl_handshake;
}
/* ssl加密的数据必须读到内存中 */
r->main_filter_need_in_memory = 1;
}
}
#endif
...
}
ngx_http_init_request大部分流程已经在前面章节分析过了这个函数主要负责初始化http请求此时并没有实际解析http请求。若发来的请求是经由ssl协议加密的直接解析http请求就会出错。ngx_http_init_request中ssl协议相关处理流程:
1首先判断c->ssl是否为空。若不为空说明这里是http长连接的情况ssl连接已经在第一个请求进入时建立了。这里只要复用这个ssl连接即可跳过ssl握手阶段。
2.(1)若c->ssl为空需要进行ssl握手来建立连接。此时调用ngx_ssl_create_connection为ssl连接建立做准备。
ngx_ssl_create_connection 简化代码如下:
.. code:: c
ngx_int_t
ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags)
{
ngx_ssl_connection_t *sc;
/* ngx_ssl_connection_t是nginx对ssl连接的描述结构记录了ssl连接的信息和状态 */
sc = ngx_pcalloc(c->pool, sizeof(ngx_ssl_connection_t));
sc->buffer = ((flags & NGX_SSL_BUFFER) != 0);
/* 创建openssl库中对ssl连接的描述结构 */
sc->connection = SSL_new(ssl->ctx);
/* 关联(openssl库)ssl连接到tcp连接对应的socket */
SSL_set_fd(sc->connection, c->fd);
if (flags & NGX_SSL_CLIENT) {
/* upstream中发起对后端的ssl连接指明nginx ssl连接是客户端 */
SSL_set_connect_state(sc->connection);
} else {
/* 指明nginx ssl连接是服务端 */
SSL_set_accept_state(sc->connection);
}
/* 关联(openssl库)ssl连接到用户数据(当前连接c) */
SSL_set_ex_data(sc->connection, ngx_ssl_connection_index, c);
c->ssl = sc;
return NGX_OK;
}
2.(2)设置连接读事件处理函数为ngx_http_ssl_handshake这将改变后续处理http请求的正常流程为先进行ssl握手再正常处理http请求。
3标明当前待发送的数据须在内存中以此可以让ssl对数据进行加密。由于开启了ssl协议对发送出去的数据要进行加密这就要求待发送的数据必须在内存中。 标识r->main_filter_need_in_memory为1可以让后续数据发送前将数据读取到内存中 (防止在文件中的数据通过sendfile直接发送出去而没有加密
实际ssl握手阶段
+++++++++++++++++++++++
由于在ngx_http_init_request中将连接读事件处理函数设置成ngx_http_ssl_handshake当连接中有可读数据时将会进入ngx_http_ssl_handshake来处理(若未开启ssl将进入ngx_http_process_request_line)直接解析http请求
在ngx_http_ssl_handshake中来进行ssl握手:
1首先判断连接是否超时如果超时则关闭连接
.. code:: c
static void
ngx_http_process_request(ngx_http_request_t *r)
{
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
c->timedout = 1;
ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
return;
}
2首字节预读从tcp连接中查看一个字节(通过MSG_PEEK查看tcp连接中数据但不会实际读取该数据)若tcp连接中没有准备好的数据则重新添加读事件退出等待新数据到来。
.. code:: c
n = recv(c->fd, (char *) buf, 1, MSG_PEEK);
if (n == -1 && ngx_socket_errno == NGX_EAGAIN) {
if (!rev->timer_set) {
ngx_add_timer(rev, c->listening->post_accept_timeout);
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
}
return;
}
3首字节探测若成功查看1个字节数据通过该首字节来探测接受到的数据是ssl握手包还是http数据。根据ssl协议规定ssl握手包的首字节中包含有ssl协议的版本信息。nginx根据此来判断是进行ssl握手还是返回正常处理http请求(实际返回应答400 BAD REQUEST)。
.. code:: c
if (n == 1) {
if (buf[0] & 0x80 /* SSLv2 */ || buf[0] == 0x16 /* SSLv3/TLSv1 */) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0,
"https ssl handshake: 0x%02Xd", buf[0]);
/*
* 调用ngx_ssl_handshake函数进行ssl握手连接双方会在ssl握手时交换相
* 关数据(ssl版本ssl加密算法server端的公钥等) 并正式建立起ssl连接。
* ngx_ssl_handshake函数内部对openssl库进行了封装。
* 调用SSL_do_handshake()来进行握手并根据其返回值判断ssl握手是否完成
* 或者出错。
*/
rc = ngx_ssl_handshake(c);
/*
* ssl握手可能需要多次数据交互才能完成。
* 如果ssl握手没有完成ngx_ssl_handshake会根据具体情况(如需要读取更
* 多的握手数据包,或者需要发送握手数据包)来重新添加读写事件
*/
if (rc == NGX_AGAIN) {
if (!rev->timer_set) {
ngx_add_timer(rev, c->listening->post_accept_timeout);
}
c->ssl->handler = ngx_http_ssl_handshake_handler;
return;
}
/*
* 若ssl握手完成或者出错ngx_ssl_handshake会返回NGX_OK或者NGX_ERROR, 然后ngx_http_ssl_handshake调用
* ngx_http_ssl_handshake_handler以继续处理
*/
ngx_http_ssl_handshake_handler(c);
return;
} else {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
"plain http");
r->plain_http = 1;
}
}
需要特别注意如果ssl握手完成ngx_ssl_handshake会替换连接的读写接口。这样后续需要读写数据时替换的接口会对数据进行加密解密。详细代码见下:
.. code:: c
ngx_int_t
ngx_ssl_handshake(ngx_connection_t *c)
{
n = SSL_do_handshake(c->ssl->connection);
/* 返回1表示ssl握手成功 */
if (n == 1) {
...
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;
return NGX_OK;
}
...
}
4探测为http协议正常的http协议包处理直接调用ngx_http_process_request_line处理http请求并将读事件处理函数设置成ngx_http_process_request_line。(实际处理结果是向客户端返回400 BAD REQUET在ngx_http_process_request中又对r->plain_http标志的单独处理。)
.. code:: c
c->log->action = "reading client request line";
rev->handler = ngx_http_process_request_line;
ngx_http_process_request_line(rev);
} /* end of ngx_http_process_request() */
5当ssl握手成功或者出错时调用ngx_http_ssl_handshake_handler函数。
5.(1)若ssl握手完成 (c->ssl->handshaked由ngx_ssl_handshake()确定握手完成后设为1)设置读事件处理函数为ngx_http_process_request_line并调用此函数正常处理http请求。
5.(2)若ssl握手没完成则说明ssl握手出错则返回400 BAD REQUST给客户端。
至此ssl连接已经建立此后在ngx_http_process_request中会读取数据并解密然后正常处理http请求。
.. code:: c
static void
ngx_http_ssl_handshake_handler(ngx_connection_t *c)
{
ngx_http_request_t *r;
if (c->ssl->handshaked) {
/*
* The majority of browsers do not send the "close notify" alert.
* Among them are MSIE, old Mozilla, Netscape 4, Konqueror,
* and Links. And what is more, MSIE ignores the server's alert.
*
* Opera and recent Mozilla send the alert.
*/
c->ssl->no_wait_shutdown = 1;
c->log->action = "reading client request line";
c->read->handler = ngx_http_process_request_line;
/* STUB: epoll edge */ c->write->handler = ngx_http_empty_handler;
ngx_http_process_request_line(c->read);
return;
}
r = c->data;
ngx_http_close_request(r, NGX_HTTP_BAD_REQUEST);
return;
}
ssl协议接受数据
+++++++++++++++++++++++
ngx_http_process_request中处理http请求需要读取和解析http协议。而实际数据读取是通过c->recv()函数来读取的此函数已经在ngx_ssl_handshake中被替换成ngx_ssl_recv了。
ngx_ssl_recv函数中调用openssl库函数SSL_read()来读取并解密数据,简化后如下:
.. code:: c
ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size)
{
...
n = SSL_read(c->ssl->connection, buf, size);
...
return n;
}
ssl协议发送数据
+++++++++++++++++++++++
当nginx发送数据时如使用ngx_output_chain函数发送缓存的http数据缓存链时通过调用c->send_chain()来发送数据。这个函数已经在ngx_ssl_handshake中被设置成ngx_ssl_send_chain了。ngx_ssl_send_chain会进一步调用ngx_ssl_write。而ngx_ssl_write调用openssl库SSL_write函数来加密并发送数据。
.. code:: c
/* ngx_output_chain
* -> ..
* -> ngx_chain_writer
* -> c->send_chain (ngx_ssl_send_chain)
* -> ngx_ssl_write
*/
ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size)
{
...
n = SSL_write(c->ssl->connection, data, size);
...
return n;
}

View File

@ -40,7 +40,7 @@ nginx由于出色的性能在世界范围内受到了越来越多人的关注
2012/11/09|增加第12章(请求体的读取和丢弃解析)
2012/11/24|更新第2章(ngx_buf_t的部分字段以及其他一些书写错误和表达)
2012/12/18|更新第11章(解析http块)
2012/12/10|增加第3章的内容
2012/12/10|增加第3章的内容
2012/12/28|补充和完善了第3章的内容
2013/01/25|增加了第2章(nginx的配置系统)
2013/02/18|增加了第2章(nginx的模块化体系结构, nginx的请求处理)
@ -49,6 +49,7 @@ nginx由于出色的性能在世界范围内受到了越来越多人的关注
2013/04/16|完成第9章第1节(源码目录结构、configure原理)
2013/09/30|完成第12章部分内容(多阶段执行链各个阶段解析)
2013/10/11|完成第12章部分内容(filter解析)
2013/10/11|完成第12章部分内容(ssl解析)
版权申明
++++++++++++