背景
上回实现了QUIC协议的Connection Migration,本章实现early data。首先要浓情以这个概念,early data跟0-RTT不是一个概念,前者基于后者,在首次发送初始frame时候就将请求内容一起发送过去,这里涉及到PSK和session ticket概念,这个可以通过过QUIC协议整明白,网上也有文章说明,后面有机会整理。
https://datatracker.ietf.org/doc/html/rfc9000
https://datatracker.ietf.org/doc/html/rfc9001#name-0-rtt
实现
本文主要使用ngtcp2和nghttp3实现early data请求,下面主要描述主要代码步骤,如果不明白,可以参考全部文件。
OpenSSL session管理
TLS1.3目前已经抛弃前面版本使用的Session IDs, 转而使用PSK(Pre Shared Key)。OpenSSL库还是使用Session的概念管理TLS1.3的Session Resumption。OpenSSL可以配置使用外部pem文件保存session数据
/* 在成功新建SSL Context后配置callback保存PSK数据 */
if (c->session_file)
{
// session stored externally by hand in callback function
SSL_CTX_set_session_cache_mode(c->ssl_ctx, SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL);
SSL_CTX_sess_set_new_cb(c->ssl_ctx, new_session_cb);
}
- Session callback中先使用max_early_data参数判断当前连接是否支持early data, 然后保存session数据
uint32_t max_early_data;
if ((max_early_data = SSL_SESSION_get_max_early_data(session)) != UINT32_MAX)
{
fprintf(stderr, "max_early_data_size is not 0xffffffff: %#x\n", max_early_data);
}
BIO *f = BIO_new_file(c->session_file, "w");
if (f == NULL)
{
fprintf(stderr, "Could not write TLS session in %s\n", c->session_file);
return 0;
}
if (!PEM_write_bio_SSL_SESSION(f, session))
{
fprintf(stderr, "Unable to write TLS session to file\n");
}
BIO_free(f);
- 新建SSL对象后, 加载session数据
BIO *f = BIO_new_file(c->session_file, "r");
if (f == NULL) /* open BIO file failed */
{
fprintf(stderr, "BIO_new_file: Could not read TLS session file %s\n", c->session_file);
}
else
{
SSL_SESSION *session = PEM_read_bio_SSL_SESSION(f, NULL, 0, NULL);
BIO_free(f);
if (session == NULL)
{
fprintf(stderr, "PEM_read_bio_SSL_SESSION: Could not read TLS session file %s\n", c->session_file);
}
else
{
if (!SSL_set_session(c->ssl, session))
{
fprintf(stderr, "SSL_set_session: Could not set session\n");
}
else if (!c->disable_early_data && SSL_SESSION_get_max_early_data(session))
{
c->early_data_enabled = 1;
SSL_set_quic_early_data_enabled(c->ssl, 1);
}
SSL_SESSION_free(session);
}
}
- 保存QUIC协议中的Transport Prameters为pem格式文件,用于下次early data时发送,可以在ngtcp2的handshake_completed的callback中保存
/* save quic transport parameters */
if (c->tp_file)
{
uint8_t data[256];
ngtcp2_ssize datalen = ngtcp2_conn_encode_0rtt_transport_params(c->conn, data, 256);
if (datalen < 0)
{
fprintf(stderr, "Could not encode 0-RTT transport parameters: %s\n", ngtcp2_strerror(datalen));
return -1;
}
else if (write_transport_params(c->tp_file, data, datalen) != 0)
{
fprintf(stderr, "Could not write transport parameters in %s\n", c->tp_file);
}
}
- 在应用代码侧, 如果可以使用early data功能, 传递上次保存的Quic Transport Parameters,
/* load quic transport parameters */
if (c->early_data_enabled && c->tp_file)
{
char *data;
long datalen;
if ((data = read_pem(c->tp_file, "transport parameters", "QUIC TRANSPORT PARAMETERS", &datalen)) == NULL)
{
fprintf(stderr, "client quic init early data read pem failed\n");
c->early_data_enabled = 0;
}
else
{
rv = ngtcp2_conn_decode_and_set_0rtt_transport_params(c->conn, (uint8_t *)data, (size_t)datalen);
if (rv != 0)
{
fprintf(stderr, "ngtcp2_conn_decode_and_set_0rtt_transport_params failed: %s\n", ngtcp2_strerror(rv));
c->early_data_enabled = 0;
}
else if (make_stream_early(c) != 0) // setup nghttp3 connection and populate http3 request
{
free(data); // free memory which allocated in read_pem function
return -1;
}
}
free(data); // free memory which allocated in read_pem function
}
后续
下次继续QUIC特性key update。
Top comments (0)