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。

Image of Docusign

Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more