Skip to content

Networking and security

Requirements

  • Up to four players per game against an authoritative server: the server decides, clients display and send inputs.
  • UDP for everything in game (imposed); TCP only with a strong justification.
  • A binary protocol, documented so that someone can write a new client from the RFC.
  • A multithreaded server that never blocks on a client and survives any client crash or malformed packet.
  • Playable with 150 ms latency, 2 % loss or duplication, and a 5 KB/s bandwidth cap.

Transport

Transport Behaviour Fit
UDP (chosen for gameplay) Datagrams, no ordering, no retransmission, no head-of-line blocking A lost position is replaced by the next snapshot 50 ms later; waiting for it would freeze every newer update
TCP Ordered reliable stream A single lost segment stalls all later data until retransmitted: fatal for real-time positions
TCP for the login only (chosen) One short exchange before the game Login needs reliability and is not time-critical. TCP gives it for free and lets the server issue a session token before any UDP traffic is accepted; it carries no in-game data.
QUIC Reliable and unreliable streams over UDP Excellent, but heavy libraries and a lot of protocol we would not use

Networking library

Option Assessment
Standalone Asio (chosen) Header-only, cross-platform (epoll, kqueue, IOCP), asynchronous UDP and TCP, timers; allowed by the subject; we still design the whole protocol
BSD sockets / Winsock directly Two code paths to maintain and wrap; error-prone non-blocking I/O
ENet, yojimbo, GameNetworkingSockets Provide reliability, fragmentation and sessions: the very parts the project asks us to design, and harder to document as our own protocol

Protocol design

  • Binary and explicit (planned, specified in the RFC): fixed-size little-endian fields written field by field by a BinaryWriter, never by copying structs, so padding and compiler layout never reach the wire and Windows, Linux and macOS clients agree.
  • Header on every datagram: magic number, protocol version, message type, sequence number, acknowledgement and ack bitfield, session id. A wrong magic or version is dropped immediately.
  • Inputs, not positions, from clients: a compact bitmask per tick. The server cannot be told "I am here", only "I pressed up", which removes a whole class of cheating.
  • Snapshots from the server at 20 Hz; clients interpolate remote entities between the two latest snapshots to hide the low rate and jitter.
  • Reliable events over UDP: spawns, deaths and disconnections must arrive. They carry sequence numbers; receivers acknowledge them with the last sequence plus a 32-bit bitfield of the previous ones, and the sender resends unacknowledged events after a timeout. This is the approach popularised by Glenn Fiedler's articles: no extra connection, and acknowledgements ride on traffic that is sent anyway.
  • Sequence numbers on 16 bits with wrap-around comparison (RFC 1982 serial number arithmetic), so the counter can wrap during long games without reordering bugs.
  • Datagrams of at most 1200 bytes, below common path MTUs, to avoid IP fragmentation (a lost fragment loses the whole datagram).

Threading

  • Network thread + game thread (planned): Asio runs on its own thread; the simulation runs at a fixed 60 Hz on another. They exchange messages through single-producer single-consumer lock-free queues, so the simulation never waits on a socket and a slow client cannot stall the tick.
  • Alternatives: one thread per client (does not scale, needs locks around the world state); everything on one thread with non-blocking sockets (simple but couples the tick rate to network activity).

Security

Threat Countermeasure
Malformed or truncated packets crashing the parser Every read is bounds-checked against the remaining bytes and returns an error instead of reading past the buffer; lengths are validated before allocation; unknown types are dropped
Oversized packets exhausting memory Fixed maximum datagram size; no allocation proportional to an untrusted length beyond that limit
Spoofed or foreign UDP traffic UDP is accepted only from the endpoint bound to a session token obtained through the TCP login; other datagrams are dropped
Flooding a server Per-IP packet rate limit; excess is dropped and logged, so the tick rate is unaffected
Cheating clients Server authority: clients send inputs only; movement, firing cooldowns, collisions and scores are computed on the server
One faulty client taking the server down Per-client error isolation: an exception while handling one client is caught and that client is disconnected
Vulnerable or tampered dependencies Dependencies pinned by version, commit or SHA-256 (CPM); GitHub Actions pinned by commit SHA

Detection and long-term monitoring

  • Fuzzing the packet parser with libFuzzer (planned) and dedicated malformed-packet unit tests.
  • AddressSanitizer and UndefinedBehaviorSanitizer on every pull request (in place), catching memory errors before they ship.
  • Static analysis with clang-tidy (bugprone, cert, cppcoreguidelines...) on every pull request (in place).
  • Server logs (planned): rejected packets, rate-limit hits and disconnections are logged with the source endpoint, so abuse is visible after the fact.
  • Degraded-network tests with netem (Linux) or clumsy (Windows) (planned): loss, duplication, 150 ms latency and 5 KB/s cap, as the subject requires.