server.internal.network
Constants #
const compression_flate = u8(0x00)
A NetherNet message carries the batch on its own: the first byte is the compression marker below, with no game packet header in front of it.
const compression_none = u8(0xff)
const max_compressed_batch = 2 * 1024 * 1024 // wire bytes accepted per read
Inbound frame bounds - a malformed or hostile peer must never drive unbounded work. All values are hard limits; a violation aborts decode and the caller disconnects the peer. Sized generously above vanilla traffic so legitimate clients are unaffected.
const max_decompressed_batch = 8 * 1024 * 1024 // guards against decompression bombs
const max_packets_per_batch = 512
const max_single_packet = 2 * 1024 * 1024
const default_compression_threshold = 256
const max_packets_per_second = 1000
Inbound rate limits, enforced per connection over a 1s sliding window. A peer that exceeds either bound is disconnected. Bedrock clients burst on chunk requests and movement but stay well under these ceilings.
const max_bytes_per_second = 8 * 1024 * 1024
const max_prelogin_packets = 64
A connection that has not finished logging in may only send a handful of packets (network settings, login, resource-pack handshake). This caps the work an unauthenticated peer can force before we know who it is.
fn decode_batch #
fn decode_batch(payload []u8, compression_enabled bool) ![][]u8
fn encode_batch #
fn encode_batch(packets [][]u8, compression_enabled bool, threshold int) ![]u8
fn identity_key_matches #
fn identity_key_matches(transport_key string, chain_key string) bool
identity_key_matches reports whether the key a login chain is signed with is the one the transport was opened under.
On RakNet the encryption handshake did this by itself: the session key came out of an exchange against the chain's key, so only its holder could read what followed. NetherNet runs inside DTLS and skips that handshake, which leaves the chain unbound, and an unbound chain is replayable by anyone who captured one somewhere else. The signalling assertion is what binds it, because the peer proved it holds the key the assertion names before the transport was accepted.
fn is_connection_closed #
fn is_connection_closed(err IError) bool
is_connection_closed reports whether err is the peer going away rather than a genuine failure. NetherNet reports it as a plain error, so the message is all there is to match on.
fn load_identity #
fn load_identity(path string, domain string) !nethernet.Identity
load_identity returns the identity the server answers under, creating the key at path the first time.
The key is kept across restarts so a client that remembers the server keeps seeing the same one. The token itself is short lived and reissued per connection by nethernet.
fn new_identity_token_verifier #
fn new_identity_token_verifier(verifier auth.Verifier) &IdentityTokenVerifier
fn new_session #
fn new_session(mut conn nethernet.Conn, log &logger.Logger) &Session
fn remote_endpoint #
fn remote_endpoint(addr nethernet.Addr) string
remote_endpoint renders addr as the peer's "ip:port", read from the ICE candidate line the connection settled on. Addr.str() carries the network id, the connection id and the full candidate line, which says nothing useful in a game log. Falls back to the full form when no candidate was selected, so a log line is never left without an address at all.
fn transport_identity_key #
fn transport_identity_key(conn &nethernet.Conn) string
transport_identity_key is the peer's key as it appears in a login chain: the base64 SubjectPublicKeyInfo both sides of the comparison are written in.
interface Transport #
interface Transport {
// disable_encryption reports whether the transport already encrypts, in
// which case the protocol layer must not negotiate its own encryption.
disable_encryption() bool
// transport_identity is the key the peer proved it holds while the transport
// was being set up, base64 SubjectPublicKeyInfo, or empty when it proved
// none. A login chain signed with a different key did not come from whoever
// opened this connection.
transport_identity() string
mut:
send(p protocol.Packet) !
send_batch(packets []protocol.Packet) !
read() ![]protocol.Packet
remote_addr() string
close()
mark_logged_in()
enable_compression(threshold int)
enable_encryption(mut ctx encryption.Context)
}
Transport is everything NetworkSession needs from the wire: framed packet send/receive plus the handshake time hooks (compression, encryption, marking login complete).
struct IdentityTokenVerifier #
struct IdentityTokenVerifier {
mut:
verifier auth.Verifier
mutex &sync.Mutex = sync.new_mutex()
}
IdentityTokenVerifier checks the token in a NetherNet identity assertion against Microsoft's published signing keys.
It is the same token the Login packet carries later, issued by the same service for the same audience, so the check is the one the login path already makes. Making it here moves the decision earlier: an offer from somebody who cannot present a genuine token is refused before a peer connection is built for them.
The verifier underneath caches what it discovered, so the lock is held across the call rather than around a copy: a handful of concurrent joins waiting on one key fetch is better than several of them racing the same request.
fn (IdentityTokenVerifier) verify_token #
fn (mut v IdentityTokenVerifier) verify_token(token string) !
verify_token implements nethernet.TokenVerifier.
struct Session #
struct Session {
mut:
conn &nethernet.Conn = unsafe { nil }
pool protocol.PacketPool
compression_enabled bool
threshold int = default_compression_threshold
cipher &encryption.Context = unsafe { nil }
send_queue [][]u8
write_mutex &sync.Mutex = sync.new_mutex()
logged_in bool
prelogin_packets int
window_start time.Time = time.now()
window_packets int
window_bytes int
pub mut:
log &logger.Logger = unsafe { nil }
}
fn (Session) mark_logged_in #
fn (mut s Session) mark_logged_in()
mark_logged_in lifts the pre-login packet cap once authentication completes.
fn (Session) enable_compression #
fn (mut s Session) enable_compression(threshold int)
fn (Session) enable_encryption #
fn (mut s Session) enable_encryption(mut ctx encryption.Context)
enable_encryption installs the per-session cipher once the handshake has completed. From this point every batch body (after the 0xfe header) is encrypted outbound and decrypted inbound. Guarded by write_mutex so it never races an in-flight flush.
fn (Session) encryption_enabled #
fn (s &Session) encryption_enabled() bool
fn (Session) read #
fn (mut s Session) read() ![]protocol.Packet
fn (Session) queue #
fn (mut s Session) queue(p protocol.Packet)
fn (Session) flush #
fn (mut s Session) flush() !
fn (Session) send #
fn (mut s Session) send(p protocol.Packet) !
fn (Session) send_batch #
fn (mut s Session) send_batch(packets []protocol.Packet) !
fn (Session) remote_addr #
fn (mut s Session) remote_addr() string
fn (Session) transport_identity #
fn (s &Session) transport_identity() string
transport_identity is the key the peer proved it holds during signalling, in the base64 SubjectPublicKeyInfo form a login chain names a key in.
It is empty for a peer that connected without an identity assertion, and for a transport that has no such notion at all.
fn (Session) disable_encryption #
fn (s &Session) disable_encryption() bool
disable_encryption reports that the transport already encrypts every byte, so the protocol layer must not negotiate a second layer on top of it.
fn (Session) close #
fn (mut s Session) close()