start documenting the protocols
This commit is contained in:
parent
ddb83e9d59
commit
9314c6918f
56
docs/introduction.md
Normal file
56
docs/introduction.md
Normal file
|
@ -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.
|
||||
|
235
docs/server-protocol.md
Normal file
235
docs/server-protocol.md
Normal file
|
@ -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.
|
Loading…
Reference in New Issue
Block a user