Skip to content

merge - #6719

Closed
Orstando wants to merge 130 commits into
GeyserMC:masterfrom
Orstando:master
Closed

merge#6719
Orstando wants to merge 130 commits into
GeyserMC:masterfrom
Orstando:master

Conversation

@Orstando

Copy link
Copy Markdown

No description provided.

SendableMetatype and others added 30 commits April 12, 2026 18:55
- Custom StartGamePacket serializer appending 3 education-specific strings
- Codec processor to swap serializer for education clients
- Handle CONTINUE_BREAK and STOP_BREAK block actions sent by education clients
- Deserialize education fields from client JWT (IsEduMode, ADRole, EduTokenChain)
- Detect education clients and skip Xbox chain validation
- Extract and verify MESS-signed server token from EduTokenChain
- Echo client's token back in handshake JWT (stateless, no config needed)
- Set xuid to MESS-verified oid for identity and duplicate detection
- Education session fields (client flag, tenant ID, server token, skin hash)
- Education codec swap and codebuilder gamerule disable in StartGamePacket
- Education fields in BedrockData for Floodgate (education flag, tenantId, adRole)
- Education UUID generation (SHA-256 of tenantId:username, MSB 0x0000000100000001)
- Education skin relay integration for Java player visibility
- Hash-based skin matching fallback for education players
- MSB check fix in JavaLoginFinishedTranslator for Bedrock skin cache
* Update README.md

* Fuck, messed it up by accident

* I still messed it up gng ;-;

* Fix branding (EduGeyser, one word) and supported versions

---------

Co-authored-by: SendableMetatype <263203301+SendableMetatype@users.noreply.github.com>
Previously, a client could set isEduMode=true in clientData and bypass
both Xbox Live validation and education token verification by simply
omitting the EduTokenChain. The code only rejected clients with bad
signatures, not missing or malformed tokens, letting attackers connect
with fully self-signed login chains and any claimed xuid/username.

Now any client claiming education edition must present a valid
MESS-signed token. Rejections are silent (no logging) to prevent log
amplification from spammed invalid tokens.

Also remove the empty-string tenant ID fallback in GeyserSessionAdapter
that caused UUID collisions across token-less edu clients sharing a
username.
The previous UUID scheme hashed tenantId:username, which is unsafe
because usernames come from the self-signed login chain and can be
spoofed by a modified client. OIDs are cryptographically signed in
the MESS token and immutable per user per tenant.

The LSB is now 64 purely random bits extracted from the OID by
stripping the 6 fixed UUID v4 bits (version nibble at 48-51, variant
at 64-65). The education MSB (0x0000000100000001) is unchanged.

BREAKING: existing education player data (inventories, permissions,
bans, builds) will not carry over. The legacy-uuid branch preserves
the old behavior for servers that need a migration window.
- Enforce MESS token expiry (ISO 8601 UTC, matches client semantics: failed parse = expired)
- Distinct user-facing messages for EXPIRED vs INVALID tokens so expired sessions get actionable "restart MCEE" guidance
- Cache parsed MESS RSA key once at class load instead of rebuilding per verification
- Validate OID/tenantId are well-formed UUIDs at the verification boundary
- Stricter token parsing: split("\|", -1) + exact 4-field count, odd-length hex rejected
- Drop pointless base64 padding loop; Base64.getUrlDecoder() accepts unpadded input
- Remove unused CONTINUE_BREAK / STOP_BREAK block-break aliases (confirmed unnecessary in testing)
- Document adRole as client-controlled and unsafe for authorization decisions
- Document why the outer EduTokenChain JWT signature is intentionally not verified
- Fix inconsistent field naming in EducationCodecProcessor javadoc
- Revert stray whitespace-only changes in GeyserSession
- Rewrite comments that used em dashes or arrows as sentence separators;
  the replacement prose uses proper sentence structure instead.
- Reword the OID javadoc to describe it as an Entra account identifier
  (globally unique, analogous to an Xbox xuid) rather than "per tenant",
  which was confusing.
Move education-specific protocol handling down into CloudburstProtocol
via libs.versions.toml pointing at a fork on JitPack that provides:
MESS token verification, EduTokenChain JWT extraction, the handshake
JWT overload with signedToken claim, StartGamePacket education fields,
and an education-variant codec on Bedrock_v898.

- LoginEncryptionUtils: drop in-tree MESS key, hexToBytes, token
  verification, and manual jose4j handshake JWT construction; call
  EncryptionUtils.extractServerTokenFromEduTokenChain,
  validateEducationToken, and the three-arg createHandshakeJwt
- GameProtocol: register Bedrock_v898.EDUCATION_CODEC and expose
  getEducationCodec(protocolVersion) for session-level lookup
- GeyserSession: use the pre-registered education codec from
  GameProtocol instead of per-session runtime codec wrapping;
  null-guard for protocol versions without an education variant
- Delete EducationCodecProcessor and EducationStartGameSerializer
  (superseded by library equivalents)
