h2rev2

package module
v0.0.0-...-95e0258 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 13, 2022 License: Apache-2.0 Imports: 18 Imported by: 2

README

h2rev2: Reverse proxy using http2 reverse connections

This package is based on https://pkg.go.dev/golang.org/x/build/revdial/v2 , however, it uses HTTP2 to multiplex the reverse connections over HTTP2 streams, instead of using TCP connections.

This package implements a Dialer and Listener that works together to create reverse connections.

The motivation is that sometimes you want to run a server on a machine deep inside a NAT. Rather than connecting to the machine directly (which you can't, because of the NAT), you have the sequestered machine connect out to a public machine.

The public machine runs the Dialer and the NAT machine the Listener. The Listener opens a connections to the public machine that creates a reverse connection, so the Dialer can use this connection to reach the NATed machine.

Typically, you would install a reverse proxy in the public machine and use the Dialer as a transport.

You can also install another reverse proxy in the NATed machine, to expose internal servers through this double reverse proxy chain.

diagram

How to use it

Public Server

The Dialer runs in a public server, it exposes one http handler that handles 2 URL paths:

  • /revdial?key=id establish reverse connections and queue them so it can be consumed by the dialer
  • /proxy/id/ proxies the through the reverse connection identified by id

A common way to use it is:

// initialize your middleware
mux := http.NewServeMux()

// add the reverse dialer to your router
dialer := h2rev2.NewDialer()
defer dialer.Close()
mux.Handle("/reverse/connections/", dialer)
Internal Server

The Listener runs in the server that is not accessible from outside, it has to be able to connect to the server with the Dialer though.

An example on how to use it, is to use it as a reverse proxy of an internal network:

// Create a client that is able to connect to the Dialer on the public server.
// You may want to use custom certificates for security.
caCert, err := ioutil.ReadFile("cert.crt")
if err != nil {
        log.Fatalf("Reading server certificate: %s", err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig = &tls.Config{
        RootCAs:            caCertPool,
        InsecureSkipVerify: false,
}
client := &http.Client{}
// remember this only works using http2 :-)
client.Transport = &http2.Transport{
        TLSClientConfig: tlsConfig,
}

// create the listener with the http.Client, the Dialer URL and an unique identifier for the Listener
l, err := h2rev2.NewListener(client, "https://mypublic.server.io/reverse/connections/", "revdialer0001")
if err != nil {
        panic(err)
}
defer l.Close()

// we are going to use the listener to receive the forwarded connections
// from the public server and proxy them to an internal host
// serve requests
mux := http.NewServeMux()
mux.HandleFunc("/", ProxyRequestHandler(proxy))
// Create a reverse proxy to an internal host
url, err := url.Parse("http://my.internal.host")
if err != nil {
        panic(err)
}
proxy := httputil.NewSingleHostReverseProxy(url)
mux.HandleFunc("/", proxy.ServeHTTP)
server := &http.Server{Handler: mux}
defer server.Close()
server.Serve(l)
Clients

Now clients can use the public server url to connect to the proxied server in the internal network

curl -k https://public.server.url/reverse/connections/proxy/revdialer0001/internal/path

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrListenerClosed = errors.New("revdial: Listener closed")

ErrListenerClosed is returned by Accept after Close has been called.

Functions

This section is empty.

Types

type Dialer

type Dialer struct {
	// contains filtered or unexported fields
}

The Dialer can create new connections back to the origin. A Dialer can have multiple clients.

func NewDialer

func NewDialer(id string, conn net.Conn) *Dialer

NewDialer returns the side of the connection which will initiate new connections over the already established reverse connections.

func (*Dialer) Close

func (d *Dialer) Close() error

Close closes the Dialer.

func (*Dialer) Dial

func (d *Dialer) Dial(ctx context.Context, network string, address string) (net.Conn, error)

Dial creates a new connection back to the Listener.

func (*Dialer) Done

func (d *Dialer) Done() <-chan struct{}

Done returns a channel which is closed when d is closed (either by this process on purpose, by a local error, or close or error from the peer).

type Listener

type Listener struct {
	// contains filtered or unexported fields
}

Listener is a net.Listener, returning new connections which arrive from a corresponding Dialer.

func NewListener

func NewListener(client *http.Client, host string, id string) (*Listener, error)

NewListener returns a new Listener, it dials to the Dialer creating "reverse connection" that are accepted by this Listener. - client: http client, required for TLS - host: a URL to the base of the reverse handler on the Dialer - id: identify this listener

func (*Listener) Accept

func (ln *Listener) Accept() (net.Conn, error)

Accept blocks and returns a new connection, or an error.

func (*Listener) Addr

func (ln *Listener) Addr() net.Addr

Addr returns a dummy address. This exists only to conform to the net.Listener interface.

func (*Listener) Close

func (ln *Listener) Close() error

Close closes the Listener, making future Accept calls return an error.

type ReversePool

type ReversePool struct {
	// contains filtered or unexported fields
}

ReversePool contains a pool of Dialers to create reverse connections It exposes an http.Handler to handle the clients.

pool := h2rev2.NewReversePool()
mux := http.NewServeMux()
mux.Handle("", pool)

func NewReversePool

func NewReversePool() *ReversePool

NewReversePool returns a ReversePool

func (*ReversePool) Close

func (rp *ReversePool) Close()

Close the Reverse pool and all its dialers

func (*ReversePool) CreateDialer

func (rp *ReversePool) CreateDialer(id string, conn net.Conn) *Dialer

CreateDialer creates a reverse dialer with id it's a noop if a dialer already exists

func (*ReversePool) DeleteDialer

func (rp *ReversePool) DeleteDialer(id string)

DeleteDialer delete the reverse dialer for the id

func (*ReversePool) GetDialer

func (rp *ReversePool) GetDialer(id string) *Dialer

GetDialer returns a reverse dialer for the id

func (*ReversePool) ServeHTTP

func (rp *ReversePool) ServeHTTP(w http.ResponseWriter, r *http.Request)

HTTP Handler that handles reverse connections and reverse proxy requests using 2 different paths: path base/revdial?key=id establish reverse connections and queue them so it can be consumed by the dialer path base/proxy/id/(path) proxies the (path) through the reverse connection identified by id

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL