### nginx支持ssl简介[](http://tengine.taobao.org/book/chapter_12.html#nginxssl "永久链接至标题")
nginx-1.2.0编译时默认是不支持ssl协议的,需要通过编译指令来开启对其支持:
[](http:// "点击提交Issue,反馈你的意见...")
./configure --with-http_ssl_module
在nginx源码中,ssl相关代码用宏定义变量NGX_HTTP_SSL来控制是否开启。这给我们查找和阅读ssl相关代码带来了方便,如下:
ssl协议工作在tcp协议与http协议之间。nginx在支持ssl协议时,需要注意三点,其他时候只要正常处理http协议即可:
1. tcp连接建立时,在tcp连接上建立ssl连接
1. tcp数据接收后,将收到的数据解密并将解密后的数据交由正常http协议处理流程
1. tcp数据发送前,对(http)数据进行加密,然后再发送
以下章节将分别介绍这三点。
[](http:// "点击提交Issue,反馈你的意见...")
### ssl连接建立(ssl握手)[](http://tengine.taobao.org/book/chapter_12.html#ssl-ssl "永久链接至标题")
[](http:// "点击提交Issue,反馈你的意见...")
#### 对ssl连接建立的准备[](http://tengine.taobao.org/book/chapter_12.html#ssl "永久链接至标题")
更具ssl协议规定,在正式发起数据收发前,需要建立ssl连接,连接建立过程既ssl握手。nginx在创建和初始化http请求阶段的同时为tcp连接建立做准备,主要流程在ngx_http_init_request函数中实现:
[](http:// "点击提交Issue,反馈你的意见...")
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 简化代码如下:
[](http:// "点击提交Issue,反馈你的意见...")
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直接发送出去,而没有加密)。
[](http:// "点击提交Issue,反馈你的意见...")
#### 实际ssl握手阶段[](http://tengine.taobao.org/book/chapter_12.html#id18 "永久链接至标题")
由于在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,首先判断连接是否超时,如果超时则关闭连接
[](http:// "点击提交Issue,反馈你的意见...")
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连接中没有准备好的数据,则重新添加读事件退出等待新数据到来。
[](http:// "点击提交Issue,反馈你的意见...")
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)。
[](http:// "点击提交Issue,反馈你的意见...")
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会替换连接的读写接口。这样,后续需要读写数据时,替换的接口会对数据进行加密解密。详细代码见下:
[](http:// "点击提交Issue,反馈你的意见...")
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标志的单独处理。)
[](http:// "点击提交Issue,反馈你的意见...")
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请求。
[](http:// "点击提交Issue,反馈你的意见...")
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;
}
[](http:// "点击提交Issue,反馈你的意见...")
#### ssl协议接受数据[](http://tengine.taobao.org/book/chapter_12.html#id19 "永久链接至标题")
ngx_http_process_request中处理http请求,需要读取和解析http协议。而实际数据读取是通过c->recv()函数来读取的,此函数已经在ngx_ssl_handshake中被替换成ngx_ssl_recv了。
ngx_ssl_recv函数中调用openssl库函数SSL_read()来读取并解密数据,简化后如下:
[](http:// "点击提交Issue,反馈你的意见...")
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;
}
[](http:// "点击提交Issue,反馈你的意见...")
#### ssl协议发送数据[](http://tengine.taobao.org/book/chapter_12.html#id20 "永久链接至标题")
当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函数来加密并发送数据。
[](http:// "点击提交Issue,反馈你的意见...")
/* 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;
}
- 上篇:nginx模块开发篇
- nginx平台初探
- 初探nginx架构
- nginx基础概念
- connection
- request
- keepalive
- pipe
- lingering_close
- 基本数据结构
- ngx_str_t
- ngx_pool_t
- ngx_array_t
- ngx_hash_t
- ngx_hash_wildcard_t
- ngx_hash_combined_t
- ngx_hash_keys_arrays_t
- ngx_chain_t
- ngx_buf_t
- ngx_list_t
- ngx_queue_t
- nginx的配置系统
- 指令参数
- 指令上下文
- nginx的模块化体系结构
- 模块的分类
- nginx的请求处理
- handler模块
- handler模块简介
- 模块的基本结构
- 模块配置结构
- 模块配置指令
- 模块上下文结构
- 模块的定义
- handler模块的基本结构
- handler模块的挂载
- handler的编写步骤
- 示例: hello handler 模块
- handler模块的编译和使用
- 更多handler模块示例分析
- http access module
- http static module
- http log module
- 过滤模块
- 过滤模块简介
- 过滤模块的分析
- upstream模块
- upstream模块
- upstream模块接口
- memcached模块分析
- 本节回顾
- 负载均衡模块
- 配置
- 指令
- 钩子
- 初始化配置
- 初始化请求
- peer.get和peer.free回调函数
- 本节回顾
- 其他模块
- core模块
- event模块
- 模块开发高级篇
- 变量
- 下篇:nginx原理解析篇
- nginx架构详解
- nginx的源码目录结构
- nginx的configure原理
- 模块编译顺序
- nginx基础设施
- 内存池
- nginx的启动阶段
- 概述
- 共有流程
- 配置解析
- nginx的请求处理阶段
- 接收请求流程
- http请求格式简介
- 请求头读取
- 解析请求行
- 解析请求头
- 请求体读取
- 读取请求体
- 丢弃请求体
- 多阶段处理请求
- 多阶段执行链
- POST_READ阶段
- SERVER_REWRITE阶段
- FIND_CONFIG阶段
- REWRITE阶段
- POST_REWRITE阶段
- PREACCESS阶段
- ACCESS阶段
- POST_ACCESS阶段
- TRY_FILES阶段
- CONTENT阶段
- LOG阶段
- Nginx filter
- header filter分析
- body filter分析
- ngx_http_copy_filter_module分析
- ngx_http_write_filter_module分析
- subrequest原理解析
- https请求处理解析
- 附录A 编码风格
- 附录B 常用API
- 附录C 模块编译,调试与测试