route/lib/tunnel/spec.md

5.4 KiB

Specification

Naming conventions

  • server is listening to public connection and is responsible of routing public HTTP requests to clients.
  • client is a long running process, connected to a server and running on a local machine.
  • virtualHost is a virtual domain that maps a domain to a single client. i.e: arslan.koding.io is a virtualhost which is mapped to my client running on my local machine.
  • identifier is a secret token, which is not meant to be shared with others. An identifier is responsible of mapping a virtualhost to a client.
  • session is a single TCP connection which uses the library yamux. A session can be created either via yamux.Server() or yamux.Client
  • stream is a net.Conn compatible virtual connection that is multiplexed over the session. A session can have hundreds of thousands streams
  • control connection is a single stream which is used to communicate and handle messaging between server and client. It uses a custom protocol which is JSON encoded.
  • tunnel connection is a single stream which is used to proxy public HTTP requests from the server to the client and vice versa. A single tunnel connection is created for every single HTTP requests.
  • public connection is a connection from a remote machine to the server
  • ControlHandler is a http.Handler which listens to requests coming to /_controlPath_/. It's used to setup the initial session connection from client to server. And creates the control connection from this session. server and client, and also for all additional new tunnel. It literally captures the incoming HTTP request and hijacks it and converts it into RAW TCP, which then is used as the foundation for all yamux sessions.

Server

  1. Server is created with NewServer() which returns *Server, a http.Handler compatible type. Plug into any HTTP server you want. The root path "/" is recommended to listen and proxy any tunnels. It also listens to any request coming to ControlHandler
  2. Tunneling is based on virtual hosts. A virtual hosts is identified with an unique identifier. This identifier is the only piece that both client and server needs to known ahead. Think of it as a secret token.
  3. To add a virtual host, call server.AddHost(virtualHost, identifier). This step needs to be done from the server itself. This can be could manually or via custom auth based HTTP handlers, such as "/addhost", which adds virtualhosts and returns the identifier to the requester (in our case client)
  4. A DNS record and it's subdomains needs to point to a server, so it can handle virtual hosts, i.e: *.example.com is routed to a server, which can handle foo.example.com, bar.example.com, etc..

Client

  1. Client is created with NewClient(serverAddr, localAddr) which returns a *Client. Here serverAddr is the TCP address to the server. localAddr is the server in which all public requests are forwarded to. It's optional if you want it to be done dynamically
  2. Once a client is created, it starts with client.Start(identifier). Here identifier is needed upfront. This method creates the initial TCP connection to the server. It sends the identifier back to the server. This TCP connection is used as the foundation for yamux.Client(). Once a yamux session is established, we are able to use this single connection to have multiple streams, which are multiplexed over this one connection. A control connection is created and client starts to listen it. client.Start is blocking.

Control Handshake

  1. Client sends a handshakeRequest over the control connection stream
  2. The server sends back a handshakeResponse to the client over the control connection stream
  3. Once the client receives the handshakeResponse from the server, it starts to listen from the control connection stream.
  4. A control connection is json.Encoder/Decoder both for server and client

Tunnel creation

  1. When the server receives a public connection, it checks the HTTP host headers and retrieves the corresponding identifier from the given host.

  2. The server retrieves the control connection which was associated with this identifier and sends a ControlMsg message with the action RequestClientSession. This message is in the form of:

     type ControlMsg struct {
     	Action    Action            `json:"action"`
     	Protocol  TransportProtocol `json:"transportProtocol"`
     	LocalPort string            `json:"localPort"`
     }
    

    Here the LocalPort is read from the HTTP Host header. If absent a zero port is sent and client maps it to the local server running at port 80, unless the localAddr is specified in client.Start() method. Protocol is reserved for future features.

  3. The server immediately starts to listen(accept) to a new stream. This is blocking and it waits there.

  4. When the client receives the RequestClientSession message, it opens a new virtual TCP connection, a stream to the server.

  5. The server which was waiting for a new stream in step 3, establish the stream.

  6. The server copies the request over the stream to the client.

  7. The client copies the request coming from the server to the local server and copies back the result to the server

  8. The server reads the response coming from the client and returns back it to the public connection requester