DEV Community

Michael
Michael

Posted on • Edited on

HTTP3之QUIC协议early data

背景

上回实现了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请求,下面主要描述主要代码步骤,如果不明白,可以参考全部文件

  1. OpenSSL session管理
    TLS1.3目前已经抛弃前面版本使用的Session IDs, 转而使用PSK(Pre Shared Key)。OpenSSL库还是使用Session的概念管理TLS1.3的Session Resumption。

  2. 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);
}
Enter fullscreen mode Exit fullscreen mode
  1. 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);
Enter fullscreen mode Exit fullscreen mode
  1. 新建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);
     }
}
Enter fullscreen mode Exit fullscreen mode
  1. 保存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);
     }
}
Enter fullscreen mode Exit fullscreen mode
  1. 在应用代码侧, 如果可以使用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
}
Enter fullscreen mode Exit fullscreen mode

后续

下次继续QUIC特性key update。

Top comments (0)