1. 为什么需要ALPN
TLS只负责建立加密通道,不负责上层到底是什么应用,所以如果用户想在1个地址上支持多种应用协议,比如1个443端口,既想支持HTTP/1.1,还能支持HTTP/2、SPDY/1,没有ALPN(App-Layer Protocol Negotiation)的话,用户需要在ssl建立连接之后,再协商是用哪个协议,然后分发到各个协议的处理流程中,这样多了一个来回。ALPN把应用层协商附带到握手协商中,让用户在握手建立之后就立即知道使用的应用协议,这样节省了一个来回。
2. TLS中的具体实现
主要见 RFC 7301。
ALPN作为扩展项存在ClientHello和ServerHello中,ALPN格式为:
1 2 3 4 5 6 7 8 9 10 11 12
| enum { application_layer_protocol_negotiation(16), (65535) } ExtensionType;
The "extension_data" field of the ("application_layer_protocol_negotiation(16)") extension SHALL contain a "ProtocolNameList" value.
opaque ProtocolName<1..2^8-1>;
struct { ProtocolName protocol_name_list<2..2^16-1> } ProtocolNameList;
|
协商过程:
client在ClientHello中设置期望的协议列表,优先支持的放在前边。
server设置支持的应用协议列表,优先支持的放到前边。收到ClientHello后,应用选择支持的协议。
server在ServerHello中返回支持的1个应用协议,此后的应用层就使用该协议。
client收到ServerHello中的应用协议,此后的应用层就使用该协议。
如果server没有支持的协议,会返回握手失败的警告。
需要注意的是ALPN协商每次都在握手的时候进行,不会保存到session中,所以即使会话恢复,也会重新协商。会话恢复是TLS层的东西,不是应用层的,不管是哪个协议的应用,都可以使用TLS恢复的会话。
3. openssl中的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| // client或者server设置支持的应用协议,格式是带1个字节长度前缀的协议,类似 // "\x08HTTP/1.1" 或者 "\x08HTTP/1.1\x06SPDY/1" int SSL_CTX_set_alpn_protos(SSL_CTX *ctx, const unsigned char *protos, unsigned int protos_len); int SSL_set_alpn_protos(SSL *ssl, const unsigned char *protos, unsigned int protos_len);
// 设置协商回调,server在收到ALPN时会调用该回调,让用户去进行协商和一些其他操作 void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, int (*cb) (SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg), void *arg);
// openssl提供的标准的协商过程,应用可以在握手的时候就知道准备用什么协议了。 // 该函数应该在SSL_CTX_set_alpn_select_cb()设置的回调中使用 int SSL_select_next_proto(unsigned char **out, unsigned char *outlen, const unsigned char *server, unsigned int server_len, const unsigned char *client, unsigned int client_len)
// 取出已经协商好的应用协议 void SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data, unsigned int *len);
|
参考
- RFC 7301