目录
- 1. 概述
- 2. alpn协议的简要理解
-
- 2.1 ssl的握手过程
- 2.2 通过抓包看一下alpn的细节
- 3. nginx源码分析
-
- 3.1 给ssl上下文设置alpn回调
- 3.2 连接初始化
- 3.3 处理alpn协议回调
- 3.4 握手完成,启用http协议
- 4.4 总结
阅读姊妹篇:深入理解nginx的https alpn机制
1. 概述
应用层协议协商(Application-Layer Protocol Negotiation,简称ALPN)是一个传输层安全协议(TLS) 的扩展, ALPN 使得应用层可以协商在安全连接层之上使用什么协议, 避免了额外的往返通讯, 并且独立于应用层协议。 ALPN 用于 HTTP/2 连接, 和HTTP/1.x 相比, HTTP 2的使用增强了网页的压缩率减少了网络延时。 ALPN 和 HTTP/2 协议是伴随着 Google 开发 SPDY 协议出现的。
nginx能够在一个ssl监听端口上同时提供http/1.1和http/2的服务,而http/2协议规定是必须基于tls安全通信协议的,因此,nginx在ssl握手过程中实现了ALPN的协议协商功能,能够自动完成和客户端的协议协商,从而根据客户端的协议支持能力提供http/1.1或者http/2的服务。
本文基于nginx,对alpn的实现原理进行深入的分析。
2. alpn协议的简要理解
2.1 ssl的握手过程
由上图可以看到,alpn的协商过程是在ssl握手的最早的两个阶段,即ClientHello和ServerHello中完成的,通过将应用层协议协商信息附加到ClientHello和ServerHello报文中完成的交互。
2.2 通过抓包看一下alpn的细节
下面通过TLS v1.2握手协议来查看alpn的细节,对于TLS v1.3协议,在ServerHello响应的时候由于alpn部分的信息被加密,所以查看起来比较会麻烦。抓包通过wireshark来实现,通过以下命令来模拟http2的请求:
curl --http2 "https://www.test.com" -kv
下到的报文如下:
ClientHello报文:
ServerHello报文:
在ClientHello报文中可以看到application_layer_protocol_negotiation的信息,表明了客户端可以同时支持h2和http/1.1,而在ServerHello报文中也可以看到application_layer_protocol_negotiation的信息,表明服务器选择了h2协议作为应用层协议。
3. nginx源码分析
3.1 给ssl上下文设置alpn回调
nginx在启动的时候,ngx_http_ssl_module模块在ngx_http_ssl_merge_srv_conf的时候,有以下这段代码对ssl的上下文进行初始化:
/* 创建ssl上下文 */
if (ngx_ssl_create(&conf->ssl, conf->protocols, conf) != NGX_OK) {
return NGX_CONF_ERROR;
}
/* 注册用于ssl上下文资源回收的回调函数
cln = ngx_pool_cleanup_add(cf->pool, 0);
if (cln == NULL) {
ngx_ssl_cleanup_ctx(&conf->ssl);
return NGX_CONF_ERROR;
}
cln->handler = ngx_ssl_cleanup_ctx;
cln->data = &conf->ssl;
/* 设置ClientHello消息回调 */
#if defined(T_INGRESS_SHARED_MEMORY_PB) && OPENSSL_VERSION_NUMBER >= 0x10101000L
SSL_CTX_set_client_hello_cb(conf->ssl.ctx,
ngx_http_ssl_client_hello_callback, NULL);
#endif
/* 设置SNI消息回调 */
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
if (SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx,
ngx_http_ssl_servername)
== 0)
{
ngx_log_error(NGX_LOG_WARN, cf->log, 0,
"nginx was built with SNI support, however, now it is linked "
"dynamically to an OpenSSL library which has no tlsext support, "
"therefore SNI is not available");
}
#endif
/* 设置ALPN消息回调 */
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
SSL_CTX_set_alpn_select_cb(conf->ssl.ctx, ngx_http_ssl_alpn_select, NULL);
#endif
没错,最以上源码的最后部分,nginx向openssl底层库设置了alpn的回调函数ngx_http_ssl_alpn_select,以期待接收到从客户端发过来的ClientHello中分析出有alpn扩展信息的时候回调这个函数。
3.2 连接初始化
在3.1节中所述的ssl上下文准备好以后,ssl连接当然是还没有建立的,只能说仍然只是停留在配置阶段,那么接下去可以想到客户端发起了tcp连接,nginx接受了这个连接,就需要开始对这个连接进行初始化,连接的初始化过程是由ngx_http_init_connection函数来完成的。那么如果开启了https,就会执行如下代码: