Tunneling over HTTP/2
Opened this issue · 6 comments
@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
- There is no identifier sent by client, instead certificate pinning is used
ProxyFunc
takesio.Writer
andio.Reader
instead ofnet.Conn
ControlMessage
is changed, protocol is a string, it has extra fields, in general it follows some version of Forwarded HTTP Extension https://tools.ietf.org/html/rfc7239.
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
Hi @rjeczalik did you find time to have a closer look?
@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.
@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:
- Adding dynamic client management on server
- Moving client connections description (from
AllowedClient
) toClientConfig
so that, client informs server what it wants on connect - Migrating default proxy functions
- Adding WS support
Then I will add examples to cover your concerns as well.
Have an awesome weekend!
@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!
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.