route/lib/tunnel/spec.md

101 lines
5.4 KiB
Markdown
Raw Normal View History

2017-01-20 01:27:14 +00:00
# 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