本章导读
TLS 的对象栈有两层:会话层(xtlssession——裸协议机,输入密文块、输出密文块,传输完全由调用方决定)与 Stream 层(第 87 章——把会话机挂上 TCP 流)。本章讲会话层的服务端侧:xtlsserverconfig 配置身份与 ALPN,xrtTlsServerCreate 建会话,Feed* 喂收到的密文、xrtTlsServerDrive 推进状态机、公共 Send 队列取待发航班——传输是什么(内存桥、TCP、串口、自定义 IoT 链路)协议机毫不关心。服务端特有的三件事也在本章:SNI 动态选择(Select 回调按域名换身份与协议)、票据签发(xrtTlsServerTicket)与恢复接受(Resume 回调,PSK+DHE)。本章示例用"内存搬运"当传输,服务端与真实客户端在进程内对打——这也是协议测试的标准姿势。
86.1引入
什么时候需要裸协议机而不是 Stream 层?三种真实场景。其一:协议测试——你想在内存里同时驱动客户端与服务端、逐字节检查航班,不碰网络。其二:自定义传输——IoT 串口、蓝牙 BLE、甚至消息队列上跑 TLS,传输字节从哪来由你决定。其三:极致控制——网关要把密文按自定义调度转发,Stream 层的事件模型不合身。
会话层的服务端比客户端多一个"等待"维度:客户端创建即发首航,服务端创建后等待 ClientHello——收到什么 SNI、什么 ALPN,可能决定用哪个身份应答(一台服务器服务多个域名的标准场景)。这个"看菜下单"的能力由 Select 回调承载;不需要路由就给静态 Identity,二者必须有一个——保证任何客户端(包括恢复回退的)都有明确认证路径。
86.2概念
会话驱动的三步循环
三步循环对客户端与服务端完全同构(客户端用 xrtTlsClientDrive)——掌握一侧另一侧免费。喂入四种形态对应四种所有权(借用/接管/引用/缓冲链——第 65 章的四类追加在这里重演);Send 队列的 Size/Front/Consume 消费三件与第 65 章缓冲链同一套词汇。TLS 1.3 完整证书航班在 Drive 中依次生成 ServerHello、EncryptedExtensions、Certificate、CertificateVerify、Finished(第 84 章时序),验证客户端 Finished 后原子切换到应用 epoch 与 READY。
服务端配置与首航 Arena
xtlsserverconfig 创建期间借用上下文与静态身份,成功后会话持引用并深复制 ALPN 列表。RequireProtocol = true 时没有共同 ALPN 明确失败——协议歧义在握手期消灭。首航的扩展表、证书条目、签名输入与临时编码流来自会话内惰性临时 Arena:空闲连接与未握手会话不分配 Arena 块,首航完成立即安全清零释放——不为每个连接保留固定握手缓冲,1.2/1.3 复用同一 Arena 与清理路径。内存形态对高连接数服务端是关键契约。
SNI 动态选择:Select 回调
Select 在 ClientHello 严格解析、SNI/ALPN 提取之后、任何服务端输出生成之前执行。回调收到请求中的 SNI 与完整 ALPN 负载(仅回调期间借用),xtlsserverchoice 初始含静态身份、按服务端偏好算好的协议下标与零值 Cookie;回调可替换身份或协议,并写入一个 XRT 不解释的 64 位宿主路由标识。xrtTlsServerCookie 在成功选择后返回该标识——传输适配层用它把已握手连接关联回"选择身份时的配置代"(比如选择时查的租户配置版本)。约束:上下文只借用到首航完成;不能递归驱动同一会话。xrtTlsServerName 返回深复制的 SNI(视图稳定到会话销毁)、xrtTlsSessionProtocol 返回最终 ALPN。
票据签发与恢复接受
启用 XRT_FEATURE_TLS_SERVER_RESUME 后服务端两侧接入恢复:
- 签发:READY 后
xrtTlsServerTicket(会话, 不透明ticket, 寿命, &恢复对象)把调用方给的 ticket 编码成 NewSessionTicket 发送,并把对应服务端恢复对象的所有权交给调用方;TicketNew便捷入口用 32 字节安全随机 ticket 与 86400 秒默认寿命。XRT 不维护进程全局票据缓存——按租户/容量/过期/持久化自选 Map(第 18 章)、分片缓存或外部存储。发送硬上限在随机数与派生前预检;XTLS_AGAIN时输出对象为NULL、写序号与恢复状态不变——排空后原样重试。 - 接受:
Resume回调收到 SNI、ALPN、不透明 ticket 与混淆年龄,返回借用的不可变xtlsresume(第 88 章的对象契约);会话加引用后校验版本/套件/SNI/ALPN/有效期/年龄容差。未找到票据或路由不匹配安全回退完整握手;但"票据元数据匹配而 binder 错误"是认证失败——必须 fatal Alert,不能降级绕过(攻击者拿不到 PSK 就该被拒)。恢复始终保留 ECDHE(PSK+DHE,前向保密不降级),不支持纯 PSK 与 0-RTT。
TLS 1.2 路径与 KeyUpdate
服务端 1.2 路径要求 EMS,执行 ECDHE 证书完整握手、在 ChangeCipherSpec 边界原子切换 epoch;无恢复、无重新协商、无 KeyUpdate(1.2 的历史决定)。1.3 的主动/被动 KeyUpdate 都遵循"旧 epoch 完整排队、新 epoch 一次提交"——发送背压与分配失败不产生半更新态。
86.3示例
第一个完整程序:传输无关的服务端对打
下面的程序来自 examples/tls/server/main.c——服务端与真实客户端在内存桥上完成握手与双向数据:
#include "../common.h" /* 范例验证器只接管信任决策,协议签名和 Finished 仍由 XRT 验证。 */ static xtlsverifydecision exampleTlsAccept( const xtlspeer* pPeer, ptr pContext ) { (void)pContext; return ((pPeer != NULL) && (pPeer->Role == XTLS_SERVER) && (pPeer->CertificateCount != 0)) ? XTLS_VERIFY_ACCEPT : XTLS_VERIFY_REJECT; } /* 把一个会话的全部待发密文复制到对端并精确消费。 */ static bool exampleTlsMove(xtlssession* pSource, xtlssession* pTarget) { xnetspan Span; while ( xrtTlsSessionSendSize(pSource) != 0 ) { if ( !xrtTlsSessionSendFront(pSource, &Span) || (Span.Size == 0) || (xrtTlsSessionFeed( pTarget, Span.Data, Span.Size ) != XTLS_OK) || !xrtTlsSessionSendConsume( pSource, Span.Size ) ) { return false; } } return true; } /* 驱动双方并搬运密文,直到会话同时进入 READY。 */ static bool exampleTlsHandshake( xtlssession* pClient, xtlssession* pServer ) { for ( size_t i = 0; i < 4096u; i++ ) { xtlsresult ClientResult = xrtTlsClientDrive(pClient); xtlsresult ServerResult = xrtTlsServerDrive(pServer); if ( ((ClientResult != XTLS_OK) && (ClientResult != XTLS_AGAIN)) || ((ServerResult != XTLS_OK) && (ServerResult != XTLS_AGAIN)) || !exampleTlsMove(pClient, pServer) || !exampleTlsMove(pServer, pClient) ) { return false; } if ( (xrtTlsSessionState(pClient) == XTLS_STATE_READY) && (xrtTlsSessionState(pServer) == XTLS_STATE_READY) ) { return true; } } return false; } /* 发送一段应用数据,并在对端驱动后完整读取。 */ static bool exampleTlsTransfer( xtlssession* pSource, xtlssession* pTarget, bool bTargetServer, cstr sText ) { char Output[64]; size_t iSize = strlen(sText); size_t iWritten = 0; size_t iRead = 0; xtlsresult Result; if ( (iSize > sizeof(Output)) || (xrtTlsSessionWrite( pSource, sText, iSize, &iWritten ) != XTLS_OK) || (iWritten != iSize) || !exampleTlsMove(pSource, pTarget) ) { return false; } Result = bTargetServer ? xrtTlsServerDrive(pTarget) : xrtTlsClientDrive(pTarget); if ( (Result != XTLS_OK) && (Result != XTLS_AGAIN) ) { return false; } return (xrtTlsSessionRead( pTarget, Output, sizeof(Output), &iRead ) == XTLS_OK) && (iRead == iSize) && (memcmp(Output, sText, iSize) == 0); } /* 打印当前线程保存的结构化根错误。 */ static void exampleTlsError(void) { const xerror* pError = xrtGetError(); if ( pError != NULL ) { fprintf( stderr, "%s: %s\n", xrtErrorOperation(pError), xrtErrorMessage(pError) ); } } */ /* 用真实客户端演示传输无关 TLS 1.3 服务端握手与双向数据。 */ int main(int argc, char** argv) { static const xstrview Protocols[] = { XRT_STR_INIT("h2"), XRT_STR_INIT("http/1.1") }; uint8* pCertificateData = NULL; uint8* pPrivateData = NULL; size_t iCertificateSize = 0; size_t iPrivateSize = 0; xbytesview Certificate; xtlsidentity* pIdentity = NULL; xtlsverifierconfig VerifierConfig; xtlsverifier* pVerifier = NULL; xtlsclientconfig ClientConfig; xtlsserverconfig ServerConfig; xtlssession* pClient = NULL; xtlssession* pServer = NULL; xbytesview Protocol; int iResult = 1; if ( argc != 4 ) { printf( "usage: server <rsa|p256|p384|ed25519> " "<certificate.der> <private.der>\n" ); return 0; } if ( !exampleTlsReadFile( argv[2], &pCertificateData, &iCertificateSize ) || !exampleTlsReadFile( argv[3], &pPrivateData, &iPrivateSize ) ) { fprintf(stderr, "failed to read DER identity\n"); goto Cleanup; } Certificate = (xbytesview) { pCertificateData, iCertificateSize }; pIdentity = exampleTlsIdentity( argv[1], &Certificate, 1u, (xbytesview) { pPrivateData, iPrivateSize } ); if ( pIdentity == NULL ) { exampleTlsError(); goto Cleanup; } xrtTlsVerifierConfigInit(&VerifierConfig); VerifierConfig.Verify = exampleTlsAccept; pVerifier = xrtTlsVerifierCreate(&VerifierConfig); if ( pVerifier == NULL ) { exampleTlsError(); goto Cleanup; } xrtTlsClientConfigInit(&ClientConfig); ClientConfig.ServerName = XRT_STR_LITERAL("example.com"); ClientConfig.Protocols = Protocols; ClientConfig.ProtocolCount = sizeof(Protocols) / sizeof(Protocols[0]); ClientConfig.Verifier = pVerifier; xrtTlsServerConfigInit(&ServerConfig); ServerConfig.Identity = pIdentity; ServerConfig.Protocols = Protocols; ServerConfig.ProtocolCount = sizeof(Protocols) / sizeof(Protocols[0]); ServerConfig.RequireProtocol = true; pClient = xrtTlsClientCreate(&ClientConfig, NULL); pServer = xrtTlsServerCreate(&ServerConfig, NULL); if ( (pClient == NULL) || (pServer == NULL) || !exampleTlsHandshake(pClient, pServer) || !exampleTlsTransfer(pClient, pServer, true, "ping") || !exampleTlsTransfer(pServer, pClient, false, "pong") || !xrtTlsSessionProtocol(pServer, &Protocol) ) { exampleTlsError(); goto Cleanup; } printf( "ready, protocol=%.*s, client=ping, server=pong\n", (int)Protocol.Size, (const char*)Protocol.Data ); iResult = 0; Cleanup: xrtTlsSessionDestroy(pServer); xrtTlsSessionDestroy(pClient); xrtTlsVerifierRelease(pVerifier); xrtTlsIdentityRelease(pIdentity); free(pPrivateData); free(pCertificateData); return iResult; }
刚才发生了什么。 ① 身份与验证器装配:DER 文件读入后 exampleTlsIdentity 构造身份(第 83 章的启动期验证);验证器用最小 Verify 回调(对打场景信任自己)。② 双会话创建:ClientCreate 与 ServerCreate 各拿一份配置——服务端配 Identity + Protocols + RequireProtocol;两边会话都不碰任何 socket。③ 内存桥握手:exampleTlsHandshake 是会话层驱动循环的范本——ClientDrive + ServerDrive 交替推进,exampleTlsMove 把一边 Send 队列的密文逐 Span Feed 给对端——"传输"就是这两个循环之间的搬运。接真实网络时把 Move 换成 socket 读写,其余不变。④ 双向数据:exampleTlsTransfer 演示应用明文的 SessionWrite/Read(同样经 Drive 与 Move 的循环);最后 SessionProtocol 查询协商出的 ALPN——RequireProtocol 保证它非空。⑤ 清理段两会话 Destroy → 验证器与身份 Release → 文件缓冲 free——会话层对象全是"Destroy/Release 一次"的简单生命周期。
第二个完整程序:会话层全特性巡检
第二个程序来自 examples/tls/session_tour/main.c——公共会话 API 的六行自检:
*/ #include <stdio.h> #include <string.h> #include <xrt.h> #include "../embedded_identity.h" /* FeedRef 的释放回调:记录次数。 */ static void exampleRelease(ptr pContext, cbytes pData, size_t iSize) { int* pCount = (int*)pContext; (void)pData; (void)iSize; ++(*pCount); } /* 验证器回调:只接管信任决策,协议签名仍由 XRT 验证。 */ static xtlsverifydecision exampleAccept( const xtlspeer* pPeer, ptr pContext ) { (void)pContext; return ((pPeer != NULL) && (pPeer->CertificateCount != 0u)) ? XTLS_VERIFY_ACCEPT : XTLS_VERIFY_REJECT; } /* 用 SendSpans 聚集全部待发密文,按指定形态喂给对端。 */ typedef enum examplefeed { EXAMPLE_FEED_BORROW, EXAMPLE_FEED_TAKE, EXAMPLE_FEED_REF, EXAMPLE_FEED_BUFFER } examplefeed; /* 按角色驱动会话推进。 */ static xtlsresult exampleDrive(xtlssession* pSession) { return (xrtTlsSessionRole(pSession) == XTLS_SERVER) ? xrtTlsServerDrive(pSession) : xrtTlsClientDrive(pSession); } static bool exampleMove( xtlssession* pSource, xtlssession* pTarget, examplefeed Kind, int* pReleased ) { xnetspan Spans[8]; xnetbuf Chain; * 顺序固定为:喂入 → 驱动对端 → 消费源 Span。 */ while ( xrtTlsSessionSendSize(pSource) != 0u ) { xtlsresult Result; if ( (xrtTlsSessionSendSpans(pSource, Spans, 8u) == 0u) || (Spans[0].Size == 0u) ) { return false; } switch ( Kind ) { case EXAMPLE_FEED_BORROW: Result = xrtTlsSessionFeedBorrow(pTarget, Spans[0].Data, Spans[0].Size); break; case EXAMPLE_FEED_TAKE: { ptr pCopy = xrtMalloc(Spans[0].Size); if ( pCopy == NULL ) { return false; } memcpy(pCopy, Spans[0].Data, Spans[0].Size); Result = xrtTlsSessionFeedTake(pTarget, pCopy, Spans[0].Size); break; } case EXAMPLE_FEED_REF: Result = xrtTlsSessionFeedRef(pTarget, Spans[0].Data, Spans[0].Size, exampleRelease, (ptr)pReleased); break; case EXAMPLE_FEED_BUFFER: default: if ( !xrtNetBufInit(&Chain, NULL) || !xrtNetBufAppend(&Chain, Spans[0].Data, Spans[0].Size) ) { return false; } Result = xrtTlsSessionFeedBuffer(pTarget, &Chain); break; } if ( Result != XTLS_OK ) { return false; } Result = exampleDrive(pTarget); if ( (Result != XTLS_OK) && (Result != XTLS_AGAIN) ) { return false; } if ( !xrtTlsSessionSendConsume(pSource, Spans[0].Size) ) { return false; } } return true; } /* 双向驱动 + 搬运,直到双方 READY。 */ static bool exampleHandshake( xtlssession* pClient, xtlssession* pServer, int* pReleased ) { for ( size_t i = 0; i < 4096u; i++ ) { xtlsresult C = xrtTlsClientDrive(pClient); xtlsresult S = xrtTlsServerDrive(pServer); if ( ((C != XTLS_OK) && (C != XTLS_AGAIN)) || ((S != XTLS_OK) && (S != XTLS_AGAIN)) || !exampleMove(pClient, pServer, EXAMPLE_FEED_BORROW, pReleased) || !exampleMove(pServer, pClient, EXAMPLE_FEED_BORROW, pReleased) ) { return false; } /* exampleMove 内部已驱动对端;此处再补一轮源侧驱动。 */ if ( (xrtTlsSessionState(pClient) == XTLS_STATE_READY) && (xrtTlsSessionState(pServer) == XTLS_STATE_READY) ) { return true; } } return false; } /* 单向:写应用数据 → 搬运 → 驱动对端。 */ static bool exampleSend( xtlssession* pSource, xtlssession* pTarget, bool bTargetServer, cstr sText, examplefeed Kind, int* pReleased ) { size_t iWritten = 0; xtlsresult Result; (void)bTargetServer; (void)Result; if ( (xrtTlsSessionWrite(pSource, sText, strlen(sText), &iWritten) != XTLS_OK) || (iWritten != strlen(sText)) || !exampleMove(pSource, pTarget, Kind, pReleased) ) { return false; } return true; } int main(void) { xtlsidentity* pIdentity = NULL; xtlsverifier* pVerifier = NULL; xtlsclientconfig ClientConfig; xtlsserverconfig ServerConfig; xtlssession* pClient = NULL; xtlssession* pServer = NULL; xnetspan Spans[4]; int iReleased = 0; int iResult = 1; /* ---- 建立双会话:嵌入证书 + 接受式验证器。 ---- */ pIdentity = exampleEmbeddedIdentity(); { xtlsverifierconfig VerifierConfig; xrtTlsVerifierConfigInit(&VerifierConfig); VerifierConfig.Verify = exampleAccept; pVerifier = xrtTlsVerifierCreate(&VerifierConfig); } if ( (pIdentity == NULL) || (pVerifier == NULL) ) { goto Cleanup; } xrtTlsClientConfigInit(&ClientConfig); ClientConfig.ServerName = XRT_STR_LITERAL("localhost"); ClientConfig.VerifyName = XRT_STR_LITERAL("localhost"); ClientConfig.Verifier = pVerifier; xrtTlsServerConfigInit(&ServerConfig); ServerConfig.Identity = pIdentity; pClient = xrtTlsClientCreate(&ClientConfig, NULL); pServer = xrtTlsServerCreate(&ServerConfig, NULL); if ( (pClient == NULL) || (pServer == NULL) || !exampleHandshake(pClient, pServer, &iReleased) || (xrtTlsSessionFeedSize(pClient) != 0u) ) { goto Cleanup; } printf("session: handshake via feed-borrow -> both ready ok\n"); /* ---- 会话属性:角色/版本/套件/事件位/上下文。 ---- */ if ( (xrtTlsSessionRole(pClient) != XTLS_CLIENT) || (xrtTlsSessionRole(pServer) != XTLS_SERVER) || (xrtTlsSessionVersion(pClient) != XTLS_VERSION_13) || (xrtTlsSessionCipher(pClient) == 0u) || (xrtTlsSessionWait(pClient) == 0u) || /* 未显式配置 Context 时会话仍挂在共享默认上下文上。 */ (xrtTlsSessionContext(pClient) == NULL) || (xrtTlsSessionContext(pServer) == NULL) ) { goto Cleanup; } printf("session: role/version/cipher/wait/context ok\n"); /* ---- Take/Ref 喂入:接管与释放回调恰好一次。 ---- */ if ( !exampleSend(pClient, pServer, true, "take", EXAMPLE_FEED_TAKE, &iReleased) || (xrtTlsSessionPlainSize(pServer) != 4u) || !exampleSend(pClient, pServer, true, "ref!", EXAMPLE_FEED_REF, &iReleased) ) { goto Cleanup; } { /* 驱动消费 Ref 喂入的数据后释放回调恰好一次。 */ for ( int i = 0; (i < 100) && (iReleased == 0); i++ ) { (void)xrtTlsServerDrive(pServer); xrtSleep(1u); } if ( iReleased != 1 ) { goto Cleanup; } } printf("session: feed-take + feed-ref (release once) ok\n"); /* ---- Buffer 零复制:密文缓冲链整体移交。 ---- */ if ( !exampleSend(pServer, pClient, false, "chain", EXAMPLE_FEED_BUFFER, &iReleased) || (xrtTlsSessionPlainSize(pClient) != 5u) ) { goto Cleanup; } printf("session: feed-buffer zero-copy chain ok\n"); /* ---- 明文队列零复制检查 + 消费。 ---- */ { char arrText[16]; size_t iCount; size_t iSize; /* 发送侧 Span 计数与聚集同族——上一轮 Move 已清空。 */ if ( (xrtTlsSessionSendSpanCount(pClient) != 0u) ) { goto Cleanup; } iSize = xrtTlsSessionPlainSize(pClient); iCount = xrtTlsSessionPlainSpanCount(pClient); if ( (iSize != 5u) || (iCount == 0u) || !xrtTlsSessionPlainFront(pClient, &Spans[0]) || (Spans[0].Size == 0u) || (xrtTlsSessionPlainSpans(pClient, Spans, 4u) != iCount) ) { goto Cleanup; } memcpy(arrText, Spans[0].Data, Spans[0].Size < 5u ? Spans[0].Size : 5u); /* 前缀可能分散在多个 Span——按总长复制拼合。 */ { size_t iOffset = 0; for ( size_t i = 0; (i < iCount) && (iOffset < 5u); i++ ) { size_t n = Spans[i].Size; if ( n > 5u - iOffset ) { n = 5u - iOffset; } memcpy(arrText + iOffset, Spans[i].Data, n); iOffset += n; } if ( (iOffset != 5u) || (memcmp(arrText, "chain", 5u) != 0) || !xrtTlsSessionPlainConsume(pClient, 5u) || (xrtTlsSessionPlainSize(pClient) != 0u) ) { goto Cleanup; } } } printf("session: send spans gathered + plain spans consumed ok\n"); /* ---- 关闭:Close 排队 close_notify,对端驱动后 Eof。 ---- */ if ( xrtTlsSessionClose(pClient) != XTLS_OK ) { goto Cleanup; } if ( !exampleMove(pClient, pServer, EXAMPLE_FEED_BORROW, &iReleased) ) { goto Cleanup; } { xtlsresult Result = xrtTlsServerDrive(pServer); if ( (Result != XTLS_OK) && (Result != XTLS_AGAIN) ) { goto Cleanup; } } { xtlsalertlevel Level; xtlsalert Alert; * 干净关闭后 PeerAlert 为真且代码恰为 close_notify。 */ if ( (xrtTlsSessionEof(pServer) != XTLS_OK) || !xrtTlsSessionPeerAlert(pServer, &Level, &Alert) || (Level != XTLS_ALERT_WARNING) || (Alert != XTLS_ALERT_CLOSE_NOTIFY) ) { goto Cleanup; } } printf("session: close_notify -> peer eof ok\n"); iResult = 0; Cleanup: xrtTlsSessionDestroy(pServer); xrtTlsSessionDestroy(pClient); xrtTlsVerifierRelease(pVerifier); xrtTlsIdentityRelease(pIdentity); return iResult; }
刚才发生了什么。 ① 第 1 行是 server 示例同款的内存桥握手(借用形态 Feed);第 3 行把另外三种喂入形态逐一验证——Take(接管所有权)、Ref(加引用喂、恰好释放一次)、Buffer(第 65 章缓冲链零拷贝直挂)——四种所有权全覆盖。② 第 2 行查询会话公开事实:角色(客户端/服务端同一对象类型)、协商版本与套件、等待原因(输入还是输出——XTLS_AGAIN 的两态分解)、共享上下文。③ 第 5 行验证双向 Span:发送侧 Span 族聚合发送、接收侧明文按 Span 消费——应用层的零拷贝词汇延伸到会话边界。④ 第 6 行走认证关闭:close_notify 发出、对端收到 EOF——第 82 章的关闭协议在会话层的样子。六行合起来就是"会话层这一章的全部 API 在跑"——把它当本章的可执行速查表。
86.4契约
- 身份强制:静态
Identity或同步Select至少一个——未知 SNI 与恢复回退仍有明确认证路径。 - 驱动同构:Feed 四形态(借用/Take/Ref/Buffer 零拷贝)+ Drive + Send 队列,客户端服务端共享同一套公共会话 API。
- 原子切换:1.3 在客户端 Finished 验证后原子切应用 epoch 与 READY;1.2 在 CCS 边界原子切换;失败不发布半状态。
- Arena 惰性:首航临时内存按需分配、用后安全清零释放;空闲连接零握手缓冲。
- Select 时机:ClientHello 严格解析后、任何输出前;SNI/ALPN 仅回调期间借用;Cookie 为 64 位宿主路由标识(XRT 不解释);上下文借到首航完成;禁止递归驱动。
- 票据签发:对象所有权转移给调用方;无全局缓存;AGAIN 时状态不变可重试;硬上限预检。
- 恢复接受:未找到/路由不匹配回退完整握手;binder 错误是认证失败(fatal,不降级);始终 PSK+DHE;无纯 PSK/0-RTT。
- 1.2 边界:EMS 必须;无恢复/重协商/KeyUpdate。
- KeyUpdate:旧 epoch 完整排队、新 epoch 一次提交;背压与 OOM 无半更新。
- 未实现:客户端证书认证、0-RTT、异步身份选择;TCP 组合入口属第 87 章 Stream 层。
常见错误
症状:Select 回调里顺手调 xrtTlsServerDrive 想"提前推进"——状态错乱或断言失败。
原因:Select 运行在 Drive 内部(首航生成前);此时递归 Drive 同一会话等于重入状态机。回调的职责只是"选身份、选协议、写 Cookie",推进留给外层循环。
static void onSelect(xtlsserverchoice* pChoice, ...) { pick_identity(pChoice); xrtTlsServerDrive(pSession); /* 重入状态机:未定义行为 */ }
static void onSelect(xtlsserverchoice* pChoice, ...) { pick_identity(pChoice); /* 只做选择 */ pChoice->Cookie = tenant_generation(); /* 路由标识 */ /* 推进由外层 feed/drive/send 循环完成 */ }
症状:为了"高可用",Resume 回调查不到票据返回回退、binder 校验失败也回退完整握手——攻击者伪造票据元数据即可绕过 PSK 认证探测。
原因:两类失败的安全语义完全不同。未找到/路由不匹配:客户端可能持旧票据,正常业务路径,回退合理。binder 错误:客户端声称持有某票据的 PSK 却证明不了——要么票据被盗要么就是攻击,必须 fatal Alert。
static const xtlsresume* onResume(...) { const xtlsresume* pFound = cache_lookup(Ticket); if ( (pFound == NULL) || !quick_check(Ticket) ) { return NULL; /* 一律回退:binder 错误也被吞掉 */ } return pFound; }
你要做的是不要在回调里伪造"找到" */症状:万级空闲连接的服务端内存暴涨——每连接几百 KB 的"预分配握手区"乘以连接数。
原因:误解了会话层的内存模型。首航临时内存是惰性 Arena:未握手不分配、握手完立即清零释放;需要长期驻留的只有会话状态本身。
/* 自行给每个连接 malloc 一块"握手工作区"挂着 */ struct conn { uint8 HandshakeArena[262144]; ... }; /* 全部白占 */
闲置连接近零握手内存,容量规划只看活跃握手数 */ pSession = xrtTlsServerCreate(&Config, pPool);
编程练习
用 OpenSSL 生成 Ed25519 自签证书导出 DER,跑通 server ed25519 cert.der key.der——确认输出 ready, protocol=h2, client=ping, server=pong(ALPN 首选项命中)。把 RequireProtocol 换 false 再改 Protocols 只留 h2,观察协议下标的选择变化。验收标准:两种配置的协商协议与手推一致。
实现 Select 回调:准备两套身份(自签两个域名的证书),按 ClientHello 的 SNI 选择对应身份;Cookie 写入"租户代次"并在 READY 后用 xrtTlsServerCookie 读回对比。验收标准:两个 SNI 各自拿到正确证书;Cookie 读写一致;未知 SNI 走你定义的默认策略(静态身份或明确失败)。
把 exampleTlsMove 的"整 Span 搬运"改成"每次最多搬 7 字节"的随机分片——模拟恶劣传输。统计握手完成的 Drive 轮数上限。验收标准:任意分片大小(1 到整条)握手都成功(协议机支持跨记录重组——第 84 章);最坏轮数有界并可解释(航班大小 ÷ 分片大小量级);无内存增长(第 6 章统计)。
小结
| 知识点 | 速查 |
|---|---|
| 会话层定位 | 裸协议机:Feed 密文 / Drive 推进 / Send 队列取航班,传输无关 |
| 驱动循环 | Feed×4(借用/Take/Ref/Buffer)→ Drive → Send(Size/Front/Consume/Spans) |
| 服务端配置 | Identity 或 Select 必须一个;ALPN 深复制;RequireProtocol 歧义即失败 |
| 首航 Arena | 惰性分配、用后清零释放;空闲连接零握手缓冲;1.2/1.3 共用路径 |
| Select | ClientHello 解析后、输出前;SNI/ALPN 回调期借用;Cookie=64 位路由标识 |
| 查询 | ServerName(深复制 SNI)/ SessionProtocol(最终 ALPN)/ ServerCookie |
| 票据签发 | Ticket/TicketNew;对象所有权转移;无全局缓存;AGAIN 状态不变 |
| 恢复接受 | 未找到回退完整握手;binder 错误 fatal 不降级;PSK+DHE;无 0-RTT |
| 1.2 边界 | EMS 必须;无恢复/重协商/KeyUpdate |
| 同构性 | 客户端与服务端共享公共会话 API——学一侧得两侧 |
📖 本章涉及的 API 参考手册:tls(201 个条目)tls_verify(17 个条目)tls_server(16 个条目)crypto(140 个条目)
点击查看完整签名、参数约束、返回值与错误说明