十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenSSL QUIC 连接 ID 缓存设计解析:从 RFC 9000 到 QUIC_CONN_ID 路由表实现

OpenSSL QUIC 连接 ID 缓存设计解析:从 RFC 9000 到 QUIC_CONN_ID 路由表实现 OpenSSL QUIC 连接 ID 缓存设计解析从 RFC 9000 到 QUIC_CONN_ID 路由表实现【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl导读本文基于 OpenSSL 仓库中的设计文档 doc/designs/quic-design/connection-id-cache.md深入剖析 OpenSSL 3.x 中 QUIC 连接 IDConnection ID缓存与路由表的完整设计包括本地Local与远端Remote连接 ID 的职责划分、双端队列与哈希字典的数据结构选型、按序列号退休Retire与删除Remove的语义以及配套的 C API 设计。读完本文你将理解 QUIC 连接迁移与多路复用场景下连接 ID 如何被生成、缓存、查询和退休并能对照仓库中的实际源码如 quic_rcidm.c、quic_lcidm.c验证这些设计如何落地。连接 ID 缓存的整体职责在 QUIC 协议中连接 ID 是连接的唯一寻址标识短包头Short Header报文不携带长度字段因此连接 ID 必须定长连接迁移Connection Migration与多路径场景下同一连接可以同时拥有多个连接 ID。OpenSSL 的 QUIC 实现位于 ssl/quic 目录将连接 ID 管理拆成两个方向远端连接 IDRemote Connection ID由对端签发、我方在发送数据包时使用的连接 ID本地连接 IDLocal Connection ID由我方本栈签发、对端在发送数据包时使用的连接 ID。设计文档明确指出缓存的核心管理能力远端侧按连接添加新 ID、从可用未退休ID 中挑选一个、按序列号选中一个 ID 并退休该 ID 及其所有更旧 ID本地侧生成新 ID 并与连接关联、查询某个 ID 是否存在、服务端将 ID 映射回QUIC_CONNECTION、删除某连接关联的全部 ID、按序列号退休/删除该 ID 及更旧 ID。这一对称设计直接对应 RFC 9000 中NEW_CONNECTION_ID与RETIRE_CONNECTION_ID两种帧的语义见 quic-connID-retire.md。远端连接 ID双端队列与取最新可用数据结构选型设计文档规定远端连接 ID 缓存将作为QUIC_CONNECTION对象的一部分以双端队列double ended queue实现按序列号sequence number排序并维护当前缓存的连接 ID 总数。之所以不需要全局映射是因为远端 ID 只在发送数据包时使用接收时不会用到——这与本地 ID 的定位形成鲜明对比。MVP最小可行产品阶段要求多对一匹配一个连接对象可以关联多个连接 IDRFC 9000 5.1 节的要求而在挑选非退休连接 ID 时策略为选取最年轻序列号最大的可用 ID。源码中的实现演进优先级队列与状态机文档所描述的按序列号排序的双端队列思想在实现中演化为一个更精细的结构QUIC_RCIDMRemote Connection ID Managerquic_rcidm.c。其注释揭示了关键设计动机由于网络中的报文乱序NEW_CONNECTION_ID帧可能乱序到达。若对端按 12、10、11 的顺序发送序列号我们仍必须先消费序列号 10 的 ID再使用 11、12。因此实现选用**优先级队列priority queue**保证按序列号有序消费并定义了不可逆的状态机(add) → PENDING →(select)→ CURRENT →(retire)→ RETIRING →(pop)→ (fin)PENDING已入队但尚未被选中使用的 IDCURRENT当前正被使用的首选 ID同一时刻至多一个RETIRING已发出退休、等待对端确认后移除的 ID。同时代码区分了三种带序列号的 RCID 类型来自对端 INITIAL 包的 CID序列号 0、来自preferred_address传输参数的 CID序列号 1、来自NEW_CONNECTION_ID帧的 CID而 Initial ODCID 与 Retry ODCID 属于无序列号的 RCID作为单例单独跟踪。RCID 的切换策略在 quic_rcidm.c 中以PACKETS_PER_RCID每 10000 个数据包切换一次 RCID体现用于反连接指纹追踪anti-connection fingerprinting。本地连接 ID全局路由表与随机定长 ID定长随机 ID 的设计约束设计文档强调本栈签发的所有连接 ID 必须等长因为短包头包含连接 ID 但不含长度字节ID 内容为随机数不编码任何额外信息。这与 include/internal/quic_types.h 中的定义一致#define QUIC_MAX_CONN_ID_LEN 20 #define QUIC_MIN_ODCID_LEN 8 /* RFC 9000 s. 7.2 */ typedef struct quic_conn_id_st { unsigned char id_len, id[QUIC_MAX_CONN_ID_LEN]; } QUIC_CONN_ID;头文件还提供了ossl_quic_gen_rand_conn_id()用于按指定长度生成随机连接 IDlibctx 可为 NULL以及内联比较函数ossl_quic_conn_id_eq()。文档原稿中的 API 草案还预留了 stateless reset token 字段每个连接 ID 一个 16 字节 tokenQUIC_STATELESS_RESET_TOKEN_LEN注释表明后续很可能需要但这里可能不是理想位置。全局路由表 QUIC_ROUTE_TABLE与远端缓存不同本地连接 ID 采用一个全局集合QUIC_ROUTE_TABLE由所有连接在所有SSL_CTX对象间共享以**字典dictionary**形式实现用途是将到达的数据报分派dispatch到正确的连接。设计文档专门注明MVP 阶段只要求本地缓存含一个元素、无需实现真正的查找服务端的连接 ID → QUIC_CONNECTION映射在 MVP 中也不强制。该全局哈希表的落点可以从 quic_lcidm.c 中看到——实现围绕哈希表展开并特别处理了删除引起的哈希表收缩contraction问题而数据报分派层 quic_demux.c 则封装了通用的DGRAM_DEMUX通过短连接 ID 长度short_conn_id_len与默认回调把数据包导向对应连接正是路由表dispatch incoming datagrams职责的运行时体现。Routes一条路由 一对连接 ID 公共序列号设计文档将路由定义为连接两端各一个连接 ID 构成的二元组同时携带两端的公共序列号QUIC_ROUTE结构包含conn所属连接、local本地 ID、remote远端 ID、seq_no两端共用的序列号与retired标志位结构体预留了BIO_ADDR remote_address远端源地址字段注释标明后续将需要——这正是文档Post MVP从host address, source port对接收这一设想在结构上的伏笔文档推测本地与远端连接 ID 的操作很可能都可以被路由功能涵盖subsume。路由表 API 提供了两类查询按本地 ID 查询ossl_route_table_get0_route_from_local与按远端 ID 查询ossl_route_table_get0_route_from_remote以及按连接序列号批量退休/删除的ossl_quic_route_table_retire/ossl_quic_route_table_remove。ID 退休retired与remove两种状态的区分退休流程与两阶段语义连接 ID 通过NEW_CONNECTION_ID或RETIRE_CONNECTION_ID帧退休并以相反类型的帧作为确认retirement 由 RETIRE_CONNECTION_ID 确认反之亦然。文档定义了关键的两阶段状态语义场景我方动作ID 状态收到退休帧立即从缓存**移除remove**覆盖的 ID随后回送退休确认直接删除我方主动退休发送退休帧先将覆盖的 ID 标记为退休retired待对端确认后再移除不能用于发送但仍可接收也就是说retired 禁止发送但允许接收remove 彻底删除。这一语义保证了退休过程中的报文不丢失——quic-connID-retire.md中进一步说明我方已退休的路由在未收到对端确认前需要持续跟踪对端已退休的路由无需跟踪、可立即删除对于退休时仍有在途数据的路由数据会在退休确认发送前发出若需重传则改用新 CID 走新路由因此无需等待数据冲刷完毕再发送退休确认。乱序报文的处理策略文档明确提出一个边角问题退休通知之后仍可能收到乱序数据包。设计取舍是直接丢弃dropping them seems reasonable备选方案是让路由保持可删除deletable状态直到退休时刻所有在途包都被确认。这一取舍在 MVP 语境下选择了简单性。缓存 API 全景设计文档为三类缓存给出了完整 C API 草案现按文档原文整理如下。连接 ID 基础 API/* QUIC connection ID representation. */ #define QUIC_MAX_CONN_ID_LEN 20 typedef struct quic_conn_id_st { unsigned char id_len; unsigned char id[QUIC_MAX_CONN_ID_LEN]; /* likely required later: unsigned char reset_token[16]; */ } QUIC_CONN_ID; static ossl_unused ossl_inline int ossl_quic_conn_id_eq(const QUIC_CONN_ID *a, const QUIC_CONN_ID *b); /* New functions */ int ossl_quic_conn_id_set(QUIC_CONN_ID *cid, unsigned char *id, unsigned int id_len); int ossl_quic_conn_id_generate(QUIC_CONN_ID *cid);远端连接 ID 缓存 APItypedef struct quic_remote_conn_id_cache_st QUIC_REMOTE_CONN_ID_CACHE; QUIC_REMOTE_CONN_ID_CACHE *ossl_quic_remote_conn_id_cache_new( size_t id_limit /* [active_connection_id_limit] */ ); void ossl_quic_remote_conn_id_cache_free(QUIC_REMOTE_CONN_ID_CACHE *cache); int ossl_quic_remote_conn_id_cache_add(QUIC_REMOTE_CONN_ID_CACHE *cache, const QUIC_CONNECTION *conn, const unsigned char *conn_id, size_t conn_id_len, uint64_t seq_no); /* 返回该连接当前可用 ID 之一选择方式未指定 */ int ossl_quic_remote_conn_id_cache_get0_conn_id( const QUIC_REMOTE_CONN_ID_CACHE *cache, const QUIC_CONNECTION *conn, QUIC_CONN_ID **cid); /* 退休retire序列号 seq_no 的所有远端 ID */ int ossl_quic_remote_conn_id_cache_retire( QUIC_REMOTE_CONN_ID_CACHE *cache, uint64_t seq_no); /* 删除remove序列号 seq_no 的所有远端 ID */ int ossl_quic_remote_conn_id_cache_remove( QUIC_REMOTE_CONN_ID_CACHE *cache, uint64_t seq_no);注意id_limit参数对应 RFC 9000 的active_connection_id_limit传输参数quic_types.h 中QUIC_MIN_ACTIVE_CONN_ID_LIMIT为 2即 MVP 每端至少维持两个可用 ID。本地连接 ID 缓存 APItypedef struct quic_local_conn_id_cache_st QUIC_LOCAL_CONN_ID_CACHE; QUIC_LOCAL_CONN_ID_CACHE *ossl_quic_local_conn_id_cache_new(void); void ossl_quic_local_conn_id_cache_free(QUIC_LOCAL_CONN_ID_CACHE *cache); /* 生成新随机本地 ID 并与连接关联MVP 可为零长度 ID */ int ossl_quic_local_conn_id_cache_new_conn_id(QUIC_LOCAL_CONN_ID_CACHE *cache, QUIC_CONNECTION *conn, QUIC_CONN_ID **cid); /* 移除一个连接及其全部关联 ID */ int ossl_quic_local_conn_id_cache_remove_conn(QUIC_LOCAL_CONN_ID_CACHE *cache, const QUIC_CONNECTION *conn); /* 按 ID 查找连接未命中返回 NULL */ QUIC_CONNECTION *ossl_quic_local_conn_id_cache_get0_conn( const QUIC_LOCAL_CONN_ID_CACHE *cache, const unsigned char *conn_id, size_t conn_id_len); int ossl_quic_local_conn_id_cache_retire( QUIC_LOCAL_CONN_ID_CACHE *cache, uint64_t from_seq_no); int ossl_quic_local_conn_id_cache_remove( QUIC_LOCAL_CONN_ID_CACHE *cache, uint64_t from_seq_no);路由表 APItypedef struct quic_route_st QUIC_ROUTE; typedef struct quic_route_table QUIC_ROUTE_TABLE; struct quic_route_st { QUIC_CONNECTION *conn; QUIC_CONN_ID local; QUIC_CONN_ID remote; uint64_t seq_no; /* Sequence number for both ends */ unsigned int retired : 1; /* Connection ID has been retired */ /* Later will require: BIO_ADDR remote_address; */ }; QUIC_ROUTE_TABLE *ossl_quic_route_table_new(void); void ossl_quic_route_table_free(QUIC_ROUTE_TABLE *routes); int ossl_route_table_add_route(QUIC_ROUTE_TABLE *cache, QUIC_ROUTE_TABLE *route); /* 按本地或远端 ID 查询路由表项 */ QUIC_ROUTE *ossl_route_table_get0_route_from_local( const QUIC_ROUTE_TABLE *cache, const unsigned char *conn_id, size_t conn_id_len); QUIC_ROUTE *ossl_route_table_get0_route_from_remote( const QUIC_ROUTE_TABLE *cache, const unsigned char *conn_id, size_t conn_id_len); /* 按序列号退休/删除含该序列号及更旧者 */ int ossl_quic_route_table_retire(QUIC_ROUTE_TABLE *routes, QUIC_CONNECTION *conn, uint64_t seq_no); int ossl_quic_route_table_remove(QUIC_ROUTE_TABLE *routes, QUIC_CONNECTION *conn, uint64_t seq_no);MVP 边界与后续演进路线设计文档在 Notes 一节明确划定了 MVP 的能力边界这些约束在后来的实现中逐步被打破零长度连接 IDMVP 仅需零长度连接 ID 即可工作quic-connID-retire.md指出 MVP 未签发多个连接 ID创建新 CID 的代码已编写但未使用完整支持需要补充回应对端的新 CID、管理呈现给对端的 ID 数量、限制签发与退休数量、退休不再使用的 CID、并保证同一时刻只有一个 RETIRE_CONNECTION_ID 帧在途映射可不实现MVP 阶段无需连接 ID → QUIC_CONNECTION映射本地缓存仅需一个元素无需真正查找——但后续全局路由表与哈希表查找正是为此铺路单 socket 瓶颈文档预判 Post MVP 阶段所有收包集中于单个 socket 会成为瓶颈替代方案是改为从host address, source port对接收这也解释了QUIC_ROUTE中预留的remote_address字段连接迁移迁移的支持远超连接 ID 管理本身文档建议在真正支持迁移时再以即时just in time的方式补充 CID 代码。小结从设计文档到运行时源码OpenSSL 的 QUIC 连接 ID 缓存呈现出清晰的层次远端侧用按序列号有序的队列实现为优先级队列支撑发送侧的 ID 轮换与乱序容忍本地侧用全局哈希路由表支撑接收侧的数据报分派两者之上再以**路由route**统一本地 ID 远端 ID 公共序列号的视图。退休语义上retired禁发可收→ remove彻底删除的两阶段模型保证了与对端帧交互时的数据安全。对于希望深入 QUIC 实现的读者建议依次阅读 connection-id-cache.md本文源头、quic-connID-retire.md退休细节、quic_rcidm.c远端 ID 管理器、quic_lcidm.c本地 ID 管理器与 quic_demux.c数据报分派即可完整拼出连接 ID 在 OpenSSL QUIC 实现中的全生命周期。【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表