From a2cba08a99f5a3d0f46195be767c057f21f87332 Mon Sep 17 00:00:00 2001 From: piegames Date: Thu, 29 Apr 2021 22:57:58 +0200 Subject: [PATCH] Upgrade welcome message 1. It promotes a list of known relay servers to use 2. It gives a proof of work challenge to solve The list of known relay servers allows us to not always use the same one, distributing both cost and availability. It also allows to reboot single relay servers for maintenance without bringing the whole service down. Each client should pick one or two, this will result in up to four relays being pinged during relay initialization (remember, there are two clients who both pick at random). Four connection attempts should be enough to give a sufficiently high probability of finding one that is up. The proof of work challenge is added in the attempt to protect the rendezvous server from a DOS overload. It is a single point of failure, and we want to be prepared if some dumb-ass spams it with connection attempts for some reason. The challenge is made in a way that its difficulty is configurable by the server, thus it can dynamically adapt at the load situation. It is also designed in a way to be as light on the server resource as possible (also in order to not accidentally introduce a new DOS vector): - The server can use the first 8 bytes of the challenge as running counter, only that needs to be stored. A binary heap gives - All other required information are stored and forwarded by the client, a MAC protects against clients that make up their challenges. --- server-protocol.md | 72 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/server-protocol.md b/server-protocol.md index ca8b8e7..d58f178 100644 --- a/server-protocol.md +++ b/server-protocol.md @@ -70,28 +70,72 @@ messages. Clients must ignore unrecognized message types from the Server. ## Connection-Specific (Client-to-Server) Messages -The first thing each client sends to the server, immediately after the -WebSocket connection is established, is a `bind` message. This specifies the -AppID and side (in keys `appid` and `side`, respectively) that all subsequent -messages will be scoped to. While technically each message could be -independent (with its own `appid` and `side`), I thought it would be less -confusing to use exactly one WebSocket per logical wormhole connection. - The first thing the server sends to each client is the `welcome` message. This is intended to deliver important status information to the client that -might influence its operation. The Python client currently reacts to the -following keys (and ignores all others): +might influence its operation. Clients should look out for the following fields, +and handle them accordingly, if present: -* `current_cli_version`: prompts the user to upgrade if the server's +* `current_cli_version`: *(deprecated)* prompts the user to upgrade if the server's advertised version is greater than the client's version (as derived from the git tag) -* `motd`: prints this message, if present; intended to inform users about +* `motd`: This message is intended to inform users about performance problems, scheduled downtime, or to beg for donations to keep - the server running -* `error`: causes the client to print the message and then terminate. If a + the server running. Clients should print it or otherwise display prominently + to the user. +* `error`: The client should show this message to the user and then terminate. If a future version of the protocol requires a rate-limiting CAPTCHA ticket or other authorization record, the server can send `error` (explaining the requirement) if it does not see this ticket arrive before the `bind`. +* `relays`: An advertizement list of relay servers. It is a JSON list of which each + entry may look like this: + ```json + { + "url": "tcp:myrelay.example.org:12345", + "country": "Italy", + "continent": "EU", + } + ``` + The only mandatory key is `url`, al others are optional information to help the client + choose an appropriate one. Further keys may be added in the future. Clients must not + expect the protocol to be `tcp` (expect websockets support in the future). Clients + should make a preselection of viable relay servers (which may include entries from other + sources as well), and randomly select one or two. +* `proof_of_work`: A proof of work task to mitigate DOS attacks. The difficulty is dictated + by the server based on its current load. The entry looks like this: + ```json + { + "challenge": "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABXXXX", + "sha256": "a66cdd7def34cb0267b83d21c37c429af0a15dcd00465da22402dab9f1cbf10e", + "mac": "" + } + ``` + The challenge contains exactly 32 hex-encoded bytes, of which the last nibbles are censored ('X'), + and the `sha256sum` of the uncensored challenge. The goal is to find the complete `challenge` with + the given hash. + +The first thing each client sends to the server, immediately after receiving +the welcome message, is a `bind` message. This specifies the +AppID and side (in keys `appid` and `side`, respectively) that all subsequent +messages will be scoped to. While technically each message could be +independent (with its own `appid` and `side`), I thought it would be less +confusing to use exactly one WebSocket per logical wormhole connection. + +Due to backwards compatibity, clients are not forced to give a proof of work. +However, it is strongly recommended. Here's how a full "bind" message may look like: + +```json +{ + "type": "bind", + "appid": "lothar.com/wormhole/text-or-file-xfer", + "side": "123456", + "proof_of_work": { + "challenge": "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABXXXX", + "sha256": "a66cdd7def34cb0267b83d21c37c429af0a15dcd00465da22402dab9f1cbf10e", + "mac": "", + "response": "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF", + }, +} +``` A `ping` will provoke a `pong`: these are only used by unit tests for synchronization purposes (to detect when a batch of messages have been fully @@ -201,7 +245,7 @@ This lists all message types, along with the type-specific keys for each (if any), and which ones provoke direct responses: * S->C welcome {welcome:} -* (C->S) bind {appid:, side:} +* (C->S) bind {appid:, side:, proof_of_work: {..},} * (C->S) list {} -> nameplates * S->C nameplates {nameplates: [{id: str},..]} * (C->S) allocate {} -> allocated