Skip to content

Repository files navigation

nethernet

PythonLicense

A cleanroom Python implementation of NetherNet, the WebRTC transport Minecraft Bedrock uses for peer-to-peer and LAN play. It implements both signaling paths — LAN discovery and the partner HTTP endpoint — over one asyncio connection model, with WebRTC provided by aiortc.

importnethernetasyncforhostinnethernet.discover(timeout=3):
asyncwithawaitnethernet.connect(host) asconn:
awaitconn.send(b"\xfe...") # a Bedrock game packetprint(awaitconn.recv())
break

Installation

The project is not published to PyPI yet; the nethernet name there belongs to an unrelated project. Install from source:

pip install "nethernet[http] @ git+https://github.com/EndstoneMC/nethernet-python"

Requires Python 3.11 or newer. The http extra pulls in aiohttp for HTTP signaling; the LAN path needs only the base install.

The two signaling paths

Signaling is how two peers exchange their WebRTC session descriptions. NetherNet has two channels for it, and they produce the same connection:

PathEntry pointsUsed for
LANserve / connect / discoverencrypted UDP broadcast on the local network; how Bedrock finds LAN games
HTTPserve_http / connect_httpa client posts its offer to /v1/join/{networkId}; how Bedrock reaches a partner-hosted server

Both hand back the same Connection, a message pipe with send / recv. NetherNet is symmetric peer-to-peer, so one class serves both ends of a connection.

The wire protocol is specified in SPEC.md, reverse-engineered from Bedrock Dedicated Server. The HTTP path additionally follows Mojang's NetherNet onboarding guide.

Quick start

LAN server

importasyncio, secrets, nethernetfromnethernetimportSendType, NetworkIDasyncdefhandle(connection):
asyncforpacketinconnection: # iterates until the peer disconnectsawaitconnection.send(packet, SendType.RELIABLE)
asyncdefmain():
local_id=NetworkID.p2p(secrets.randbits(64))
asyncwithnethernet.serve(handle, local_id, advertisement=b"MCPE;...") asserver:
awaitserver.serve_forever() # one task per connectionasyncio.run(main())

LAN client

asyncforhostinnethernet.discover(timeout=3):
print(host.network_id, host.advertisement)
asyncwithawaitnethernet.connect(host) asconn:
awaitconn.send(b"hello", SendType.RELIABLE)
reply=awaitconn.recv()
break

HTTP signaling

serve_http exposes the endpoints a Bedrock client posts its offer to. Every answer must carry an operator identity assertion or the client refuses the connection:

fromnethernetimportIdentitySigner, generate_operator_keykey=generate_operator_key() # long-lived; share one across a fleetasyncwithnethernet.serve_http(
handle, port=8080,
identity_signer=IdentitySigner(key, domain="partner.example"),
) asserver:
awaitserver.serve_forever()
conn=awaitnethernet.connect_http("https://partner.example")

More runnable programs are in examples/.

Messages

send and recv move whole application packets; there is no added framing to parse. On the wire each packet carries a one-byte fragment header, which the library adds and strips for you. Reliability is per-call:

awaitconn.send(data, SendType.RELIABLE) # ordered, retransmittedawaitconn.send(data, SendType.UNRELIABLE) # unordered, may be lost

Reliable packets are fragmented as needed, up to about 64 MiB. Unreliable ones are never fragmented, so anything over 262143 bytes is dropped rather than sent.

Identity assertions

Both signaling directions can carry an a=identity assertion in the SDP: the client asserts an authenticated player, the server asserts a long-lived operator key. This library implements the envelope mechanics — canonical JSON, detached JWS over the DTLS fingerprints, the self-signed cpk token — and leaves policy to you.

ConcernWhere it lives
signing an answer, verifying structureIdentitySigner, verify_server_identity
authorizing a player from their tokenyour validate_offer callback
pinning an operator key across connectionsyour on_server_identity callback

Validating a GameServerToken against Minecraft's auth service, reading XUIDs, and storing trust-on-first-use pins are all application decisions, so they stay outside the transport.

Errors

Failures raise exceptions rather than returning sentinels. All inherit from NetherNetError.

ExceptionRaised when
ConnectionFaileda dial never reached the connected state (.error carries the cause)
ConnectionClosedsend / recv on a closed connection; .error is NONE for a clean close
InvalidIdentityan a=identity assertion is missing, malformed, or fails verification
SignalingRejectedraise it from validate_offer to refuse an offer

.error is a SessionError, the same code Bedrock reports: negotiation timeouts, ICE failure, data-channel close.

Development

This project uses uv.

uv sync --extra dev
uv run pytest
uv run ruff check

License

MIT. See LICENSE.

About

Cleanroom Python implementation of Minecraft Bedrock's NetherNet P2P/LAN transport, built on aiortc.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages