基于阿里xquic库的QUIC协议分析

登阁揽胜 2022-01-19 21:51:06

目录

  • 基于阿里巴巴xquic的quic协议分析
  • 一、HTTP/3与QUIC
  • 1. HTTP3与QUIC的关系
  • 2. QUIC协议的特点
  • 3. XQUIC
  • 二、xquic配置
  • 1. 系统信息
  • 2. 使用BabaSSL编译
  • 3. 运行示例服务端和客户端
  • 三、xquic部分源码分析(客户端)
  • 1. xqc_connect
  • 2. xqc_client_connect
  • 3. xqc_client_create_connection
  • 4. xqc_client_create_tls
  • 四、xquic评价与展望
  • 参考资料

基于阿里巴巴xquic的quic协议分析

一、HTTP/3与QUIC

1. HTTP3与QUIC的关系

img

通过图片可以清楚地了解 HTTP 协议的发展和其中结构上的差异。

自从 2015 年 HTTP/2 标准发布后,大多数主流浏览器已经支持该标准。从图中可以看出,虽然 HTTP/2 对传输层协议做了极大的改动和重新设计,但底层仍是基于 TCP 协议,然而与之相比,UDP 协议在效率上存在天然优势。因此,Google 开发了基于 UDP 的名为 QUIC 的传输层协议,QUIC 全称 Quick UDP Internet Connections。后经提议,互联网工程任务组正式将基于 QUIC 协议的 HTTP (HTTP over QUIC)重命名为 HTTP/3。

2. QUIC协议的特点

QUIC 又称快速 UDP 网络连接,它基于 UDP 协议,是用来替代 HTTP/2 中 TCP 、TLS 的传输层协议,运行在 QUIC 协议上的 HTTP 协议即 HTTP/3。

QUIC 的优势主要体现在以下方面:

  • 作为基于UDP的协议,不需要TCP三次握手及TLS的握手时间。
  • 拥塞控制改进,针对不同场景采用多种不同的拥塞控制算法。
  • 多路复用,且没有TCP阻塞问题。
  • 连接迁移,在wifi转4g等场景下,高层协议保证连接不会中断。
  • 前向冗余纠错,可根据冗余信息来恢复数据包内容。

另外,QUIC将网络模型中控制传输行为的传输层,从内核态迁移到了用户态来实现。这将带来2个巨大的优势:

(1) 迭代优化效率大大提升。从服务端角度看,大型在线系统的内核升级成本非常高,因此通常的升级周期较长。从客户端角度看,设备操作系统版本升级同样由相应厂商控制,升级周期难以统一。调整为用户态实现后,不管是服务端还是用户端,都将加速迭代而不受系统软件的限制。

(2) 灵活适应不同业务场景的网络需求。随着直播、短视频等不同业务需求不断涌现,实现在内核态的TCP,不能采用同一套拥塞控制算法或参数适应各类业务场景。5G条件下这种缺陷变得更加显著。QUIC可将拥塞控制算法/参数调控到连接的粒度,甚至可针对同一个APP内的不同业务场景灵活适配。

3. XQUIC

XQUIC是IETF QUIC草案版本的一个C协议库实现,整体链路架构设计如图所示。XQUIC内部包含了QUIC-Transport(传输层)、QUIC-TLS(加密层)和HTTP/3.0(应用层)的实现。在外部依赖方面,TLS/1.3依赖了开源boringssl或openssl实现,除此之外无其他外部依赖。

XQUIC传输层内部流程设计如下图,XQUIC内部维护了每条连接的状态机、Stream状态机,在Stream级别实现可靠传输,并通过读事件通知的方式将数据投递给应用层。传输层Stream与应用层HTTP/3的Request Stream有一一映射关系,通过这样的方式解决HTTP/2 over TCP的头部阻塞问题。

img

由于IETF QUIC传输层的设计可以独立剥离,并作为TCP的替代方案对接其他应用层协议。XQUIC内部实现同样基于这样的分层方式,并对外提供两套原生接口:HTTP/3请求应答接口 和 传输层独立接口(类似TCP),使得例如RTMP、上传协议等可以较为轻松地接入。

img

二、xquic配置

1. 系统信息

OS: Ubuntu 20.04 focal(on the Windows Subsystem for Linux)
Kernel: x86_64 Linux 5.10.60.1-micros
CPU: AMD Ryzen 5 3550H with Radeon Vega Mobile Gfx @ 8x 2.096GHz
RAM: 8GB

2. 使用BabaSSL编译

# 获取 XQUIC 源码
git clone https://github.com/alibaba/xquic.git
cd xquic

# 编译 BabaSSL
git clone https://github.com/BabaSSL/BabaSSL.git ./third_party/babassl
cd ./third_party/babassl/
./config --prefix=/usr/local/babassl
make -j
SSL_TYPE_STR="babassl"
SSL_PATH_STR="${PWD}"
SSL_INC_PATH_STR="${PWD}/include"
SSL_LIB_PATH_STR="${PWD}/libssl.a;${PWD}/libcrypto.a"
cd -

# 使用 BabaSSL 编译 XQUIC
git submodule update --init --recursive
mkdir -p build; cd build
cmake -DGCOV=on -DCMAKE_BUILD_TYPE=Debug -DXQC_ENABLE_TESTING=1 -DXQC_SUPPORT_SENDMMSG_BUILD=1 -DXQC_ENABLE_EVENT_LOG=1 -DXQC_ENABLE_BBR2=1 -DXQC_DISABLE_RENO=0 -DSSL_TYPE=${SSL_TYPE_STR} -DSSL_PATH=${SSL_PATH_STR} -DSSL_INC_PATH=${SSL_INC_PATH_STR} -DSSL_LIB_PATH=${SSL_LIB_PATH_STR} ..
make -j4

注意要准备好libevent-dev包,以避免'<event2/event.h>' undefined的错误。
Ubuntu下使用以下命令安装:

sudo apt install libevent-dev

3. 运行示例服务端和客户端

分别在不同的终端窗口运行'build'文件夹下的test_server和test_client。

server :

denk@DESKTOP-B0LS0SD:~/xquic/build$ ./test_server
congestion control flags: 0
/home/denk/xquic/tests/test_server.c:906 (xqc_server_accept)
/home/denk/xquic/tests/test_server.c:369 (xqc_server_h3_conn_create_notify)
sending rate: 0.000 Kbps
recvfrom size:32256
timer wakeup now:1642594182366290
timer wakeup now:1642594182389419
/home/denk/xquic/tests/test_server.c:400 (xqc_server_h3_conn_handshake_finished)
0rtt_flag:2
recvfrom size:16761
/home/denk/xquic/tests/test_server.c:564 (xqc_server_request_create_notify)
:method = POST
:scheme = https
:path = /path/resource
host = test.xquic.com
content-type = text/plain
content-length = 1048576
xqc_h3_request_recv_body read:4096, offset:4096, fin:0
xqc_h3_request_recv_body read:4096, offset:8192, fin:0
xqc_h3_request_recv_body read:4096, offset:12288, fin:0
recvfrom size:21341
xqc_h3_request_recv_body read:4096, offset:16384, fin:0
xqc_h3_request_recv_body read:4096, offset:20480, fin:0
...
xqc_h3_request_recv_body read:4096, offset:1048576, fin:1
xqc_h3_request_send_headers success size=8
xqc_h3_request_send_body sent:1048576, offset=1048576
recvfrom size:34
recvfrom size:36
recvfrom size:37
sending rate: 34629.855 Kbps
recvfrom size:222
recvfrom size:37
recvfrom size:37
recvfrom size:37
recvfrom size:37
timer wakeup now:1642594183040102
/home/denk/xquic/tests/test_server.c:577 (xqc_server_request_close_notify)
sending rate: 1125.921 Kbps
recvfrom size:33
timer wakeup now:1642594184538073
/home/denk/xquic/tests/test_server.c:386 (xqc_server_h3_conn_close_notify)
send_count:1045, lost_count:0, tlp_count:4, recv_count:1090, srtt:17255 early_data_flag:2, conn_err:0, ack_info:#1078-1068#0-0#0-0#v0429

client :

denk@DESKTOP-B0LS0SD:~/xquic/build$ ./test_client
congestion control flags: 0
read token size 9
/home/denk/xquic/tests/test_client.c:685 (xqc_client_h3_conn_create_notify)
xqc_h3_conn_is_ready_to_send_early_data:1
xqc_h3_request_send_headers success size=39
xqc_h3_request_send_body sent:28672, offset=28672
save_tp_cb use server domain as the key. h3[1]
/home/denk/xquic/tests/test_client.c:736 (xqc_client_h3_conn_handshake_finished)
0rtt_flag:2
====>DCID:381014683905b7dd
====>SCID:0bcb285e3aa7fcac
xqc_h3_request_send_body sent:1019904, offset=1048576
save_session_cb use server domain as the key. h3[1]
save_session_cb use server domain as the key. h3[1]
/home/denk/xquic/tests/test_client.c:752 (xqc_client_h3_conn_ping_acked_notify)
====>ping_id:1
/home/denk/xquic/tests/test_client.c:752 (xqc_client_h3_conn_ping_acked_notify)
====>no ping_id
recvfrom size:3991
timer wakeup now:1642594182428293
...
:method = POST
:scheme = https
:path = /path/resource
:status = 200
host = test.xquic.com
content-type = text/plain
xqc_h3_request_recv_body read:4096, offset:4096, fin:0
xqc_h3_request_recv_body read:4096, offset:8192, fin:0
xqc_h3_request_recv_body read:4096, offset:12288, fin:0
...
>>>>>>>> request time cost:471444 us, speed:4448 K/s
>>>>>>>> send_body_size:1048576, recv_body_size:1048576
timer wakeup now:1642594182957815
/home/denk/xquic/tests/test_client.c:1474 (xqc_client_request_close_notify)
send_body_size:1048576, recv_body_size:1048576, send_header_size:103, recv_header_size:92, recv_fin:1, err:0
xqc_client_timeout_callback now 1642594183328903
xqc_client_timeout_callback now 1642594184330054
recving rate: 0.174 Kbps
recvfrom size:33
timer wakeup now:1642594184467099
/home/denk/xquic/tests/test_client.c:720 (xqc_client_h3_conn_close_notify)
conn errno:256
send_count:1068, lost_count:0, tlp_count:0, recv_count:1073, srtt:5478 early_data_flag:2, conn_err:0, ack_info:#1061-15#0-0#0-0#v0429

三、xquic部分源码分析(客户端)

从外到内展示 xquic client 封装的功能。

1. xqc_connect

const xqc_cid_t *
xqc_connect(xqc_engine_t *engine, const xqc_conn_settings_t *conn_settings, 
    const unsigned char *token, unsigned token_len, const char *server_host, int no_crypto_flag,
    const xqc_conn_ssl_config_t *conn_ssl_config, const struct sockaddr *peer_addr,
    socklen_t peer_addrlen, const char *alpn, void *user_data)
{
    xqc_connection_t *conn;

    /* 判断ALPN */
    if (NULL == alpn || strlen(alpn) > XQC_MAX_ALPN_LEN) {
        return NULL;
    }
    /* 创建连接(调用xqc_client_connect) */
    conn = xqc_client_connect(engine, conn_settings, token, token_len, server_host, no_crypto_flag, 
                              conn_ssl_config, alpn, peer_addr, peer_addrlen, user_data);
    if (conn) {
        return &conn->scid_set.user_scid;
    }

    return NULL;
}

2. xqc_client_connect

xqc_connection_t *
xqc_client_connect(xqc_engine_t *engine, const xqc_conn_settings_t *conn_settings,
    const unsigned char *token, unsigned token_len, const char *server_host, int no_crypto_flag,
    const xqc_conn_ssl_config_t *conn_ssl_config, const char *alpn, 
    const struct sockaddr *peer_addr, socklen_t peer_addrlen, void *user_data)
{
    xqc_cid_t dcid;
    xqc_cid_t scid;
    /* 判断ssl config是否配置 */
    if (NULL == conn_ssl_config) {
        xqc_log(engine->log, XQC_LOG_ERROR,
                "|xqc_conn_ssl_config is NULL|");
        return NULL;
    }
    /* 判断token长度是否越界 */
    if (token_len > XQC_MAX_TOKEN_LEN) {
        xqc_log(engine->log, XQC_LOG_ERROR,
                "|%ud exceed XQC_MAX_TOKEN_LEN|", token_len);
        return NULL;
    }
    /* 判断scid、dcid是否正确 */
    if (xqc_generate_cid(engine, NULL, &scid, 0) != XQC_OK
        || xqc_generate_cid(engine, NULL, &dcid, 0) != XQC_OK)
    {
        xqc_log(engine->log, XQC_LOG_ERROR,
                "|generate dcid or scid error|");
        return NULL;
    }
    /* 创建连接(调用xqc_client_create_connection) */
    xqc_connection_t *xc = xqc_client_create_connection(engine, dcid, scid, conn_settings,
                                                        server_host, no_crypto_flag,
                                                        conn_ssl_config, alpn, user_data);
    if (xc == NULL) {
        xqc_log(engine->log, XQC_LOG_ERROR,
                "|create connection error|");
        return NULL;
    }

    if (token && token_len > 0) {
        xc->conn_token_len = token_len;
        memcpy(xc->conn_token, token, token_len);
    }

    if (peer_addr && peer_addrlen > 0) {
        xc->peer_addrlen = peer_addrlen;
        memcpy(xc->peer_addr, peer_addr, peer_addrlen);
    }

    xqc_log(engine->log, XQC_LOG_DEBUG, "|xqc_connect|");
    xqc_log_event(xc->log, CON_CONNECTION_STARTED, xc, XQC_LOG_REMOTE_EVENT);

    /* conn_create 回调 */
    if (xc->app_proto_cbs.conn_cbs.conn_create_notify) {
        if (xc->app_proto_cbs.conn_cbs.conn_create_notify(xc, &xc->scid_set.user_scid, user_data)) {
            xqc_conn_destroy(xc);
            return NULL;
        }

        xc->conn_flag |= XQC_CONN_FLAG_UPPER_CONN_EXIST;
    }

    /* xqc_conn_destroy 必须在连接插入 conns_active_pq 后调用 */
    if (!(xc->conn_flag & XQC_CONN_FLAG_TICKING)) {
        if (xqc_conns_pq_push(engine->conns_active_pq, xc, 0)) {
            return NULL;
        }
        xc->conn_flag |= XQC_CONN_FLAG_TICKING;
    }

    xqc_engine_main_logic_internal(engine, xc);

    /* 当连接被主函数逻辑destroy后,应该把错误返回到上层 */
    if (xqc_engine_conns_hash_find(engine, &scid, 's') == NULL) {
        return NULL;
    }

    return xc;
}

3. xqc_client_create_connection

