Provided by: syncthing_0.14.43+ds1-6_amd64 bug

NAME

       syncthing-relay - 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  it’s  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 │ JoinSessionRequest(A)-> │                   │                         │
              ├──┼─────────────────────────┼───────────────────┼─────────────────────────┤
              │2 │                         │ <-ResponseSuccess │                         │
              ├──┼─────────────────────────┼───────────────────┼─────────────────────────┤
              │3 │ Data->                  │ (Buffers data)    │                         │
              ├──┼─────────────────────────┼───────────────────┼─────────────────────────┤
              │4 │ Data->                  │ (Buffers data)    │                         │
              ├──┼─────────────────────────┼───────────────────┼─────────────────────────┤
              │5 │                         │                   │ <-JoinSessionRequest(B) │
              ├──┼─────────────────────────┼───────────────────┼─────────────────────────┤
              │6 │                         │ ResponseSuccess-> │                         │
              ├──┼─────────────────────────┼───────────────────┼─────────────────────────┤
              │7 │                         │ Relays data ->    │                         │
              ├──┼─────────────────────────┼───────────────────┼─────────────────────────┤
              │8 │                         │ Relays data ->    │                         │
              ├──┼─────────────────────────┼───────────────────┼─────────────────────────┤
              │9 │                         │ <-Relays data     │ <-Data                  │
              └──┴─────────────────────────┴───────────────────┴─────────────────────────┘

MESSAGES

       All messages are preceded 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.

   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          │
                   └───────┴─────────────────────────────┴──────────────────────────┘

AUTHOR

       The Syncthing Authors

COPYRIGHT

       2015, The Syncthing Authors