document file-transfer protocol and Transit
This commit is contained in:
parent
187e14862d
commit
dd6e139c19
191
docs/file-transfer-protocol.md
Normal file
191
docs/file-transfer-protocol.md
Normal file
|
@ -0,0 +1,191 @@
|
|||
# File-Transfer Protocol
|
||||
|
||||
The `bin/wormhole` tool uses a Wormhole to establish a connection, then
|
||||
speaks a file-transfer -specific protocol over that Wormhole to decide how to
|
||||
transfer the data. This application-layer protocol is described here.
|
||||
|
||||
All application-level messages are dictionaries, which are JSON-encoded and
|
||||
and UTF-8 encoded before being handed to `wormhole.send` (which then encrypts
|
||||
them before sending through the rendezvous server to the peer).
|
||||
|
||||
## Sender
|
||||
|
||||
`wormhole send` has two main modes: file/directory (which requires a
|
||||
non-wormhole Transit connection), or text (which does not).
|
||||
|
||||
If the sender is doing files or directories, its first message contains just
|
||||
a `transit` key, whose value is a dictionary with `abilities-v1` and
|
||||
`hints-v1` keys. These are given to the Transit object, described below.
|
||||
|
||||
Then (for both files/directories and text) it sends a message with an `offer`
|
||||
key. The offer contains a single key, exactly one of (`message`, `file`, or
|
||||
`directory`). For `message`, the value is the message being sent. For `file`
|
||||
and `directory`, it contains a dictionary with additional information:
|
||||
|
||||
* `message`: the text message, for text-mode
|
||||
* `file`: for file-mode, a dict with `filename` and `filesize`
|
||||
* `directory`: for directory-mode, a dict with:
|
||||
* `mode`: the compression mode, currently always `zipfile/deflated`
|
||||
* `dirname`
|
||||
* `zipsize`: integer, size of the transmitted data in bytes
|
||||
* `numbytes`: integer, estimated total size of the uncompressed directory
|
||||
* `numfiles`: integer, number of files+directories being sent
|
||||
|
||||
The sender runs a loop where it waits for similar dictionary-shaped messages
|
||||
from the recipient, and processes them. It reacts to the following keys:
|
||||
|
||||
* `error`: use the value to throw a TransferError and terminates
|
||||
* `transit`: use the value to build the Transit instance
|
||||
* `answer`:
|
||||
* if `message_ack: ok` is in the value (we're in text-mode), then exit with success
|
||||
* if `file_ack: ok` in the value (and we're in file/directory mode), then
|
||||
wait for Transit to connect, then send the file through Transit, then wait
|
||||
for an ack (via Transit), then exit
|
||||
|
||||
The sender can handle all of these keys in the same message, or spaced out
|
||||
over multiple ones. It will ignore any keys it doesn't recognize, and will
|
||||
completely ignore messages that don't contain any recognized key. The only
|
||||
constraint is that the message containing `message_ack` or `file_ack` is the
|
||||
last one: it will stop looking for wormhole messages at that point.
|
||||
|
||||
## Recipient
|
||||
|
||||
`wormhole receive` is used for both file/directory-mode and text-mode: it
|
||||
learns which is being used from the `offer` message.
|
||||
|
||||
The recipient enters a loop where it processes the following keys from each
|
||||
received message:
|
||||
|
||||
* `error`: if present in any message, the recipient raises TransferError
|
||||
(with the value) and exits immediately (before processing any other keys)
|
||||
* `transit`: the value is used to build the Transit instance
|
||||
* `offer`: parse the offer:
|
||||
* `message`: accept the message and terminate
|
||||
* `file`: connect a Transit instance, wait for it to deliver the indicated
|
||||
number of bytes, then write them to the target filename
|
||||
* `directory`: as with `file`, but unzip the bytes into the target directory
|
||||
|
||||
## Transit
|
||||
|
||||
The Wormhole API does not currently provide for large-volume data transfer
|
||||
(this feature will be added to a future version, under the name "Dilated
|
||||
Wormhole"). For now, bulk data is sent through a "Transit" object, which does
|
||||
not use the Rendezvous Server. Instead, it tries to establish a direct TCP
|
||||
connection from sender to recipient (or vice versa). If that fails, both
|
||||
sides connect to a "Transit Relay", a very simple Server that just glues two
|
||||
TCP sockets together when asked.
|
||||
|
||||
The Transit object is created with a key (the same key on each side), and all
|
||||
data sent through it will be encrypted with a derivation of that key. The
|
||||
transit key is also used to derive handshake messages which are used to make
|
||||
sure we're talking to the right peer, and to help the Transit Relay match up
|
||||
the two client connections. Unlike Wormhole objects (which are symmetric),
|
||||
Transit objects come in pairs: one side is the Sender, and the other is the
|
||||
Receiver.
|
||||
|
||||
Like Wormhole, Transit provides an encrypted record pipe. If you call
|
||||
`.send()` with 40 bytes, the other end will see a `.gotData()` with exactly
|
||||
40 bytes: no splitting, merging, dropping, or re-ordering. The Transit object
|
||||
also functions as a twisted Producer/Consumer, so it can be connected
|
||||
directly to file-readers and writers, and does flow-control properly.
|
||||
|
||||
Most of the complexity of the Transit object has to do with negotiating and
|
||||
scheduling likely targets for the TCP connection.
|
||||
|
||||
Each Transit object has a set of "abilities". These are outbound connection
|
||||
mechanisms that the client is capable of using. The basic CLI tool (running
|
||||
on a normal computer) has two abilities: `direct-tcp-v1` and `relay-v1`.
|
||||
|
||||
* `direct-tcp-v1` indicates that it can make outbound TCP connections to a
|
||||
requested host and port number. "v1" means that the first thing sent over
|
||||
these connections is a specific derived handshake message, e.g. `transit
|
||||
sender HEXHEX ready\n\n`.
|
||||
* `relay-v1` indicates it can connect to the Transit Relay and speak the
|
||||
matching protocol (in which the first message is `please relay HEXHEX for
|
||||
side HEX\n`, and the relay might eventually say `ok\n`).
|
||||
|
||||
Future implementations may have additional abilities, such as connecting
|
||||
directly to Tor onion services, I2P services, WebSockets, WebRTC, or other
|
||||
connection technologies. Implementations on some platforms (such as web
|
||||
browsers) may lack `direct-tcp-v1` or `relay-v1`.
|
||||
|
||||
While it isn't strictly necessary for both sides to emit what they're capable
|
||||
of using, it does help performance: a Tor Onion-service -capable receiver
|
||||
shouldn't spend the time and energy to set up an onion service if the sender
|
||||
can't use it.
|
||||
|
||||
After learning the abilities of its peer, the Transit object can create a
|
||||
list of "hints", which are endpoints that the peer should try to connect to.
|
||||
Each hint will fall under one of the abilities that the peer indicated it
|
||||
could use. Hints have types like `direct-tcp-v1`, `tor-tcp-v1`, and
|
||||
`relay-v1`. Hints are encoded into dictionaries (with a mandatory `type` key,
|
||||
and other keys as necessary):
|
||||
|
||||
* `direct-tcp-v1` {hostname:, port:, priority:?}
|
||||
* `tor-tcp-v1` {hostname:, port:, priority:?}
|
||||
* `relay-v1` {hints: [{hostname:, port:, priority:?}, ..]}
|
||||
|
||||
For example, if our peer can use `direct-tcp-v1`, then our Transit object
|
||||
will deduce our local IP addresses (unless forbidden, i.e. we're using Tor),
|
||||
listen on a TCP port, then send a list of `direct-tcp-v1` hints pointing at
|
||||
all of them. If our peer can use `relay-v1`, then we'll connect to our relay
|
||||
server and give the peer a hint to the same.
|
||||
|
||||
`tor-tcp-v1` hints indicate an Onion service, which cannot be reached without
|
||||
Tor. `direct-tcp-v1` hints can be reached with direct TCP connections (unless
|
||||
forbidden) or by proxying through Tor. Onion services take about 30 seconds
|
||||
to spin up, but bypass NAT, allowing two clients behind NAT boxes to connect
|
||||
without a transit relay (really, the entire Tor network is acting as a
|
||||
relay).
|
||||
|
||||
The file-transfer application uses `transit` messages to convey these
|
||||
abilities and hints from one Transit object to the other. After updating the
|
||||
Transit objects, it then asks the Transit object to connect, whereupon
|
||||
Transit will try to connect to all the hints that it can, and will use the
|
||||
first one that succeeds.
|
||||
|
||||
The file-transfer application, when actually sending file/directory data,
|
||||
will close the Wormhole as soon as it has enough information to begin opening
|
||||
the Transit connection. The final ack of the received data is sent through
|
||||
the Transit object, as a UTF-8-encoded JSON-encoded dictionary with `ack: ok`
|
||||
and `sha256: HEXHEX` containing the hash of the received data.
|
||||
|
||||
|
||||
## Future Extensions
|
||||
|
||||
Transit will be extended to provide other connection techniques:
|
||||
|
||||
* WebSocket: usable by web browsers, not too hard to use by normal computers,
|
||||
requires direct (or relayed) TCP connection
|
||||
* WebRTC: usable by web browsers, hard-but-technically-possible to use by
|
||||
normal computers, provides NAT hole-punching for "free"
|
||||
* (web browsers cannot make direct TCP connections, so interop between
|
||||
browsers and CLI clients will either require adding WebSocket to CLI, or a
|
||||
relay that is capable of speaking/bridging both)
|
||||
* I2P: like Tor, but not capable of proxying to normal TCP hints.
|
||||
* ICE-mediated STUN/STUNT: NAT hole-punching, assisted somewhat by a server
|
||||
that can tell you your external IP address and port. Maybe implemented as a
|
||||
uTP stream (which is UDP based, and thus easier to get through NAT).
|
||||
|
||||
The file-transfer protocol will be extended too:
|
||||
|
||||
* "command mode": establish the connection, *then* figure out what we want to
|
||||
use it for, allowing multiple files to be exchanged, in either direction.
|
||||
This is to support a GUI that lets you open the wormhole, then drop files
|
||||
into it on either end.
|
||||
* some Transit messages being sent early, so ports and Onion services can be
|
||||
spun up earier, to reduce overall waiting time
|
||||
* transit messages being sent in multiple phases: maybe the transit
|
||||
connection can progress while waiting for the user to confirm the transfer
|
||||
|
||||
The hope is that by sending everything in dictionaries and multiple messages,
|
||||
there will be enough wiggle room to make these extensions in a
|
||||
backwards-compatible way. For example, to add "command mode" while allowing
|
||||
the fancy new (as yet unwritten) GUI client to interoperate with
|
||||
old-fashioned one-file-only CLI clients, we need the GUI tool to send an "I'm
|
||||
capable of command mode" in the VERSION message, and look for it in the
|
||||
received VERSION. If it isn't present, it will either expect to see an offer
|
||||
(if the other side is sending), or nothing (if it is waiting to receive), and
|
||||
can explain the situation to the user accordingly. It might show a locked set
|
||||
of bars over the wormhole graphic to mean "cannot send", or a "waiting to
|
||||
send them a file" overlay for send-only.
|
Loading…
Reference in New Issue
Block a user