diff --git a/docs/introduction.md b/docs/introduction.md new file mode 100644 index 0000000..7e2c255 --- /dev/null +++ b/docs/introduction.md @@ -0,0 +1,56 @@ +# Magic-Wormhole + +The magic-wormhole (Python) distribution provides several things: an +executable tool ("bin/wormhole"), an importable library (`import wormhole`), +the URL of a publically-available Rendezvous Server, and the definition of a +protocol used by all three. + +The executable tool provides basic sending and receiving of files, +directories, and short text strings. These all use `wormhole send` and +`wormhole receive` (which can be abbreviated as `wormhole tx` and `wormhole +rx`). It also has a mode to facilitate the transfer of SSH keys. This tool, +while useful on its own, is just one possible use of the protocol. + +The `wormhole` library provides an API to establish a bidirectional ordered +encrypted record pipe to another instance (where each record is an +arbitrary-sized bytestring). This does not provide file-transfer directly: +the "bin/wormhole" tool speaks a simple protocol through this record pipe to +negotiate and perform the file transfer. + +`wormhole/cli/public_relay.py` contains the URLs of a Rendezvous Server and a +Transit Relay which I provide to support the file-transfer tools, which other +developers should feel free to use for their applications as well. I cannot +make any guarantees about performance or uptime for these servers: if you +want to use Magic Wormhole in a production environment, please consider +running a server on your own infrastructure (just run `wormhole-server start` +and modify the URLs in your application to point at it). + +## The Magic-Wormhole Protocol + +There are several layers to the protocol. + +At the bottom level, each client opens a WebSocket to the Rendezvous Server, +sending JSON-based commands to the server, and receiving similarly-encoded +messages. Some of these commands are addressed to the server itself, while +others are instructions to queue a message to other clients, or are +indications of messages coming from other clients. All these messages are +described in "server-protocol.md". + +These inter-client messages are used to convey the PAKE protocol exchange, +then a "VERSION" message (which doubles to verify the session key), then some +number of encrypted application-level data messages. "client-protocol.md" +describes these wormhole-to-wormhole messages. + +Each wormhole-using application is then free to interpret the data messages +as it pleases. The file-transfer app sends an "offer" from the `wormhole +send` side, to which the `wormhole receive` side sends a response, after +which the Transit connection is negotiated (if necessary), and finally the +data is sent through the Transit connection. "file-transfer-protocol.md" +describes this application's use of the client messages. + +## The `wormhole` API + +Application use the `wormhole` library to establish wormhole connections and +exchange data through them. Please see `api.md` for a complete description of +this interface. + diff --git a/docs/server-protocol.md b/docs/server-protocol.md new file mode 100644 index 0000000..de52cd5 --- /dev/null +++ b/docs/server-protocol.md @@ -0,0 +1,235 @@ +# Rendezvous Server Protocol + +## Concepts + +The Rendezvous Server provides queued delivery of binary messages from one +client to a second, and vice versa. Each message contains a "phase" (a +string) and a body (bytestring). These messages are queued in a "Mailbox" +until the other side connects and retrieves them, but are delivered +immediately if both sides are connected to the server at the same time. + +Mailboxes are identified by a large random string. "Nameplates", in contrast, +have short numeric identities: in a wormhole code like "4-purple-sausages", +the "4" is the nameplate. + +Each client has a randomly-generated "side", a short hex string, used to +differentiate between echoes of a client's own message, and real messages +from the other client. + +## Application IDs + +The server isolates each application from the others. Each client provides an +"App Id" when it first connects (via the "BIND" message), and all subsequent +commands are scoped to this application. This means that nameplates +(described below) and mailboxes can be re-used between different apps. The +AppID is a unicode string. Both sides of the wormhole must use the same +AppID, of course, or they'll never see each other. The server keeps track of +which applications are in use for maintenance purposes. + +Each application should use a unique AppID. Developers are encouraged to use +"DNSNAME/APPNAME" to obtain a unique one: e.g. the `bin/wormhole` +file-transfer tool uses `lothar.com/wormhole/text-or-file-xfer`. + +## WebSocket Transport + +At the lowest level, each client establishes (and maintains) a WebSocket +connection to the Rendezvous Server. If the connection is lost (which could +happen because the server was rebooted for maintenance, or because the +client's network connection migrated from one network to another, or because +the resident network gremlins decided to mess with you today), clients should +reconnect after waiting a random (and exponentially-growing) delay. The +Python implementation waits about 1 second after the first connection loss, +growing by 50% each time, capped at 1 minute. + +Each message to the server is a dictionary, with at least a `type` key, and +other keys that depend upon the particular message type. Messages from server +to client follow the same format. + +`misc/dump-timing.py` is a debug tool which renders timing data gathered from +the server and both clients, to identify protocol slowdowns and guide +optimization efforts. To support this, the client/server messages include +additional keys. Client->Server messages include a random `id` key, which is +copied into the `ack` that is immediately sent back to the client for all +commands (and is ignored except for the timing tool). Some client->server +messages (`list`, `allocate`, `claim`, `release`, `close`, `ping`) provoke a +direct response by the server: for these, `id` is copied into the response. +This helps the tool correlate the command and response. All server->client +messages have a `server_tx` timestamp (seconds since epoch, as a float), +which records when the message left the server. Direct responses include a +`server_rx` timestamp, to record when the client's command was received. The +tool combines these with local timestamps (recorded by the client and not +shared with the server) to build a full picture of network delays and +round-trip times. + +All messages are serialized as JSON, encoded to UTF-8, and the resulting +bytes sent as a single "binary-mode" WebSocket payload. + +Servers can signal `error` for any message type it does not recognize. +Clients and Servers must ignore unrecognized keys in otherwise-recognized +messages. + +## 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, 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): + +* `current_cli_version`: 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 + 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 + 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`. + +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 +processed by the server). NAT-binding refresh messages are handled by the +WebSocket layer (by asking Autobahn to send a keepalive messages every 60 +seconds), and do not use `ping`. + +If any client->server command is invalid (e.g. it lacks a necessary key, or +was sent in the wrong order), an `error` response will be sent, This response +will include the error string in the `error` key, and a full copy of the +original message dictionary in `orig`. + +## Nameplates + +Wormhole codes look like `4-purple-sausages`, consisting of a number followed +by some random words. This number is called a "Nameplate". + +On the Rendezvous Server, the Nameplate contains a pointer to a Mailbox. +Clients can "claim" a nameplate, and then later "release" it. Each claim is +for a specific side (so one client claiming the same nameplate multiple times +only counts as one claim). Nameplates are deleted once the last client has +released it, or after some period of inactivity. + +Clients can either make up nameplates themselves, or (more commonly) ask the +server to allocate one for them. Allocating a nameplate automatically claims +it (to avoid a race condition), but for simplicity, clients send a claim for +all nameplates, even ones which they've allocated themselves. + +Nameplates (on the server) must live until the second client has learned +about the associated mailbox, after which point they can be reused by other +clients. So if two clients connect quickly, but then maintain a long-lived +wormhole connection, the do not need to consume the limited spare of short +nameplates for that whole time. + +The `allocate` command allocates a nameplate (the server returns one that is +as short as possible), and the `allocated` response provides the answer. +Clients can also send a `list` command to get back a `nameplates` response +with all allocated nameplates for the bound AppID: this helps the code-input +tab-completion feature know which prefixes to offer. The `nameplates` +response returns a list of dictionaries, one per claimed nameplate, with at +least an `id` key in each one (with the nameplate string). Future versions +may record additional attributes in the nameplate records. + +## Mailboxes + +The server provides a single "Mailbox" to each pair of connecting Wormhole +clients. This holds an unordered set of messages, delivered immediately to +connected clients, and queued for delivery to clients which connect later. +Messages from both clients are merged together: clients use the included +`side` identifier to distinguish echoes of their own messages from those +coming from the other client. + +Each mailbox is "opened" by some number of clients at a time, until all +clients have closed it. Mailboxes are kept alive by either an open client, or +a Nameplate which points to the mailbox (so when a Nameplate is deleted from +inactivity, the corresponding Mailbox will be too). + +The `open` command both marks the mailbox as being opened by the bound side, +and also adds the WebSocket as subscribed to that mailbox, so new messages +are delivered immediately to the connected client. There is no explicit ack +to the `open` command, but since all clients add a message to the mailbox as +soon as they connect, there will always be a `message` reponse shortly after +the `open` goes through. The `close` command provokes a `closed` response. + +The `close` command accepts an optional "mood" string: this allows clients to +tell the server (in general terms) about their experiences with the wormhole +interaction. The server records the mood in its "usage" record, so the server +operator can get a sense of how many connections are succeeding and failing. +The moods currently recognized by the Rendezvous Server are: + +* happy (default): the PAKE key-establishment worked, and the client saw a + valid encrypted message from its peer +* lonely: the client gave up without hearing anything from its peer +* scary: the client saw an invalid encrypted message from its peer, + indicating that either the wormhole code was typed in wrong, or an attacker + tried (and failed) to guess the code +* errory: the client encountered some other error: protocol problem or + internal error + +The server will also record "pruney" if it deleted the mailbox due to +inactivity, or "crowded" if more than two sides tried to access the mailbox. + +When clients use the `add` command to add a client-to-client message, they +will put the body (a bytestring) into the command as a hex-encoded string in +the `body` key. They will also put the message's "phase", as a string, into +the `phase` key. See client-protocol.md for details about how different +phases are used. + +When a client sends `open`, it will get back a `message` response for every +message in the mailbox. It will also get a real-time `message` for every +`add` performed by clients later. These `message` responses include "side" +and "phase" from the sending client, and "body" (as a hex string, encoding +the binary message body). The decoded "body" will either by a random-looking +cryptographic value (for the PAKE message), or a random-looking encrypted +blob (for the VERSION message, as well as all application-provided payloads). +The `message` response will also include `id`, copied from the `id` of the +`add` message (and used only by the timing-diagram tool). + +The Rendezvous Server does not de-duplicate messages, nor does it retain +ordering: clients must do both if they need to. + +## All Message Types + +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) list {} -> nameplates +* S->C nameplates {nameplates: [{id: str},..]} +* (C->S) allocate {} -> allocated +* S->C allocated {nameplate:} +* (C->S) claim {nameplate:} -> claimed +* S->C claimed {mailbox:} +* (C->S) release {nameplate:?} -> released +* S->C released +* (C->S) open {mailbox:} +* (C->S) add {phase: str, body: hex} -> message (to all connected clients) +* S->C message {side:, phase:, body:, id:} +* (C->S) close {mailbox:?, mood:?} -> closed +* S->C closed +* S->C ack +* (C->S) ping {ping: int} -> ping +* S->C pong {pong: int} +* S->C error {error: str, orig:} + +# Persistence + +The server stores all messages in a database, so it should not lose any +information when it is restarted. The server will not send a direct +response until any side-effects (such as the message being added to the +mailbox) being safely committed to the database. + +The client library knows how to resume the protocol after a reconnection +event, assuming the client process itself continues to run. + +Clients which terminate entirely between messages (e.g. a secure chat +application, which requires multiple wormhole messages to exchange +address-book entries, and which must function even if the two apps are never +both running at the same time) can use "Journal Mode" to ensure forward +progress is made: see "api.md" (Journal Mode) for details.