赞
踩
我一直来对ssl建立连接的过程一知半解,以前分析nginx代码的时候一旦碰到ssl连接部分的代码都是直接跳过,前面在分析ngx_http_upstream_dynamic_module的时候正好想到了是不是可以给它添加一个能够支持https健康检查的功能,所以今天决定沉下心来仔细分析一下nginx本身的与上游服务器建立连接的实现逻辑。
为了简单起见,我决定选用ngx_stream_proxy_module模块作为分析学习的目标,因为相对于ngx_http_stream_proxy_module来说,前者逻辑上更加纯粹,少了七层的业务逻辑,让我能够更加专注地去分析关于ssl连接处理逻辑部分。
希望这次通过分析,能够对ssl连接的建立以及其后续的读写交互的实现逻辑有个整体的把握,在此基础上,将来为ngx_http_upstream_dynamic_module实现一个能够支持https主动健康检测的功能。
这次,我准备采用官方原生的最新稳定版nginx 1.24.0作为分析对象。为什么不用tengine呢?因为现在的tengine还夹杂了国密openssl的支持逻辑,采用官方原生版本更加纯粹,让分析的目标更加聚焦。
配置一个分析验证环境来进行测试分析还是非常简单的,过程如下:
1. 从官网下载好nginx 1.24.0版本后进行解压,然后用以下命令进行配置:
./configure --prefix=`pwd` --with-stream --with-stream_ssl_module
这样nginx从源码层面就开启了支持ssl的TCP代理能力。
2. 对nginx.conf文件进行配置,配置内容如下:
#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } stream { server { listen 9000; proxy_ssl on; /* 连接上游服务器采用ssl协议 */ proxy_pass 104.193.88.77:443; } }
3. 启动nginx,进行curl测试:
curl "http://127.0.0.1:9000/test/" -v * Trying 127.0.0.1:9000... * Connected to 127.0.0.1 (127.0.0.1) port 9000 (#0) > GET /test/ HTTP/1.1 > Host: 127.0.0.1:9000 > User-Agent: curl/7.81.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 500 Internal Server Error < Content-Length: 0 < Content-Type: text/plain; charset=utf-8 < Date: Wed, 07 Feb 2024 02:57:01 GMT < Server: bfe < * Connection #0 to host 127.0.0.1 left intact
从上面的信息中已经可以看到上游服务器已经响应了,只不过它响应是500错误,这个无所谓,至少表明https透明代理的功能已经正常工作了。
本文主要聚焦在ssl连接逻辑的分析,所以中间会跳过和ssl逻辑不太相关的代码,虽然可能这部分对nginx本身的功能逻辑非常重要。另外,本文也假设只是TCP代理,不对UDP代理进行分析。
下面直接进入主题,从代理模块的请求入口点开始分析。
代理模块的请求入口点是ngx_stream_proxy_handler函数,一旦客户端和nginx建立了TCP连接后,nginx就会调用代理模块的这个函数,开始与上游服务器建立连接。
该函数源码主要就是以下列出的三个步骤:
static void
ngx_stream_proxy_handler(ngx_stream_session_t *s)
{
/* 创建ngx_stream_upstream_t上下文,对它进行必要的初始化,
并关联到ngx_stream_session_t中 */
/* 如果上游服务器的地址已经解析好就调用ngx_stream_proxy_connect开始连接上游服务器 */
/* 如果上游服务器的地址需要域名解析则开启异步解析流程 */
}
因此,我们需要重点关注的是ngx_stream_proxy_connect,它负责与上游服务器建立TCP连接。
static void ngx_stream_proxy_connect(ngx_stream_session_t *s) { ngx_int_t rc; ngx_connection_t *c, *pc; ngx_stream_upstream_t *u; ngx_stream_proxy_srv_conf_t *pscf; c = s->connection; c->log->action = "connecting to upstream"; pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); u = s->upstream; u->connected = 0; u->proxy_protocol = pscf->proxy_protocol; if (u->state) { u->state->response_time = ngx_current_msec - u->start_time; } u->state = ngx_array_push(s->upstream_states); if (u->state == NULL) { ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); return; } ngx_memzero(u->state, sizeof(ngx_stream_upstream_state_t)); u->start_time = ngx_current_msec; u->state->connect_time = (ngx_msec_t) -1; u->state->first_byte_time = (ngx_msec_t) -1; u->state->response_time = (ngx_msec_t) -1; /* 创建SOCKET,并发起异步连接请求*/ rc = ngx_event_connect_peer(&u->peer); ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "proxy connect: %i", rc); if (rc == NGX_ERROR) { ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); return; } u->state->peer = u->peer.name; if (rc == NGX_BUSY) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "no live upstreams")
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。