diff --git a/.github/workflows/registration-provider.yml b/.github/workflows/registration-provider.yml index 5218763..270b6d5 100644 --- a/.github/workflows/registration-provider.yml +++ b/.github/workflows/registration-provider.yml @@ -1,33 +1,35 @@ -name: Provider extension +name: Native provider extension on: push: - branches: [codex/registration-provider] + branches: [codex/native-admission] pull_request: permissions: contents: read +concurrency: + group: native-extension-${{ github.head_ref || github.ref_name }} + cancel-in-progress: true jobs: extension: - runs-on: ubuntu-latest - timeout-minutes: 20 + runs-on: ubuntu-24.04 + timeout-minutes: 30 steps: - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 with: path: GeyserNetherNet - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - with: - repository: teamziax/NetworkCompatible - ref: a9b163bc5914b44399381aedbc473e16e3ce17e5 - path: NetworkCompatible - uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 with: distribution: temurin - java-version: '21' + java-version: | + 8 + 17 + 21 - uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 - - run: bash gradlew build -PregistrationRevision=${{ github.sha }} --console=plain + - run: sudo apt-get update && sudo apt-get install -y cmake ninja-build g++ libssl-dev pkg-config + - run: bash scripts/build-native-development.sh working-directory: GeyserNetherNet - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 with: - name: GeyserNetherNet-registration-${{ github.sha }} + name: GeyserNetherNet-native-${{ github.sha }} path: | GeyserNetherNet/build/libs/*.jar GeyserNetherNet/build/test-results/test/*.xml diff --git a/build.gradle.kts b/build.gradle.kts index 660acb4..7ea3bc4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,13 +10,8 @@ relocate("org.yaml.snakeyaml") relocate("org.spongepowered.configurate") relocate("com.google.gson") -val nativePlatforms = listOf( - "windows-x86_64", - "x86_64", - "aarch64", - "macos-x86_64", - "macos-arm64" -) +// Experimental native admission currently has a tested Linux x86_64 development classifier. +val nativePlatforms = listOf("x86_64") val id = project.property("id") as String val extensionName = project.property("name") as String @@ -25,11 +20,12 @@ val version = project.version as String val geyserVersion = "2.11.0" val netherNetVersion = "1.8.0" -val libdatachannelVersion = "0.24.1.1" +val libdatachannelVersion = "0.24.1.1-warden.5544964002162d184bacfcd0cb8d70d86ec3f271" val configurateVersion = "4.2.0-GeyserMC-20251111.004649-11" repositories { + mavenLocal { content { includeGroup("dev.ziax.warden") } } // Repo for the Geyser API artifact maven("https://repo.opencollab.dev/main/") @@ -53,9 +49,9 @@ dependencies { implementation("dev.kastle.netty:netty-transport-nethernet:$netherNetVersion") // The WebRTC library and its natives - implementation("tel.schich:libdatachannel-java:$libdatachannelVersion") + implementation("dev.ziax.warden:libdatachannel-java:$libdatachannelVersion") nativePlatforms.forEach { platform -> - runtimeOnly("tel.schich:libdatachannel-java:$libdatachannelVersion:$platform") + runtimeOnly("dev.ziax.warden:libdatachannel-java:$libdatachannelVersion:$platform") } // Configurate @@ -108,6 +104,8 @@ tasks { mergeServiceFiles() manifest.attributes["Registration-Revision"] = providers.gradleProperty("registrationRevision").getOrElse("local-development") manifest.attributes["Registration-Network-Revision"] = "a9b163bc5914b44399381aedbc473e16e3ce17e5" + manifest.attributes["Native-Network-Revision"] = "3c346c681396e0d7743467ac816a798f331dfb71" + manifest.attributes["Native-JNI-Revision"] = "5544964002162d184bacfcd0cb8d70d86ec3f271" dependencies { // Exclude netty apart from the http codec exclude { diff --git a/docs/native-admission.md b/docs/native-admission.md new file mode 100644 index 0000000..8a7f46d --- /dev/null +++ b/docs/native-admission.md @@ -0,0 +1,37 @@ +# Native provider mode (experimental) + +The ServiceLoader factory binds the native Warden admission endpoint and retains +Geyser's existing NetherNet Bedrock child pipeline. It loads the pre-provisioned +DTLS identity from `host-cert.pem` and `host-key.pem` in `provider.state-directory`. +The host publishes a fresh boot incarnation, actual certificate fingerprint and +fixed UDP candidate, and installs background host-specific admission keys. Client +context arrives only in the authenticated token carried by STUN. The native +adapter rejects per-join control admission. + +This draft is stacked on the WS2 provider extension PR. The two NetworkCompatible +slices are composed only in a disposable local checkout by: + +```sh +bash scripts/build-native-development.sh +``` + +The script pins both owned source revisions, resolves only their shared Gradle +module addition, runs provider/native tests, builds the extension and verifies +the shaded factory service, sole native library and full revision manifest. +It never merges or pushes either source branch. Linux x86_64, JDK 8/17/21, +Python 3, OpenSSL CLI/development headers, CMake, Git and a C++ compiler are +required. This JNI development classifier links system OpenSSL; other native +platforms are not packaged until independently built and verified. + +For a disposable game test, configure `mode: provider`, `fake-transport: false`, +an explicitly assigned `bind-address` and a `udp-port` separate from RakNet. +Wildcard `0.0.0.0` is refused: this initial profile publishes the bound interface +as its candidate. NAT/public advertised-address configuration remains a separate +profile extension. Provision the PEM key/certificate before starting the endpoint +and keep its state directory private. Do not change identity files while active. +Use the registration profile, provider URL and grant settings from the WS2 docs. + +Build, factory packaging and native/Worker echo do not prove a stock Minecraft +game join. Stock-client identity, token limits, both channels into gameplay, +reconnect, two-host routing and direct-join regression must still be recorded. +No production deployment or merge is part of this draft. diff --git a/scripts/build-native-development.sh b/scripts/build-native-development.sh new file mode 100644 index 0000000..9155f43 --- /dev/null +++ b/scripts/build-native-development.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -euo pipefail +# Disposable local composition only. Neither source branch is merged or pushed. +native_revision=3c346c681396e0d7743467ac816a798f331dfb71 +provider_revision=a9b163bc5914b44399381aedbc473e16e3ce17e5 +extension_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) +integration_root=$(mktemp -d "${TMPDIR:-/tmp}/geyser-native-build.XXXXXXXX") +trap 'rm -rf -- "$integration_root"' EXIT +git clone --quiet https://github.com/teamziax/NetworkCompatible.git "$integration_root/network" +git -C "$integration_root/network" checkout --quiet --detach "$native_revision" +# A source-only merge leaves HEAD at the pinned native revision; no integration commit is published. +if ! git -C "$integration_root/network" -c user.name=NativeBench -c user.email=native-bench.invalid merge --no-commit --no-ff "$provider_revision"; then + conflicts=$(git -C "$integration_root/network" diff --name-only --diff-filter=U) + if [[ "$conflicts" != 'warden-signalling/build.gradle.kts' ]]; then + echo 'Unexpected integration conflicts; refusing to invent a resolution.' >&2 + exit 1 + fi + python3 - "$integration_root/network" "$provider_revision" <<'PY' +import pathlib, subprocess, sys +root, provider = sys.argv[1:] +path = 'warden-signalling/build.gradle.kts' +def source(ref): + return subprocess.check_output(['git', '-C', root, 'show', ref + ':' + path], text=True) +native, registration = source('HEAD'), source(provider) +anchor = 'description = "Provider registration and Warden control client"' +version = 'version = providers.gradleProperty("wardenVersion").getOrElse("0.1.0-registration-dev")' +assert native.count(anchor) == 1 and version in registration +native = native.replace(anchor, anchor + '\n' + version) +tasks = registration[registration.index('tasks.register("providerStub")'):] +pathlib.Path(root, path).write_text(native + '\n' + tasks) +PY + git -C "$integration_root/network" add warden-signalling/build.gradle.kts +fi +( + cd "$integration_root/network" + ./scripts/bootstrap-native-admission.sh + ./gradlew :warden-signalling:test :warden-signalling:nativeAdmissionTest :transport-nethernet:test +) +cd "$extension_root" +extension_revision=$(git rev-parse HEAD) +if [[ -n "$(git status --porcelain)" ]]; then extension_revision+="-dirty"; fi +bash gradlew build -PwardenNetworkPath="$integration_root/network" -PregistrationRevision="$extension_revision" +python3 - "$extension_root" "$native_revision" "$provider_revision" <<'PY' +import pathlib, sys, zipfile +root, native, provider = sys.argv[1:] +jars = list(pathlib.Path(root, 'build/libs').glob('*.jar')) +assert len(jars) == 1, 'Expected one shaded extension' +with zipfile.ZipFile(jars[0]) as jar: + service = 'META-INF/services/org.geyser.extension.nethernet.provider.ProviderHostFactory' + assert jar.read(service).decode().strip() == 'org.geyser.extension.nethernet.admission.NativeProviderHostFactory' + assert jar.namelist().count('native/libdatachannel-java.so') == 1 + # Unfold continuation lines in the manifest before checking full SHA pins. + manifest = jar.read('META-INF/MANIFEST.MF').decode().replace('\r\n ', '') + assert 'Native-Network-Revision: ' + native in manifest + assert 'Registration-Network-Revision: ' + provider in manifest +print('Native extension packaging PASS:', jars[0]) +PY diff --git a/src/main/java/org/geyser/extension/nethernet/admission/NativeProviderHostFactory.java b/src/main/java/org/geyser/extension/nethernet/admission/NativeProviderHostFactory.java new file mode 100644 index 0000000..2143b43 --- /dev/null +++ b/src/main/java/org/geyser/extension/nethernet/admission/NativeProviderHostFactory.java @@ -0,0 +1,24 @@ +package org.geyser.extension.nethernet.admission; + +import dev.kastle.netty.channel.nethernet.admission.AdmissionGate; +import dev.kastle.warden.admission.NativeProviderTransport; +import io.netty.bootstrap.ServerBootstrap; +import org.geyser.extension.nethernet.provider.ProviderHostFactory; +import java.net.InetSocketAddress; +import java.nio.file.Path; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +/** Fixed native endpoint using the extension's existing Bedrock child pipeline. */ +public final class NativeProviderHostFactory implements ProviderHostFactory { + @Override public CompletionStage open(ServerBootstrap bootstrap, InetSocketAddress udpBind, Map options) { + try { + String directory = options.get("stateDirectory"); + if (directory == null || directory.isBlank()) throw new IllegalArgumentException("Provider stateDirectory required"); + Path state = Path.of(directory); + return NativeProviderTransport.open(bootstrap, udpBind, state.resolve("host-cert.pem"), state.resolve("host-key.pem"), AdmissionGate.Limits.defaults()) + .thenApply(transport -> new Host(transport, transport.channel())); + } catch (Exception invalid) { return CompletableFuture.failedFuture(invalid); } + } +} diff --git a/src/main/resources/META-INF/services/org.geyser.extension.nethernet.provider.ProviderHostFactory b/src/main/resources/META-INF/services/org.geyser.extension.nethernet.provider.ProviderHostFactory new file mode 100644 index 0000000..c81e172 --- /dev/null +++ b/src/main/resources/META-INF/services/org.geyser.extension.nethernet.provider.ProviderHostFactory @@ -0,0 +1 @@ +org.geyser.extension.nethernet.admission.NativeProviderHostFactory