diff --git a/specs/index.rst b/specs/index.rst index b07943cb7..a2a551f71 100644 --- a/specs/index.rst +++ b/specs/index.rst @@ -11,3 +11,6 @@ Specifications :ref:`localdisco-v3` The protocol used for local discovery within a broadcast domain (LAN). + +:ref:`relay-v1` + The protocol used for connection relaying. diff --git a/specs/relay-v1.rst b/specs/relay-v1.rst new file mode 100644 index 000000000..1db6a34eb --- /dev/null +++ b/specs/relay-v1.rst @@ -0,0 +1,415 @@ +.. _relay-v1: + +Relay Protocol v1 +================= + +What is a relay? +---------------- + +Relay is a service which relays data between two *devices* which are not able to +connect to each other directly otherwise. This is usually due to both devices +being behind a NAT and neither side being able to open a port which would +be directly accessible from the internet. + +A relay was designed to relay BEP protocol, hence the reliance on device ID's +in the protocol spec, but at the same time it is general enough that could be +reused by other protocols or applications, as the data transferred between two +devices which use a relay is completely obscure and does not affect the +relaying. + +Operation modes +--------------- + +Relay listens on a single TCP socket, but has two different connection modes, +where a connection mode is a predefined set of messages which the relay and +the device are expecting to exchange. + +The first mode is the `protocol` mode which allows a client to interact +with the relay, for example join the relay, or request to connect to a device, +given it is available on the relay. Similarly to BEP, protocol mode requires +the device to connect via TLS using a strong suite of ciphers (same as BEP), +which allows the relay to verify and derive the identity (Device ID) of the +device. + +The second mode is the `session` mode which after a few initial messages +connects two devices directly to each other via the relay, and is a plain-text +protocol, which for every byte written by one device, sends the same set of +bytes to the other device and vica versa. + +Identifying the connection mode +------------------------------- + +Because both connection modes operate over the same single socket, a method +of detecting the connection mode is required. + +When a new client connects to the relay, the relay checks the first byte +that the client has sent, and if that matches 0x16, that implies to us that +the connection is a protocol mode connection, due to 0x16 being the first byte +in the TLS handshake, and only protocol mode connections use TLS. + +If the first byte is not 0x16, then we assume that the connection is a session +mode connection. + +Protocol mode +------------- + +Protocol mode uses TLS and protocol name defined by the TLS header should be +`bep-relay`. + +Protocol mode has two submodes: +1. Permanent protocol submode - Joining the relay, and waiting for messages from +the relay asking to connect to some device which is interested in having a +session with you. +2. Temporary protocol submode - Only used to request a session with a device +which is connected to the relay using the permanent protocol submode. + + +Permanent protocol submode +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A permanent protocol submode begins with the client sending a JoinRelayRequest +message, which the relay responds to with either a ResponseSuccess or +ResponseAlreadyConnected message if a client with the same device ID already +exists. + +After the client has joined, no more messages are exchanged apart from +Ping/Pong messages for general connection keep alive checking. + +From this point onwards, the client stand-by's and waits for SessionInvitation +messages from the relay, which implies that some other device is trying to +connect with you. SessionInvitation message contains the unique session key +which then can be used to establish a connection in session mode. + +If the client fails to send a JoinRelayRequest message within the first ping +interval, the connection is terminated. +If the client fails to send a message (even if its a ping message) every minute +(by default), the connection is terminated. + +Temporary protocol submode +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A temporary protocol submode begins with ConnectRequest message, to which the +relay responds with either ResponseNotFound if the device the client it is after +is not available, or with a SessionInvitation, which contains the unique session +key which then can be used to establish a connection in session mode. + +The connection is terminated immediately after that. + +Example Exchange +~~~~~~~~~~~~~~~~ + +Client A - Permanent protocol submode +Client B - Temporary protocol submode + +=== ======================= ====================== ===================== + # Client (A) Relay Client (B) +=== ======================= ====================== ===================== + 1 JoinRelayRequest-> + 2 <-ResponseSuccess + 3 Ping-> + 4 <-Pong + 5 <-ConnectRequest(A) + 6 SessionInvitation(A)-> + 7 <-SessionInvitation(B) + 8 (Disconnects) + 9 Ping-> + 10 <-Pong + 11 Ping-> + 12 <-Pong +=== ======================= ====================== ===================== + + +Session mode +------------ + +The first and only message the client sends in the session mode is the +SessionInvitation message which contains the session key identifying which +session you are trying to join. +.. The relay responds with one of the following Response messages: + 1. ResponseNotFound - Session key is invalid + 2. ResponseAlreadyConnected - Session is full (both sides already connected) + 3. ResponseSuccess - You have successfully joined the session + +After the successful response, all the bytes written and received will be +relayed between the two devices in the session directly. + +Example Exchange +~~~~~~~~~~~~~~~~ + +Client A - Permanent protocol mode +Client B - Temporary protocol mode + +=== ======================= ====================== ===================== + # Client (A) Relay Client (B) +=== ======================= ====================== ===================== + 1 SessionInvitation(A)-> + 2 <-ResponseSuccess + 3 Data-> (Buffers data) + 4 Data-> (Buffers data) + 5 <-SessionInvitation(B) + 6 ResponseSuccess-> + 7 Relays data -> + 8 Relays data -> + 9 <-Relays data <-Data +=== ======================= ====================== ===================== + +Messages +-------- + +All messages are preceeded by a header message. Header message contains the +magic value 0x9E79BC40, message type integer, and message length. + +.. warning:: + + Some messages have no content, apart from the implied header which allows + us to identify what type of message it is. + + +Header structure +~~~~~~~~~~~~~~~~ + +:: + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Magic | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Message Type | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Message Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + struct Header { + unsigned int Magic; + int MessageType; + int MessageLength; + } + +Ping message (Type = 0) +~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + struct Ping { + } + +Pong message (Type = 1) +~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + struct Pong { + } + +JoinRelayRequest message (Type = 2) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + struct JoinRelayRequest { + } + +JoinSessionRequest message (Type = 3) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length of Key | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / / + \ Key (variable length) \ + / / + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + struct JoinSessionRequest { + opaque Key<32>; + } + +: Key + This is a unique random session key generated by the relay server. It is + used to identify which session you are trying to connect to. + + +Response message (Type = 4) +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Code | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length of Message | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / / + \ Message (variable length) \ + / / + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + struct Response { + int Code; + string Message<>; + } + +: Code + An integer representing the status code. +: Message + Message associated with the code. + +.. Protocol defined responses: + 1. ResponseSuccess = Response{0, "success"} + 2 .ResponseNotFound = Response{1, "not found"} + 3. ResponseAlreadyConnected = Response{2, "already connected"} + 4. ResponseInternalError = Response{99, "internal error"} + 5. ResponseUnexpectedMessage = Response{100, "unexpected message"} + +ConnectRequest message (Type = 5) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length of ID | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / / + \ ID (variable length) \ + / / + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + struct ConnectRequest { + opaque ID<32>; + } + +: ID + Device ID to which the client would like to connect. + + +SessionInvitation message (Type = 6) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length of From | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / / + \ From (variable length) \ + / / + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length of Key | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / / + \ Key (variable length) \ + / / + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length of Address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / / + \ Address (variable length) \ + / / + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 0x0000 | Port | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Server Socket (V=0 or 1) |V| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + struct SessionInvitation { + opaque From<32>; + opaque Key<32>; + opaque Address<32>; + unsigned int Port; + bool ServerSocket; + } + +: From + Device ID identifying who you will be connecting with. +: Key + A unique random session key generated by the relay server. It is used to + identify which session you are trying to connect to. +: Address + An optional IP address on which the relay server is expecting you to + connect, in order to start a connection in session mode. + Empty/all zero IP should be replaced with the relay's public IP address that + was used when establishing the protocol mode connection. +: Port + An optional port on which the relay server is expecting you to connect, + in order to start a connection in session mode. +: Server Socket + Because both sides connecting to the relay use the client side of the socket, + and some protocols behave differently depending if the connection starts on + the server side or the client side, this boolean indicates which side of the + connection this client should assume it's getting. The value is inverted in + the invitation which is sent to the other device, so that there is always + one client socket, and one server socket. + +How syncthing uses relays, and general security +----------------------------------------------- + +In the case of Syncthing and BEP, when two devices connect via relay, they +start their standard TLS connection encapsulated within the relay's plain-text +session connection, effectively upgrading the plain-text connection to a TLS +connection. + +Even though the relay could be used for man-in-the-middle attack, using TLS +at the application/BEP level ensures that all the traffic is safely encrypted, +and is completely meaningless to the relay. Furthermore, the secure suite of +ciphers used by BEP provides forward secrecy, meaning that even if the relay +did capture all the traffic, and even if the attacker did get their hands on the +device keys, they would still not be able to recover/decrypt any traffic which +was transported via the relay. + +After establishing a relay session, syncthing looks at the SessionInvitation +message, and depending which side it has received, wraps the raw socket in +either a TLS client socket or a TLS server socket depending on the ServerSocket +boolean value in the SessionInvitation, and starts the TLS handshake. + +From that point onwards it functions exactly the same way as if syncthing was +establishing a direct connection with the other device over the internet, +performing device ID validation, and full TLS encryption, and provides the same +security properties as it would provide when connecting over the internet. + +Examples of Strong Cipher Suites +-------------------------------- + +====== =========================== ================================== +ID Name Description +====== =========================== ================================== +0x009F DHE-RSA-AES256-GCM-SHA384 TLSv1.2 DH RSA AESGCM(256) AEAD +0x006B DHE-RSA-AES256-SHA256 TLSv1.2 DH RSA AES(256) SHA256 +0xC030 ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 ECDH RSA AESGCM(256) AEAD +0xC028 ECDHE-RSA-AES256-SHA384 TLSv1.2 ECDH RSA AES(256) SHA384 +0x009E DHE-RSA-AES128-GCM-SHA256 TLSv1.2 DH RSA AESGCM(128) AEAD +0x0067 DHE-RSA-AES128-SHA256 TLSv1.2 DH RSA AES(128) SHA256 +0xC02F ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 ECDH RSA AESGCM(128) AEAD +0xC027 ECDHE-RSA-AES128-SHA256 TLSv1.2 ECDH RSA AES(128) SHA256 +====== =========================== ================================== +