xqc_connection_t *
xqc_client_create_connection(xqc_engine_t *engine, xqc_cid_t dcid, xqc_cid_t scid,
    const xqc_conn_settings_t *settings, const char *server_host, int no_crypto_flag,
    const xqc_conn_ssl_config_t *conn_ssl_config, const char *alpn, void *user_data)
{
    xqc_int_t               ret;
    xqc_transport_params_t  tp;
    xqc_trans_settings_t   *local_settings;

    xqc_connection_t *xc = xqc_conn_create(engine, &dcid, &scid, settings, user_data,
                                           XQC_CONN_TYPE_CLIENT);
    if (xc == NULL) {
        return NULL;
    }

    /* 保存初始dcid */
    xqc_cid_copy(&(xc->original_dcid), &(xc->dcid_set.current_dcid));

    /* 创建初始加密流,必须在tls之前创建,以存储ClientHello */
    xc->crypto_stream[XQC_ENC_LEV_INIT] = xqc_create_crypto_stream(xc, XQC_ENC_LEV_INIT, user_data);
    if (!xc->crypto_stream[XQC_ENC_LEV_INIT]) {
        goto fail;
    }

    /* 设置无加密选项 */
    local_settings = &xc->local_settings;
    if (no_crypto_flag == 1) {
        local_settings->no_crypto = XQC_TRUE;  /* no_crypto 1 means do not crypto*/

    } else {
        local_settings->no_crypto = XQC_FALSE;
    }

    /* 创建并初始化tls,启动ClientHello */
    if (xqc_client_create_tls(xc, conn_ssl_config, server_host, no_crypto_flag, alpn) != XQC_OK) {
        goto fail;
    }

    /* 复原服务器传输的参数 */
    if (conn_ssl_config->transport_parameter_data
        && conn_ssl_config->transport_parameter_data_len > 0)
    {
        xqc_memzero(&tp, sizeof(xqc_transport_params_t));
        ret = xqc_read_transport_params(conn_ssl_config->transport_parameter_data,
                                                  conn_ssl_config->transport_parameter_data_len, &tp);
        if (ret == XQC_OK) {
            xqc_conn_set_early_remote_transport_params(xc, &tp);
        }
    }

    if (xqc_conn_client_on_alpn(xc, alpn, strlen(alpn)) != XQC_OK) {
        goto fail;
    }

    return xc;
/* 失败则删除连接 */
fail:
    xqc_conn_destroy(xc);
    return NULL;
}

4. xqc_client_create_tls

xqc_int_t
xqc_client_create_tls(xqc_connection_t *conn, const xqc_conn_ssl_config_t *conn_ssl_config,
    const char *hostname, int no_crypto_flag, const char *alpn)
{
    xqc_int_t           ret;
    xqc_tls_config_t    cfg = {0};
    uint8_t             tp_buf[XQC_MAX_TRANSPORT_PARAM_BUF_LEN] = {0};
    uint8_t            *session_ticket_buf;
    uint8_t            *alpn_buf;
    size_t              alpn_cap;
    unsigned char      *hostname_buf;
    size_t              host_cap;

    /* 初始化 tls 配置 */
    cfg.cert_verify_flag = conn_ssl_config->cert_verify_flag;
    cfg.no_crypto_flag = no_crypto_flag;

    /* 复制 session ticket */
    cfg.session_ticket = xqc_malloc(conn_ssl_config->session_ticket_len + 1);
    if (NULL == cfg.session_ticket) {
        xqc_log(conn->log, XQC_LOG_ERROR, "|malloc for session ticket fail|");
        ret = -XQC_EMALLOC;
        goto end;
    }
    xqc_memcpy(cfg.session_ticket, conn_ssl_config->session_ticket_data,
               conn_ssl_config->session_ticket_len);
    cfg.session_ticket_len = conn_ssl_config->session_ticket_len;

    /* 复制 alpn */
    alpn_cap = strlen(alpn) + 1;
    cfg.alpn = xqc_malloc(alpn_cap);
    if (NULL == cfg.alpn) {
        xqc_log(conn->log, XQC_LOG_ERROR, "|malloc for alpn fail|");
        ret = -XQC_EMALLOC;
        goto end;
    }
    strncpy(cfg.alpn, alpn, alpn_cap);

    /* 复制主机名 hostname */
    host_cap = strlen(hostname) + 1;
    cfg.hostname = xqc_malloc(host_cap);
    if (NULL == cfg.alpn) {
        xqc_log(conn->log, XQC_LOG_ERROR, "|malloc for alpn fail|");
        ret = -XQC_EMALLOC;
        goto end;
    }
    strncpy(cfg.hostname, hostname, host_cap);

    /* 编码本地传输参数,传递给 tls config */
    cfg.trans_params = tp_buf;
    ret = xqc_conn_encode_local_tp(conn, cfg.trans_params,
                                   XQC_MAX_TRANSPORT_PARAM_BUF_LEN, &cfg.trans_params_len);
    if (ret != XQC_OK) {
        xqc_log(conn->log, XQC_LOG_ERROR, "|encode transport parameter error|ret:%d", ret);
        goto end;
    }

    /* 创建 tls 实例 */
    conn->tls = xqc_tls_create(conn->engine->tls_ctx, &cfg, conn->log, conn);
    if (NULL == conn->tls) {
        xqc_log(conn->log, XQC_LOG_ERROR, "|create tls instance error");
        ret = -XQC_EMALLOC;
        goto end;
    }

    /* 开始握手 */
    ret = xqc_tls_init(conn->tls, conn->version, &conn->original_dcid);
    if (ret != XQC_OK) {
        xqc_log(conn->log, XQC_LOG_ERROR, "|init tls error");
        goto end;
    }

end:
    if (cfg.session_ticket) {
        xqc_free(cfg.session_ticket);
    }

    if (cfg.alpn) {
        xqc_free(cfg.alpn);
    }

    if (cfg.hostname) {
        xqc_free(cfg.hostname);
    }

    return ret;
}

