The diagram above gives a quick overview of what happens on the wire when a diglett client connects to the server.
Next we will explain the format of each frame that goes over the wire.
The handshake frame is built as follows
magic | version | key |
---|---|---|
4 bytes | 1 byte | 33 bytes |
- The
magic
is a 4 bytes that always carries the value0x6469676c
is used to identify that this a valid diglett connection. - The
version
is a 1 byte that is set for0x01
for now (version 1). This might change in the future if needed. - The
key
segment is a 33 bytes long section that carries thePublic Key
of the handshake sender. This key is always aSecp256k1
public key.
When the client connects, it starts by sending a handshake frame as defined before. The server replies immediately by sending back also a handshake frame but carries the server public key instead.
The moment the handshake response is received, both the client and the server agree to a shared key using ecdh
algorithm. The shared key
generated is used from this point forward
to encrypt the traffic (both ways) using the chacha20
symmetric encryption algorithm.
NOTE: because the client and server exchange keys on the wire, there is no way to validate the server identity hence the system can be prone to
man in the middle
attacks. This can change in the future to fetch server public key over https only.
After the handshake the wire will only use ONE frame format which is as follows
kind | id | size | payload |
---|---|---|---|
1 byte | 4 bytes | 2 bytes | variable size |
- the
kind
is one byte, more details on this later. - the
id
is a 4 bytes identification of the frame. The id itself is split into 2 partsregistration
is the id of the registration that this frame belongs to (as per the registration step above in the sequence diagram)stream
is the id of theopen connection
which identifies one client connection to thebackend
service.- This means that the
id
is not unique because many frames can still belong to the same client connection
- the
size
is 2 bytes which is the size of the payload. this can be0
for most control frames.
Kind tells the server and the client what kind of payload is carried by this frame. Currently we have those kinds
- Ok = 0, is a response to a previous control message that donates success
- Error = 1, is a response to a previous control message that donates failure, the payload then carries the error message
- Register = 2, is a
register
request as per the sequence diagram. Theid
then carries only the registration id in the higher order 2 bytes. The payload then carries the name. - FinishRegister = 3, as per the sequence diagram this need to be sent after all registration messages. no payload
- Payload = 4, carries actual stream data for clients, the id in this case always carries a unique ID that identifies this client connection the ID is constructed to that it holds (registration, port number) of the client that makes 4 bytes in total
- Close = 5, close a stream, the id then holds the stream (client connection) to close
- Terminate = 6, terminate should terminate the agent, has no payload, also is never used in code so far
- Login = 7, login request as per the sequence diagram, payload then carries the token
Note: after sending
finish-registration
all following frames on both directions on the wire can only bepayload
orclose
frames.
If you understand the description of the wire protocol above, then the operation of the server and client becomes simple and it goes as follows:
- client initiate connection, they exchange keys, and start an encrypted stream.
- client login, by sending a token
- server reply with OK, or Error in case authentication error
- client send a register (id, name) we only support one register call for now. Id is normally 0, name is the name of the subdomain to register
- server reply with OK, or Error in case of authorization error
- once client send registration finish, the server then start listening on a random port. (for each registration) we call this
listen port
from now on- the
listening port
connections are basically forwarded (multiplexed) over the agent connection to agent side
- the
- when a client connects to the
listen port
and have local port as know asclient socket
and start writing data, a payload frame(s) are sent with the data to the agent.- all data coming from the
client
will be ofpayload
kind, andid
as(<registration>, <local socket>)
this will be the frame id. for example0x00008269
where registration id is 0 andclient socket
is33385
- that ID from now on is called
stream id
- all data coming from the
- when the agent receive a
payload
of an unknown id. A new connection is established to the backend. the connection is then mapped to the stream id. - the agent also takes care of copying any data over from that backend connection to the server. using the same
stream id
. - when the server receives any
payload
frame from the agent with that stream id the data is written back to theclient socket
. - If any of the sides loses the open socket for that stream, a control
close
type is send to the other end so it makes sure the connection is closed and cleaned up.