Networking: derive RPC identifiers from a salted compile-time hash - #252
Networking: derive RPC identifiers from a salted compile-time hash#252Segfaultd wants to merge 1 commit into
Conversation
RPC4 keys slots by, and transmits, the RPC identifier as a plain string: Signal/Call write it with WriteCompressed(RakString), so every RPC packet carried a readable name like "Framework::ChatMessage" on the wire, and the same literals sat in the compiled binary. With no transport encryption in place, that hands a passive observer (and anyone running strings on a mod) the full RPC surface. Replace each readable identifier with FW_RPC_IDENTIFIER(name): a 64-bit FNV-1a of the name, salted by FW_RPC_IDENTIFIER_SALT, rendered as a stable 16-char hex token. The readable name is consumed only in constant evaluation, so it no longer reaches the binary or the wire -- both now carry the opaque token. RPC4's const char* API is untouched (the token backs a const char*), so registration and dispatch are unchanged. The salt makes the mapping per-build: bump FW_RPC_IDENTIFIER_SALT (or set -DFW_RPC_IDENTIFIER_SALT=<uint64> in release CI) to re-derive every identifier, so a name table lifted from one build is worthless against the next. Client and server must share the salt, so a change is netcode- breaking (MAJOR); the default keeps local dev builds deterministic. Source keeps the readable names for developers. Adds a rpc_identifier unit module covering opacity, distinctness, determinism, and salt inclusion.
|
Warning Review limit reached
Next review available in: 31 minutes Limit details: You’ve used all 1 included review currently available under your plan. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (11)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
RPC identifiers were plaintext on two surfaces. RPC4 keys its slots by, and transmits, the identifier as a string —
Signal/Callwrite it withWriteCompressed(RakString)(RPC4Plugin.cpp:433,:308/340/355), and the receiver reads it back as aRakStringand does a string-keyed lookup. So every RPC packet carried a readable name likeFramework::ChatMessageon the wire, and the same literals sat in the compiled binary. With no transport encryption in the current build, that hands a passive sniffer — and anyone runningstringson a mod — the entire RPC surface.This PR replaces each readable identifier with a salted compile-time hash, so the binary and the wire carry only an opaque, per-build token.
Approach (idea "A" from the design discussion — inspired by GTA/FiveM native hashing)
New header
networking/rpc/rpc_identifier.h:FW_RPC_IDENTIFIER("Framework::ChatMessage")→ a 64-bit FNV-1a of the name, salted byFW_RPC_IDENTIFIER_SALT, rendered as a stable 16-char lowercase-hex token.const char*, so RPC4's string-keyed API is untouched — registration, dispatch, and thePayloadconcept are unchanged.Converted every framework RPC identifier:
ClientIdentity,ChatMessage,ServerResources,ResourceRefresh,ResourceStop,VoiceSettings,VoiceSpeakerRange,VoicePreference,EmitScriptEvent, plus the replicationForceState/SetOwnerRPCs. Source keeps the readable names for developers.Per-build rotation
The salt makes the mapping per-build: bump
FW_RPC_IDENTIFIER_SALT, or set-DFW_RPC_IDENTIFIER_SALT=<uint64>in release CI, to re-derive every identifier — a name table lifted from one build is worthless against the next. Client and server must share the salt (they exchange the derived token), so it defaults to a fixed constant that keeps local dev builds deterministic and interoperable; CI can inject a per-release value at tag time.Compatibility
kIdentifier, just via the macro. Projects that define their own RPCs keep working unchanged (and can opt in to the macro).Scope / non-goals
LIBCAT_SECURITYis currently0), reported separately. Identifier hashing narrows the surface and adds per-build rotation; it is not a substitute for encryption, and does not make identifiers secret at runtime.EmitScriptEvent's script-defined event name (application data, still plaintext) and entity type ids (already sent as an unsalted CRC32; a candidate for the same treatment as a follow-up). TheTwoWayAuthenticationbuild-challenge identifier is a separate auth-plugin mechanism, left untouched.Testing
Added a
rpc_identifierunit module (code/tests/modules/rpc_identifier_ut.h, wired intoframework_ut.cpp) asserting the properties the RPC layer relies on:Frameworknor the payload name,HashIdentifier(name),The machinery and all converted payloads type-check cleanly (
clang++ -std=c++20 -fsyntax-only, including compile-timestatic_asserts of the same properties). I could not run the fullFrameworkTestsbinary locally (unrelated toolchain issue building the tree on my machine: AppleClang + bundled fmt), so please confirm the new module is green in CI.