Skip to content

Latest commit

 

History

History
116 lines (91 loc) · 5.31 KB

File metadata and controls

116 lines (91 loc) · 5.31 KB

nethernet-java

WebRTC-based NetherNet transport for Minecraft: Bedrock Edition, written in Java. Implements the HTTP signaling host side (answerer): accepts SDP offers over POST /v1/join/{networkId}, negotiates a WebRTC peer connection using Kas-tle/webrtc-java, and exposes the two NetherNet data channels as a simple send/receive API.

Two signaling transports

Transport Class Signaling ICE
HTTP (partner/Realms) NetherNetServer POST /v1/join/{id} full ICE, single POST
LAN NetherNetLanServer + DiscoverySignaling encrypted UDP broadcast on 7551 trickle (CANDIDATEADD)
Xbox / Franchise NetherNetLanServer + WebSocketSignalTransport authenticated wss:// trickle (CANDIDATEADD)

NetherNetLanServer runs the trickle-ICE negotiation over any SignalTransport. The WebSocketSignalTransport connects to a signaling service over wss:// with an Authorization token, relays offer/answer/candidate signals as JSON ({"Type","From","To","Message"}), and applies TURN credentials pushed by the service to the ICE configuration automatically.

The LAN transport implements the NetherNet discovery protocol: AES-ECB + HMAC-SHA256 encrypted packets (key SHA-256(LE u64 0xdeadbeef)) carrying RequestPacket/ResponsePacket (world advertisement via ServerData) and MessagePacket (offer/answer/candidate signals). ServerData is byte-compatible with vanilla (verified against golden vectors).

Protocol

  • Signaling: full ICE, single round-trip HTTP. GET /v1/join is the capability check; POST /v1/join/{networkId} carries the SDP offer and returns the SDP answer (application/sdp).
  • Data channels: ReliableDataChannel and UnreliableDataChannel.
  • Framing: every message is prefixed with a 1-byte remaining-segment counter. Messages larger than 262143 bytes are split into descending-counter fragments on the reliable channel; the unreliable channel never fragments.
  • Identity: the answer always carries an a=identity assertion - a base64 JSON envelope holding a self-signed ES384 JWT (with a cpk public-key claim) and a detached JWS over the answer's a=fingerprint lines. Client offer assertions are verified for possession of the cpk key.

Usage

ServerIdentity identity = ServerIdentity.generate("self");

NetherNetServer server = NetherNetServer.builder(identity)
        .bind(new InetSocketAddress("0.0.0.0", 7551))
        .listener(new ServerListener() {
            @Override
            public void onConnection(NetherNetConnection connection) {
                connection.setListener((conn, data) -> conn.send(data));
            }
        })
        .build();

server.start();

LAN server

ServerData data = new ServerData();
data.serverName = "Erila NetherNet";
data.levelName = "Lobby";

NetherNetLanServer server = NetherNetLanServer.builder(identity)
        .lanDiscovery(networkId)   // long, advertised to clients on the network
        .serverData(data)
        .listener(connection -> connection.setListener((conn, msg) -> conn.send(msg)))
        .build();

server.start();

Xbox / WebSocket server

NetherNetLanServer server = NetherNetLanServer.builder(identity)
        .webSocket(URI.create("wss://signaling.example/ws/v1.0/signaling/" + networkId),
                   Long.toString(networkId),
                   () -> "MCToken " + xboxAuthToken)   // Authorization header, refreshed per connect
        .listener(connection -> connection.setListener((conn, msg) -> conn.send(msg)))
        .build();

server.start();

The Type values in the JSON envelope (SignalMessage.TYPE_SIGNAL etc.) and the wss:// URL shape follow the Franchise signaling service; verify them against your target endpoint. Auth tokens (Xbox Live / PlayFab / MCToken) are supplied by the caller - this library does not perform the Xbox Live auth flow.

Integration points

  • signaling(Signaling) - replace the built-in JDK HTTP server with your own transport (e.g. front it with an existing Netty stack or a reverse proxy terminating TLS).
  • clientVerifier(ClientIdentityVerifier) - custom offer-assertion validation. The default proves cpk key possession only.
  • verifyClientTokens(URI jwksUri) - full GameServerToken validation: verifies the token signature (RS256/ES384) against a cached JWKS from the Minecraft auth service, checks exp, then proves fingerprint possession. Also requires the assertion to be present.
  • requireClientIdentity(true) - reject offers with no a=identity.
  • configuration(Consumer<RTCConfiguration>) - add STUN servers or tune ICE.
  • advertiseAddress(String publicIp[, int publicPort]) - inject a server-reflexive candidate into the answer for hosts behind 1:1 NAT / dedicated servers with a public IP.
  • answerTransformer(UnaryOperator<String>) - arbitrary answer-SDP rewrite.

The operator keypair is the unit of trust. Persist it (see ServerIdentity.of(KeyPair, domain)) and share it across a fleet so clients on plaintext HTTP see a single first-use prompt.

Building

Requires JDK 17+. The Gradle wrapper is included.

./gradlew build              # compile + run tests
./gradlew :example:run       # run the echo server on :7551
./gradlew publishToMavenLocal # publish com.erilanetwork:nethernet:1.0.0