HelloNetcode 安全连接实战:Unity Netcode for Entities 的 TLS 证书与自定义网络驱动接入
【免费下载链接】EntityComponentSystemSamples项目地址: https://gitcode.com/GitHub_Trending/en/EntityComponentSystemSamples
本文基于 EntityComponentSystemSamples 仓库中 HelloNetcode 系列的08_SecureConnection示例文档编写。该示例演示了如何使用自定义 bootstrapper(引导器)为 Unity Netcode for Entities 的客户端-服务端世界(client/server worlds)启用 TLS 加密连接:通过一个实现INetworkStreamDriverConstructor的自定义驱动构造函数,把客户端 CA 证书、服务端证书与私钥注入到网络驱动中。读完本文,你可以完整理解三个源文件(证书参数、引导器扩展、驱动构造函数)的分工与启用机制,掌握 OpenSSL 生成密钥/证书后在代码中的落地方式,并了解为何该特性必须通过编译宏在全局强制启用。
前置条件:仅依赖 BootstrapAndFrontend 示例
原文档(SecureConnection.md)明确列出本示例的唯一前置依赖:
Only needs the bootstrap to set-up client and server world. * BootstrapAndFrontend
也就是说,只要项目中存在 HelloNetcode 基础篇的01_BootstrapAndFrontend示例所定义的自定义 Bootstrap,就可以叠加本示例。该 Bootstrap 位于 FrontendBootstrap.cs,其核心约定包括:
- 必须继承
ClientServerBootstrap,且整个项目中只能有一个继承它的类(源码注释原文:“The bootstrap needs to extendClientServerBootstrap, there can only be one class extending it in the project”); - 需要打上
[UnityEngine.Scripting.Preserve]特性,防止 il2cpp 构建在开启裁剪时把 bootstrap 类剥离掉; Initialize(string defaultWorldName)是 Entities 用来创建默认 world 的入口方法。
在 FrontendBootstrap.cs 中,Initialize负责根据启动方式决定创建 Local World(前端菜单、按需创建 client/server world)还是直接CreateDefaultClientServerWorlds()建立 client/server world 并启用 auto-connect(默认端口 7979,可用命令行-port 8000覆盖)。安全连接示例正是建立在这套 world 创建流程之上,只是替换了驱动层的默认实现。
需要说明的一点:NetCode 包本身的连接建立、client/server world 分离、网络连接与 Secure Connection 等详细指南以官方文档为准(原文档给出了对应的手册链接:Getting Started 的 "Establish a connection" 一节、Client Server Worlds、Network Connection、以及 Transport 包的 Secure Client and Server 与 "Generate Required Keys and Certificate" 小节),本文只聚焦仓库内示例代码本身的实现。
示例总体结构:无场景、纯代码修改
原文档 "Sample description" 部分强调了两点关键特性:
- 本示例不包含任何场景——启用安全连接不需要往场景中添加任何东西("This sample contains no scene as nothing needs to be added to the scene to enable this feature");
- 必须在 bootstrap 阶段替换默认驱动——要在网络驱动上配置安全连接参数,就需要一个 custom/manual driver,以便向它传入不同的网络参数。而自定义驱动构造函数必须在 bootstrap 中足够早地注册,早于 world 创建,这样才能替换默认驱动。
目录 NetcodeSamples/Assets/Samples/HelloNetcode/2_Intermediate/08_SecureConnection/ 下恰好只有三个 C# 文件,分别承担三个职责:
| 文件 | 职责 |
|---|---|
| NetworkParams.cs | 存放本示例已生成的 TLS 证书、服务端证书与私钥(PEM 格式静态字符串) |
| SecureBootstrapExtension.cs | 继承自FrontendBootstrap的扩展类,在Initialize中把安全驱动构造函数挂到NetworkStreamReceiveSystem |
| SecureDriverConstructor.cs | 实现INetworkStreamDriverConstructor,把证书参数传给网络驱动 |
三个文件全部被同一个编译宏ENABLE_NETCODE_SAMPLE_SECURE包裹。每个文件的第 1 行都写着被注释掉的宏定义:
//#define ENABLE_NETCODE_SAMPLE_SECURE原文档解释了启用方式:把ENABLE_NETCODE_SAMPLE_SECURE这一行在三个文件中同时取消注释即可。注释中还给出了重要原因——一旦启用,它就全局地作用于整个项目("since enabling it means it's enforced globally through the whole project")。这与"项目中只能有一个 bootstrap"的约束直接相关:SecureBootStrapExtension继承自FrontendBootstrap,如果它与主 bootstrap 同时生效,项目中就出现了两个继承ClientServerBootstrap的类。因此该宏实际上是"以本示例的 bootstrap 替换主 bootstrap"的全局开关。
生成安全参数:NetworkParams.cs 中的证书与私钥
原文档 "Generating secure parameters" 一节说明:在NetworkParams.cs中可以看到一组静态变量,里面是本示例自己生成的证书;你自己的游戏需要自行生成,方法参考 Transport 文档中 "Generate Required Keys and Certificate" 的 OpenSSL 流程。
对应源码 NetworkParams.cs 中的SecureParameters静态类包含四个字段,全部在#if ENABLE_NETCODE_SAMPLE_SECURE条件编译内部:
ServerCommonName:FixedString512Bytes,值为"hello_netcode_secure",即用于定义服务端证书的 Common Name(CN)。客户端在握手时会用这个名字来校验服务端身份;GameClientCA:FixedString4096Bytes,游戏客户端信任的 CA 证书(PEM 文本,-----BEGIN CERTIFICATE-----…-----END CERTIFICATE-----);GameServerCertificate:FixedString4096Bytes,服务端证书(同样 PEM 文本);GameServerPrivate:FixedString4096Bytes,服务端 RSA 私钥(-----BEGIN RSA PRIVATE KEY-----…-----END RSA PRIVATE KEY-----)。
从源码结构看,这里选用FixedString512Bytes/FixedString4096Bytes这类Unity.Collections的定长字符串类型来承载 PEM 文本,是为了在 Burst/Jobified 友好的原生内存中传递证书数据,避免在驱动创建路径上额外分配托管字符串。
该文件开头的 XML 注释原样重申了原文档最重要的安全告警:
DO NOT SHIP GENERATED PRIVATE KEYS AS PART OF YOUR GAME!
原文档(SecureConnection.md)中的完整 NOTE 是:发布游戏时不要把你的生成密钥和证书随包发布——即使代码经过混淆,恶意用户也很容易反编译出密钥。仓库中还有一处呼应:SecureBootstrapExtension.cs 的Initialize开头在非编辑器构建(!UNITY_EDITOR)下会输出SAMPLE CODE: don't ship the certificates as a part of your build的警告/错误日志,其中在定义了NETCODE_DEBUG时用LogWarning,否则用LogError,作为运行时再提醒。实际项目中应改为服务端签发/下发证书,而不是把私钥硬编码在客户端代码里。
注册驱动构造函数:SecureBootstrapExtension.cs
SecureBootstrapExtension.cs 全文如下(去除条件编译与注释后的核心逻辑):
[UnityEngine.Scripting.Preserve] public class SecureBootStrapExtension : FrontendBootstrap { public override bool Initialize(string defaultWorldName) { // 在 world 创建之前把自定义驱动构造函数挂上去 NetworkStreamReceiveSystem.DriverConstructor = new SecureDriverConstructor(); return base.Initialize(defaultWorldName); } }源码注释解释了为什么写在这里:Netcode bootstrap 已经定义在 01 主连接示例中,而一个项目里只能有一个 bootstrap,所以这里只是在现有NetCodeBootstrap类的基础上"叠加"这一行("normally a project would only have the boostrap defined in one place")。关键调用链是:
Initialize被 Entities 引导流程调用(见 FrontendBootstrap.cs 中的注释 "The initialize method is what Entities calls to create the default worlds");- 在
base.Initialize内部真正创建 client/server world 之前,先把NetworkStreamReceiveSystem.DriverConstructor静态字段替换为SecureDriverConstructor实例; - 之后 world 创建路径读取该构造函数时,拿到的就是带 TLS 参数的驱动注册逻辑。
NetworkStreamReceiveSystem.DriverConstructor是一个可全局替换的静态挂载点。仓库中其他示例也依赖同一机制来切换驱动实现,例如:
- RelayDriverConstructor.cs(Relay 支持示例);
- HostMigrationHelper.cs 中
MigrateDataToNewServerWorld会先保存旧构造函数、临时替换、再恢复(var oldConstructor = NetworkStreamReceiveSystem.DriverConstructor;…),说明该字段是"当前生效驱动构造函数"的单一入口。
这可以推断出一个实用技巧:DriverConstructor支持运行时临时替换与还原,但安全连接示例选择的是"bootstrap 阶段一次性全局替换"这种最简方式。
传递安全参数:SecureDriverConstructor.cs
原文档 "Passing secure parameters" 一节说:生成的证书在SecureDriverConstructor.cs中被传入网络驱动,这里使用了一个 helper function 来为网络设置填充默认值;你也可以改为手动构造网络驱动实例,或通过相应的 override 传入自己的网络设置。
对应源码 SecureDriverConstructor.cs:
/// <summary> /// Register client and server using TLS configuration. /// The configuration is retrieved from <see cref="SecureParameters"/>. /// </summary> public struct SecureDriverConstructor : INetworkStreamDriverConstructor { public void CreateClientDriver(World world, ref NetworkDriverStore driverStore, NetDebug netDebug) { DefaultDriverBuilder.RegisterClientDriver( world, ref driverStore, netDebug, caCertificate: ref SecureParameters.GameClientCA, serverName: ref SecureParameters.ServerCommonName); } public void CreateServerDriver(World world, ref NetworkDriverStore driverStore, NetDebug netDebug) { DefaultDriverBuilder.RegisterServerDriver( world, ref driverStore, netDebug, certificate: ref SecureParameters.GameServerCertificate, privateKey: ref SecureParameters.GameServerPrivate); } }要点:
INetworkStreamDriverConstructor接口有两个必须实现的方法:CreateClientDriver与CreateServerDriver,分别负责为指定World注册客户端/服务端网络驱动;- 客户端侧通过
DefaultDriverBuilder.RegisterClientDriver的可选参数caCertificate(CA 证书)与serverName(即ServerCommonName,用于证书 CN 校验)启用 TLS 客户端驱动; - 服务端侧通过
DefaultDriverBuilder.RegisterServerDriver的可选参数certificate(服务端证书)与privateKey(私钥)启用 TLS 服务端驱动; DefaultDriverBuilder正是原文档所说的 "helper function to set up default values on the network settings",它在内部按平台/运行模式组装默认的NetworkSettings并注册对应传输(IPC/UDP/WebSocket 等)。
对比仓库中不启用 TLS 的自定义构造函数实现,可以更清楚地看出安全连接示例的"增量"所在:
- CustomHandlers.cs 中的
CustomDriverConstructor手动GetNetworkSettings()后按平台分派到RegisterClientIpcDriver/RegisterClientUdpDriver/RegisterClientWebSocketDriver(服务端同理); - RelayDriverConstructor.cs 则按 IPC(编辑器本地)与 UDP/WebSocket(Relay 路径)组合注册;
- ConnectionMonitorSystem.cs 的
DriverConstructor演示了根据模拟器管线需求选择CreateClientSimulatorPipelines/CreateClientPipelines/CreateServerPipelines。
而SecureDriverConstructor选择直接调用RegisterClientDriver/RegisterServerDriver这两个高层入口,只在参数上追加证书信息,是最简的 TLS 接入写法。若你需要自定义 MTU、传输类型、QoS 等更多网络参数,原文档给出的方向是"manually construct the network driver instance, or pass in your own network settings using the appropriate override",即参考上述示例中GetNetworkSettings()+ 分平台Register*Driver的做法,把证书参数与自定义设置合并。
启用步骤与注意事项汇总
结合原文档与源码,完整启用流程与约束如下:
- 确认前置:项目使用
01_BootstrapAndFrontend示例的FrontendBootstrap(或其等价自定义 bootstrap)。本示例不新增场景,不需要任何编辑器操作; - 生成你自己的证书:按 Transport 文档用 OpenSSL 生成 CA、服务端证书与私钥(本仓库示例已内置一份生成好的,仅用于演示);
- 填入参数:把 PEM 文本与 CN 写入
NetworkParams.cs的SecureParameters四个字段(客户端 CA、服务端证书、服务端私钥、ServerCommonName); - 开启编译宏:在
NetworkParams.cs、SecureBootstrapExtension.cs、SecureDriverConstructor.cs三个文件中,把首行//#define ENABLE_NETCODE_SAMPLE_SECURE取消注释。三个文件必须同时开启,否则SecureDriverConstructor或SecureParameters会因条件编译被整体裁掉; - 理解全局性:该宏是项目级全局生效的,
SecureBootStrapExtension会作为继承自FrontendBootstrap的第二个 bootstrap 存在——这正是仓库用条件编译而非直接生效的原因; - 发布安全:绝不要把生成好的私钥/证书随构建发布,防止反编译泄露(源码注释与运行时日志均强调此点)。
适用前提说明:本示例基于仓库中当前版本的 Netcode for Entities API(INetworkStreamDriverConstructor、NetworkStreamReceiveSystem.DriverConstructor、DefaultDriverBuilder),接口签名以仓库内 SecureDriverConstructor.cs 为准;不同版本的 Netcode/Transport 包中这些入口可能有变动,迁移时应以所用包版本的手册为准。
【免费下载链接】EntityComponentSystemSamples项目地址: https://gitcode.com/GitHub_Trending/en/EntityComponentSystemSamples
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考