Explains that a codec swap is structurally unavoidable here: the
initial codec is chosen at RequestNetworkSettingsPacket time when
only the protocol version is known, and education clients report
the same protocol version as standard Bedrock. The education flag
first surfaces in LoginPacket's clientData, so the swap happens
here rather than at connection time.
# Conflicts:
#	gradle/libs.versions.toml
…r their protocol version

Instead of silently continuing with the standard codec (which
omits the edu-specific StartGamePacket fields and causes a
confusing later disconnect), reject the client immediately with
a clear message that the server needs updating.
# Conflicts:
#	core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java
#	gradle/libs.versions.toml
The upstream GUEST auth commit added a signed check before
education detection. Education clients are always unsigned
(self-signed chains), so they'd be rejected before we can
identify them as education. Our education-aware check further
down already handles both cases correctly.
Avoids blocking Geyser's global ScheduledExecutorService with
blocking HTTP calls to the signing relay.
Written by the skin executor thread, read by the WebSocket
SKIN_UPLOADED handler. Without volatile the WebSocket thread
can see stale null and miss the hash match, causing the player
to spawn skinless.
A connecting client can send the marker string
GEYSER_DISABLE_BEDROCK_ENCRYPTION_V1 as its first RakNet message.
The one-shot handler sets a channel attribute and removes itself.
LoginEncryptionUtils checks the attribute and skips enableEncryption()
while still sending the ServerToClientHandshake JWT for education
token echo.

This lets the Nethernet relay extension forward unencrypted bytes
from the WebRTC side (where DTLS provides transport encryption)
without building a full protocol proxy.
Log remote address at debug level when the encryption disable marker
is consumed. Add 1.21.133 to the v898 codec version list.
Add 1.21.133 to supported Education versions. Extension is no longer
optional since direct IP connection methods were removed in 1.21.133.
Accept Nethernet connections directly in Geyser's session handling
instead of relying on an external relay. A framing adapter converts
between Nethernet ByteBufs and RakNet's RakMessage format so the
existing Bedrock protocol pipeline works unchanged.

- NethernetManager API for extensions to start/stop the server
- Connection ID persisted in nethernet/connection-id.yml
- PlayFab MCToken auth and signaling managed internally
- Automatic signaling health checks and reconnection
- Bedrock encryption skipped for Nethernet (DTLS provides it)
- Guard GeyserSession.ping() for non-RakNet channels
The 3.0 branch was rebased onto upstream, which also folds in the new
ClientboundDataStorePacket dynamic value support. The previous pin
pointed at a pre rebase commit that no longer exists on any branch.
Adds the batch flush delay system property: the 1 ms coalescing window
stays the default, and deployments that prefer compression efficiency
over latency can widen it up to 50 ms, or disable it with zero.
Emits all fourteen ServerData members on GET /v1/join, the NetherNet
equivalent of the RakNet unconnected pong that vanilla 1.26.50
introduced. The document is built from the same pong the RakNet
listener answers pings with (MOTD passthrough, the ping event, and the
fallbacks included), so the two transports always describe the server
identically; onQuery gained a channel free overload for that.

Both auth bits are true since we accept Microsoft authenticated and
self signed identities alike, the nonce is generated once per process
like vanilla, and connection reports LANWebRTCSignaling as vanilla
does on this endpoint.
The 26.40 client falls back to plain HTTP with its TOFU flow only when
the server refuses TLS outright; an invalid certificate sends it
straight to RakNet (proven live with a deliberately broken manual
cert). Serving a dead certificate is therefore the one behavior that
loses NetherNet, and no certificate at all is strictly better.

One gate applies to every certificate regardless of origin: temporal
validity plus a chain to the platform trust anchors, checked the way a
stock client checks them (SAN matching and privately trusted setups
deliberately out of scope). The ACME manager validates at adoption and
withdraws past notAfter, self reversing on the next renewal. Manual
certificates moved to a per connection source that re-reads cert.pem
and key.pem whenever they change, so operators can swap certificates
live without a rebind or restart; removal withdraws TLS, and the
automatic manager takes over on the next restart.
Findings from four adversarial review passes over the TLS and status
work, each verified against the code before fixing:

The status document is a snapshot served from cache and refreshed at
most once a second, primed at start, on its own thread rather than the
maintenance scheduler, so a hung ping listener cannot silence the
signaling watchdog. Probes previously built it inline on connection I/O
threads shared with player traffic, where the Velocity ping passthrough
blocks on the plugin event chain, so anyone looping the unauthenticated
probe could stall NetherNet players; a configured hostname also cost a
blocking DNS lookup per probe, now resolved once with a loopback
fallback. The snapshot age advances on failed builds too, so a throwing
passthrough is retried once a second rather than as fast as probes
arrive, and each refresh clears the gate it claimed, so a refresh left
running by a previous start cannot release a later one.

