Skip to content

Latest commit

 

History

7 Commits

Folders and files

Repository files navigation

localnet

Serverless multiplayer sessions on a local network, in Rust. Devices find each other over mDNS, connect directly over QUIC with TLS 1.3, and exchange game traffic through an adapter for GGRS rollback netcode. There is no server, no account and no internet connection involved.

It was built for localPong, a four-player LAN Pong for Android and desktop, and extracted here as its own crate.

What it does

Module
discovery Advertises a session over mDNS and browses for others. Each host's self-signed certificate travels in the service's TXT record, split into base64 chunks.
transport QUIC (quinn) with TLS 1.3 (rustls). A host generates a certificate per session with rcgen; a client trusts exactly that certificate and nothing else. Datagrams for game traffic, streams when needed.
session Host or join, a lobby → starting → in-progress lifecycle, per-peer connection quality, and events for peers joining, leaving and reconnecting.
socket LocalnetSocket, an implementation of GGRS's NonBlockingSocket, so a rollback session runs over the QUIC connection without the game touching the transport.
clock, ptp, ptp_udp A software clock disciplined by PTP (IEEE 1588) over UDP, using statime. localPong uses it to keep background music in sync on every device.
media RTP-style framing for media datagrams. Experimental.

Trust model

mDNS is unauthenticated, so anyone on the network can advertise a session. Pinning the advertised certificate means a match is encrypted against everyone listening, and a client only ever talks to the host whose advertisement it chose. It does not prove that the host is who its advertisement claims to be. That suits games on a home network; anything more sensitive would need discovery with its own authentication, such as a code shown on the host's screen.

The two tests in tests/pinning.rs show the model directly over loopback: a client that pins the host's certificate connects and exchanges datagrams, and a client that pins any other certificate is refused during the TLS handshake.

Usage

Hosting advertises the session and waits for players:

use std::collections::HashMap;
use localnet::{PeerAddr, Session};

let mut host = Session::host(
    "0.0.0.0:0".parse()?,
    PeerAddr("host-1".into()),
    "my-game",            // app id: prefixes the mDNS instance name, and is the TLS server name
    "Alice",              // display name shown to other players
    HashMap::new(),       // free-form metadata, e.g. game mode
    4,                    // capacity
)?;
let peer = host.accept_peer().await?;

Joining browses for hosts, picks one and connects with the certificate it advertised:

use localnet::{PeerAddr, PeerDiscovery, Session};

let discovery = PeerDiscovery::start()?;
// Hosts appear as their advertisements arrive: poll `peers()`, or subscribe
// with `events()` for found and lost notifications.
let host_info = discovery.peers().into_iter().next().expect("a host on the LAN");

let mut client = Session::join("0.0.0.0:0".parse()?, PeerAddr("client-1".into()), "my-game")?;
client.connect_to_host(host_info.addr, host_info.cert_der, "my-game").await?;

For rollback netcode, give session.socket() to a GGRS P2PSession as its socket, and call session.pump_datagrams().await once per frame to carry its messages over QUIC in both directions.

Status

Used by localPong on Android and desktop. Not published to crates.io, and the API may change. Discovery needs multicast, which some access points disable.

cargo test    # the certificate-pinning tests, over loopback

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.

About

Serverless LAN sessions in Rust: mDNS discovery, QUIC with pinned self-signed certificates, PTP clock sync and a GGRS socket adapter.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages