目录
- 1 缘起
-
- 1.1 功能定义
- 2. 实现后的效果
- 2.1 配置文件
- 2.2 运行效果
- 3. 代码实现
-
- 3.1 配置指令
-
- 3.1.1 配置指令定义:
- 3.1.2 配置指令结构体:
- 3.1.3 配置指令源码定义:
- 3.2 模块的初始化
- 3.3 添加新的健康检测类型的定义
- 3.4 握手完成后的处理
- 3. 5 发送http请求
- 3.6 接收http响应
- 3.7 连接关闭
- 3.8 ssl上下文的释放
1 缘起
前面的《nginx upstream server主动健康检测模块ngx_http_upstream_check_module 使用和源码分析》系列已经分析了ngx_http_upstream_check_module的实现原理,并且在借助这个模块的框架实现了一个udp健康检测的新功能。
但是ngx_http_upstream_check_module还缺乏基于https监测上游服务器健康状况的能力,始终是一个缺憾,因此,本文基于《nginx upstream server主动健康检测模块ngx_http_upstream_check_module 使用和源码分析》和《nginx stream proxy 模块的ssl连接源码分析》两篇博文的分析成果,来实现一个基于https的上游服务器健康检测的能力。
1.1 功能定义
本次支持的功能:
- 支持向上游服务器发起https请求功能
- 请求的报文复用原有的http检测的请求报文定义
- 响应的状态码检测复用原有的http检测的响应码的定义
- 支持ssl握手过程中添加sni扩展信息
- 支持ssl握手协议类型的配置
- 支持ssl握手协议加密套件的配置
暂时不支持的功能:
- 不支持ssl会话复用(会话复用可以降低上游服务器的ssl握手压力)
- 不支持ssl证书双向验证
- 不支持服务器端证书有效性验证
&ems; 由于本次主要是检验https的链接握手流程,对一些不是特别关键的ssl握手特性暂时不支持主要是为了简化代码逻辑,但是不影响业务流程,这样也便于在本文中将整个实现流程进行阐述。后续可以参照ngx_stream_proxy_module中的实现,继续将这些特性进行完善,以臻于完美。
2. 实现后的效果
首先来看一下实现后的效果,有一些感性的认识。
2.1 配置文件
#user nobody;
worker_processes 1;
daemon off;
master_process off;
error_log logs/error.log;
pid logs/nginx.pid;
load_module objs/ngx_http_upstream_check_module.so;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log logs/access.log main;
sendfile on;
keepalive_timeout 65;
upstream backend {
# 开启https健康检测
check type=https interval=3000 rise=2 fall=5 timeout=1000 port=443;
check_http_send "GET / HTTP/1.1\r\nHost: www.baidu.com\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
# 设置sni hostname
check_ssl_server_name www.baidu.com;
server 183.240.99.24:443;
}
server {
listen 9080;
server_name localhost;
# 开启本模块的状态查询接口
location /status {
check_status html;
}
location / {
proxy_pass http://backend;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
这里假设将www.baidu.com作为被代理的上游服务器,以上配置文件中的upstream块中定义了一个https的健康检测类型, check_http_send复用了http的定义,而check_ssl_server_name是新增的指令,用来配置ssl握手设置sni扩展信息中的主机host信息。
2.2 运行效果
启动tengine后,可以通过查看nginx的error.log日志进行观察, 可以看到如下信息:
2024/02/16 09:31:33 [error] 23638#0: https check failed with return code: 403
2024/02/16 09:31:33 [error] 23638#0: check protocol https error with peer: 192.168.0.1:443
2024/02/16 09:31:45 [info] 23663#0: enable check peer: 192.168.0.1:443
error.log中的前面两条因为服务器响应403报了错误,对应在配置了“check_http_expect_alive http_2xx http_3xx;”的情况。如果配置改成“check_http_expect_alive http_2xx http_3xx http_4xx;” 则报后面那条上游服务器enable的信息。证明https的检测功能已经可用了。
另外我们可以通过wireshark抓包观察交互的情况,抓包如下:
从上述抓包的内容可以看到ssl的握手交互的过程都已经有了。关于如何进行ssl握手的内容不在本文的讨论范围之内,可以参考HTTPS篇之SSL握手过程详解 或者通过Wireshark分析HTTPS握手过程与协议概述
3. 代码实现
由于https进行ssl握手,需要一些额外的参数,因此在nginx的配置文件http.upstream块中需要新增一些配置指定定义。
3.1 配置指令
3.1.1 配置指令定义:
支持的配置指令如下:
- check_ssl_ciphers:
配置加密套件,格式参考proxy_ssl_ciphers - check_ssl_protocols:
和服务器交互采用的ssl协议版本,如TLSv1.1 TLSv1.2等,格式参考proxy_ssl_protocols。 - check_ssl_server_name:
和服务器进行ssl握手时候采用的sni扩展host名字,如果不设置并且upstream块中的server是用域名设置的,那么默认就采用设置的服务器名字。 - check_ssl_verify:[on/off]
是否校验服务器的证书有效性。(待后续实现) - check_ssl_session_reuse: [on/off]
和上游服务器进行ssl握手的时候是否复用ssl会话信息。(待后续实现)
3.1.2 配置指令结构体:
为了对上述定义的配置指令进行保存,需要在ngx_http_upstream_check_srv_conf_s结构体中增加对应的参数,包括ssl_ciphers、ssl_protocols、ssl_server_name和ssl四个参数,其中ssl参数用来保存ssl协议配置上下文。
struct ngx_http_upstream_check_srv_conf_s {
ngx_uint_t port;
ngx_uint_t fall_count;
ngx_uint_t rise_count;
ngx_msec_t check_interval;
ngx_msec_t check_timeout;
ngx_uint_t check_keepalive_requests;
ngx_check_conf_t *check_type_conf;
ngx_str_t send;
union {
ngx_uint_t return_code;
ngx_uint_t status_alive;
} code;
ngx_array_t *fastcgi_params;
ngx_uint_t default_down;
ngx_uint_t unique;
ngx_uint_t udp : 1; /* 是否udp socket */
ngx_int_t match_part : 1; /* 是否只要部分匹配就可以了 */
ngx_int_t match_offset; /* udp响应期望的内容从哪个字节开始匹配 */
ngx_str_t expect; /* udp响应的期望内容 */
#if (NGX_HTTP_SSL)
ngx_ssl_t *ssl; /* ssl 配置上下文 */
ngx_str_t ssl_ciphers; /* ssl 加密套件 */
ngx_uint_t ssl_protocols; /* 采用的ssl协议 */
ngx_str_t ssl_server_name; /* ssl握手的sni扩展hostname */
#endif
};
以上添加的ssl_protocols参数在ngx_http_upstream_check_create_srv_conf函数中需要将其设置为NGX_CONF_UNSET_UINT,避免nginx在解析配置文件的时候出现参数重复的报错。
3.1.3 配置指令源码定义:
#if (NGX_HTTP_SSL)
{
ngx_string("check_ssl_ciphers"),
NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_upstream_check_srv_conf_t, ssl_ciphers),
NULL },
{
ngx_string("check_ssl_protocols"),
NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
ngx_conf_set_bitmask_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_upstream_check_srv_conf_t, ssl_protocols),
&ngx_upstream_check_ssl_protocols },
{
ngx_string("check_ssl_server_name"),
NGX_HTTP_UPS_CONF|NGX_CONF_FLAG,
ngx_conf_set_str_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_upstream_check_srv_conf_t