四、xquic评价与展望

QUIC 丢掉了 TCP、TLS 的包袱,基于 UDP 来实现,并根据以往的经验加以借鉴、改进,实现了一个安全高效可靠的 HTTP/3 通信协议。凭借着 0 RTT 建立连接、平滑的连接迁移、基本消除了队头阻塞、改进的拥塞控制和流量控制等优秀的特性,QUIC 在绝大多数场景下获得了比 HTTP/2 更好的效果。同时,XQUIC完成了用户态的传输层实现,对各类场景进行精细优化,而非单一针对弱网。

随着IETF工作组的不断努力和推进,QUIC标准化进程不断加快,QUIC协议未来可期。

参考资料

https://github.com/alibaba/xquic

https://www.jianshu.com/p/d58da09e43f8

https://www.cnblogs.com/zhangxu1998cn/p/14342545.html

https://zhuanlan.zhihu.com/p/143464334

https://www.jianshu.com/p/dd9719c4c2c1

https://docs.google.com/document/d/1WJvyZflAO2pq77yOLbp9NsGjC1CHetAXV8I0fQe-B_U/edit

作者:NP 119

...全文
3355 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
内容概要:本文介绍了QUICQuick UDP Internet Connection)协议的发展现状及其应用情况。QUIC是由Google在2013年提出并由IETF标准化的一种基于UDP的传输层协议,旨在解决TCP的局限性,如高延迟、头部阻塞等问题。文章详细阐述了QUIC的特点与优势,包括低延迟(0-RTT)、多路复用、更好的丢包恢复机制、连接迁移等,并指出其目前面临的挑战,如性能开销、ISP对UDP的限制以及缺乏统一的开发工具。此外,文中还列举了多个中国企业在实际生产环境中部署QUIC的成功案例,如阿里巴巴的XQUIC、腾讯的TQUIC等,并探讨了英特尔为推动QUIC普及所提出的端到端解决方案。 适合人群:对网络协议感兴趣的技术人员,尤其是从事互联网产品研发、运维或网络安全领域的专业人士。 使用场景及目标:①了解QUIC协议的基本概念和发展历程;②掌握QUIC相较于传统TCP协议的优势和应用场景;③学习国内外企业在实际项目中如何应用QUIC来提升用户体验;④探索英特尔提供的优化方案和技术支持,帮助企业更好地集成QUIC。 其他说明:随着视频类应用和移动互联网流量的增长,QUIC的应用范围正在不断扩大。对于希望提高网络传输效率和服务质量的企业来说,研究和采用QUIC具有重要意义。同时,由于QUIC的实现较为分散,企业需要关注相关开源项目的进展,并考虑选择合适的硬件加速技术以应对性能挑战。

571

社区成员

发帖
与我相关
我的任务
社区描述
软件工程教学新范式,强化专项技能训练+基于项目的学习PBL。Git仓库:https://gitee.com/mengning997/se
软件工程 高校
社区管理员
  • 码农孟宁
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