koding / tunnel

Tunnel proxy package in Go
BSD 3-Clause "New" or "Revised" License
314 stars 70 forks source link

Tunneling over HTTP/2 #37

Open mmatczuk opened 7 years ago

mmatczuk commented 7 years ago

@rjeczalik I took your tip and did an experiment to replace yamux with HTTP/2. At first I wanted to make it a small change but it turned out that there were many incompatibilities so I decided to start fresh.

I did a POC that can proxy HTTP and TCP and uses ProxyFunc design (no default functions yet). It turns out that the implementation can be really short and concise with http2 package. Server is ~300LOC and client ~100LOC (mostly consumed by structs and comments). The code is available at https://github.com/mmatczuk/h2tun.

Performance using HTTP/2 is slightly better than using yamux but I think the key benefit is improved stability, you can see a report that I wrote https://github.com/mmatczuk/h2tun/blob/master/benchmark/report/README.md.

This implementation follows a similar design that the current tunnel, I'd like to highlight some changes here

It's a POC, some things that exists in the tunnel should be migrated to make it truly usable. I also have some new ideas you can see in https://github.com/mmatczuk/h2tun/blob/master/TODO.md.

Please let me know what do you think. I'd be really grateful for a review if find some time.

Cheers.

cc @cihangir

rjeczalik commented 7 years ago

aww

@mmatczuk This is so cool! I will definitely take a look soon!

mmatczuk commented 7 years ago

Hi @rjeczalik did you find time to have a closer look?

rjeczalik commented 7 years ago

@mmatczuk Yes sir, took a look. Really nice implementation, however I'm wondering how it'd be possible to use raw TCP/WebSocket without wrapping the stream with TLS handshake. In particular how we could e.g. ssh tunnelserver:12345, where :12345 is a server-side TCP listener that tunnels the connection to client.

mmatczuk commented 7 years ago

@rjeczalik thanks for the effort. Addressing your concerns the problem might be that TLS client sees that server certificate and host do not match... On that front TCP and WS are quite a different beasts.

For WS you can run Server on any http server as it's http.Handler so you could fallback to plain http. The way it works with server on https thought is that you have tree separate TLS connection not one end-to-end (if you want ent-to-end TCP proxing can do the job). When Server proxies a request it's agnostic of type of the connection to the server, except that you can check the scheme. Client must open a TLS connection to the end service (if it's required). In practice you can add TLS to services that run on http or take https off, this is a standard stuff that http proxies can do.

SSH should work just fine as it seems not to care i.e.

mmatczuk@shark:~$ ssh ubuntu@foobar
The authenticity of host 'foobar (XX.XX.XXX.XXX)' can't be established.
ECDSA key fingerprint is c2:6b:94:48:51:f6:8b:f6:32:c8:9a:cd:43:17:48:bd.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'foobar' (ECDSA) to the list of known hosts.

On server you do net.Listen("tcp", ":12345"), than you proxy anything regardless if it's TLS or not and on client side you do a net.Dial to TLS backend so you get a TCP connection and then proxy TLS handshake. As you know TLS wraps TCP connections (see listening and wrapping).

Anyway I'd like to start making h2tun production ready (on elementary level) by:

Then I will add examples to cover your concerns as well.

Have an awesome weekend!

rjeczalik commented 7 years ago

@mmatczuk Got it, on server side we use plain net.Listener that listens on TCP, we accept connection on public endpoint and multiplex the stream over http2 connection to client, and client proxies raw tcp back to the service. For WS we use http1, upgrade and again stream hijacked TCP over the same http2 connection. I've noticed piece of code that initializes http.Client with only http2.Transport so I thought we do not use http1. All clear and sound. I'm wondering, while we're at a major rewrite, whether we could change the proto a bit and instead of returning just VirtualHostname we could return full URI (so it'd be possible to support path-routing #).

Have an awesome weekend!

Likewise!

mmatczuk commented 7 years ago

change the proto a bit and instead of returning just VirtualHostname we could return full URI

This is done to some extent see here, this allows for Client to do the dispatching. What I'd like to do, however, is to enable Server to do dispatching to different clients as well, see point 8 in TODO. The final solution would be to replace host with collection of urlprefix- like expressions.

Note that host and URL path are separated in h2tunc control message (unlike in URI) this is to remove port. The current koding tunnel has some difficulties if you do not run server on default port or if you simultaneously run on http and https. H2tun ignores port as server is agnostic of how it's being run and client should know nothing about it.