Golang implementation of the Zephyr protocol
BSD 3-clause, see accompanying LICENSE file.
Required:
- Go 1.4+
NB: the
splice
patch is known to apply cleanly against Go 1.5, but may or may not work well with subsequent versions.
Optional:
- gockerize
To build a minimal docker container
Also useful to enable support for zero-copy via thesplice
syscall, which currently requires a patch to the standard library.
./valkyrie [-port <port>] [-splice=<true|false>]
port
: listening portsplice
: whether to enable use of thesplice
syscall
NB: do NOT use if the binary wasn't built with a patched standard library as it will significantly increase memory footprint and degrade performance due to suboptimal buffer allocation strategy
The Zephyr protocol is designed to allow secure communication between clients that are not able to establish a direct TCP connection.
Peer discovery and connection negotiation are not part of the protocol and must happen out-of-band. For instance, AeroFS performs a 3-way handshake over SSMP.
When a peer opens a connection to a Zephyr relay, it is assigned a random 4 bytes identifier. The relay informs the peer of this assignment through a single packet of the following form:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x82 | 0x96 | 0x44 | 0xa1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x00 | 0x00 | 0x00 | 0x04 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Assigned Zephyr Identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The first four bytes correspond to the Zephyr protocol magic header.
The second four bytes correspond to the length of the payload, which is always 4 in this case, as a 32bit integer encoded in big-endian.
The last four bytes correspond to the payload, in this case Zephyr identifier assigned by the relay server to the peer, to be used in a subsequent binding request.
To establish a bidirectional stream, an act referred to as "binding" in the Zephyr terminology, two peers must exchange their respective Zephyr identifiers out-of-band.
Having exchanged ZIDs, each peer must send a bind request to the relay:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x82 | 0x96 | 0x44 | 0xa1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x00 | 0x00 | 0x00 | 0x04 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Requested Zephyr Identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The first four bytes correspond to the Zephyr protocol magic header.
The second four bytes correspond to the length of the payload, which is always 4 in this case, as a 32bit integer encoded in big-endian.
The last four bytes correspond to the payload, in this case the Zephyr identifier that the client is attempting to bind to.
A Zephyr server MUST close any connection that fails to send a valid bind request within a reasonable amount of time.
A Zephyr server MUST immediately close any connection through which it receives a bind request:
- to an unknown peer
- to a peer that is already bound
- to the caller itself (i.e. a self-bind request)
A Zephyr server MUST close any connection through which it receives a valid bind request that is not matched by the corresponding peer within a reasonable amount of time.
A zephyr server MUST NOT start forwarding packets until a successful binding is achieved, that is until both peers have sent matching bind requests.
Once two connections are bound, the zephyr server simply forwards packets in both direction, oblivious to their contents. Crucially, this allows peers to communicate with end-to-end encryption, for instance by establishing a mutually-authenticated TLS session.
When a connection is closed, the zephyr server MUST immediately close any bound connection.