Certificate and key are checked as a pair before either source serves
them, because netty builds a context from a mismatched pair and fails
only at handshake time, which clients read as an invalid certificate
and answer by abandoning NetherNet. The manual path caught the two step
copy going live as new certificate plus old key; the automatic path can
drift the same way when an interrupted issuance leaves a fresh key
beside the old certificate, and now routes that to a reissue. Manual
file change detection is throttled to once a second and compares sizes
alongside modification times.

Persisted material that cannot be used converges on one reissue with
one accurate reason, covering every shape a torn write takes: only a
zero byte file parses to an empty chain, most tear points throw, and a
tear inside the intermediate parses clean as a short chain that then
fails path building. Adoption is retried on the backoff, which heals
temporal rejections like clock skew for free, but only a bounded number
of times, after which the material is replaced rather than retried
until the certificate expires. Key files are written atomically like
the certificate and regenerated rather than wedging issuance forever
when they are unreadable, since a torn key file can otherwise leave the
served certificate working while renewal can never succeed again.

The manager publishes the certificate, its expiry and its warned flag
as one immutable reference, keeps serving a previously valid
certificate when a replacement fails validation, compares the disk leaf
against the published one so a rejected replacement is genuinely
retried, and warns once per distinct rejection cause rather than once
per streak, so a cause that changes is not swallowed. Trust check
exceptions with null messages no longer read as acceptance anywhere.

Also reattaches two javadoc blocks orphaned by earlier method moves.
Brings in the join probe server status document and the per connection
TLS or plaintext selection on the signaling port.
The on demand flush and its configurable delay landed upstream as PR
341 with protected scheduling follow ups, so the fork now carries only
the four education commits on top of upstream, which also brings the
SyncedAttribute serialization fix and the PersonaPieceType update.
# Conflicts:
#	core/src/main/java/org/geysermc/geyser/session/GeyserSessionAdapter.java
… and entity fixes

# Conflicts:
#	README.md
#	core/src/main/java/org/geysermc/geyser/network/GameProtocol.java
#	core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java
#	gradle/libs.versions.toml
…L fix

# Conflicts:
#	README.md
#	core/src/main/java/org/geysermc/geyser/network/GameProtocol.java
#	gradle/libs.versions.toml
Geyser ships dev.kastle.webrtc and the NetherNet transport unrelocated,
as diverged forks. Parent-first delegation replaced the copy an
extension bundles with the fork. The fork moved fragment handling from
the channel into a pipeline codec, so the stock library and the fork
are not wire compatible. The extension classloader now loads dev.kastle
classes and the webrtc-java native resources from the extension jar
first. An extension that does not bundle them still falls through to
the Geyser copy.
…ixes, protocol and MCPL bumps

The skin plugin message now goes out through both mechanisms: the
upstream spawn gated send and the fork's fixed 5 second fallback, in
the retail path and in the education fallback path. A duplicate apply
overwrites the same skin, so the double send is safe.

The protocol pin moves to the rebased fork head 623c4e4e (upstream
NBT bump plus the four education commits).
The signaling service keeps a socket open after our registration on it
has died, so the socket level checks kept passing while the first join
after idle failed. The watchdog now also requires the fork's route probe
to succeed, with the nethernet pin bumped for it.
…ession tick

Living entities were interpolated on the session tick: the first step waited up to a
full tick for the next boundary and the three steps always assumed a 50 ms cadence.
Steps now go out the moment a Java movement update arrives and are spaced evenly over
the measured interval between that entity's updates, seeded with the server's declared
tick rate, so an entity updated every two ticks gets two steps and one updated every
tick is forwarded as is.

The 26.40 codec stopped serializing the teleport flag, so the snap is sent as the
force-move field. Without it, current clients interpolated Geyser's steps a second time.

The tick-end immediate flush is skipped when nothing is queued.
Both transports come from NetworkM 1.0.0 and the NetherNet native from
webrtc-java 0.17.0-sm.1, both on Maven Central.
The Protocol fork now depends on NetworkM's RakNet from Maven Central and
merges upstream 26.50 with its v2168 packet fixes. Its JitPack group moves
to the EduGeyser organization, and -PlocalProtocol is independent of
-PlocalNetworkM again because the pinned build carries the NetworkM
packages itself. 26.50 added a default biome to DimensionDefinition; pass
minecraft:plains as upstream's 26.50 branch does.
Fabric relocates the shared transport package root, so core's NetherNet
references no longer matched the nested transport jar once both transports
moved to NetworkM. NeoForge loads nested jars into its plugin layer, which
cannot see the WebRTC classes shaded into the mod, so NetherNet never
started there. Shading the transport next to the WebRTC classes fixes both.
Fork-first structure with one introduction, links to the EduGeyser
organization repositories and the website documentation, dedicated
NetherNet and bundled extension sections, and corrected build
instructions.
@Orstando

Copy link
Copy Markdown
Author

whoops, wrong repo
sorry

@Orstando Orstando closed this Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants