tcpproxy

package module
v0.0.0-...-0b97a84 Latest Latest
Warning

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

Go to latest
Published: Jun 10, 2018 License: Apache-2.0 Imports: 14 Imported by: 0

README

tcpproxy

This is a fork of https://github.com/google/tcpproxy/

For usage, see https://godoc.org/github.com/aviddiviner/tcpproxy/

Notable differences with this package:

  1. The old Matcher type has been replaced with more of a "routing" matcher. The old type did a simple boolean match against the found hostname:

    // Matcher reports whether hostname matches the Matcher's criteria.
    type Matcher func(ctx context.Context, hostname string) bool
    

    The new Matcher also returns its Target (where an incoming matched connection is sent), allowing you to dynamically retarget based on the hostname:

    // Matcher checks whether a hostname matches its criteria and, if true, returns
    // the target where the incoming matched connection should be sent to.
    type Matcher func(ctx context.Context, hostname string) (t Target, ok bool)
    
  2. Some new matchers have been added;

    1. SuffixMatcher which directs all hosts with the given domain suffix to a target, and
    2. ConsulMatcher which directs hosts based on DNS lookups in Consul of service names matching that host's subdomain.
  3. The logic around ACME tls-sni-01 challenges (and AddStopACMESearch function) has been removed. This feature was found vulnerable to certain exploits1 and disabled by Let's Encrypt2.


  1. https://labs.detectify.com/2018/01/12/how-i-exploited-acme-tls-sni-01-issuing-lets-encrypt-ssl-certs-for-any-domain-using-shared-hosting/
  2. https://community.letsencrypt.org/t/2018-01-11-update-regarding-acme-tls-sni-and-shared-hosting-infrastructure/50188

Documentation

Overview

Package tcpproxy lets users build TCP proxies, optionally making routing decisions based on HTTP/1 Host headers and the SNI hostname in TLS connections.

Typical usage:

var p tcpproxy.Proxy
p.AddHTTPHostRoute(":80", "foo.com", tcpproxy.To("10.0.0.1:8081"))
p.AddHTTPHostRoute(":80", "bar.com", tcpproxy.To("10.0.0.2:8082"))
p.AddRoute(":80", tcpproxy.To("10.0.0.1:8081")) // fallback
p.AddSNIRoute(":443", "foo.com", tcpproxy.To("10.0.0.1:4431"))
p.AddSNIRoute(":443", "bar.com", tcpproxy.To("10.0.0.2:4432"))
p.AddRoute(":443", tcpproxy.To("10.0.0.1:4431")) // fallback
log.Fatal(p.Run())

Calling Run (or Start) on a proxy also starts all the necessary listeners.

For each accepted connection, the rules for that ipPort are matched, in order. If one matches (currently HTTP Host, SNI, or always), then the connection is handed to the target.

The two predefined Target implementations are:

1) DialProxy, proxying to another address (use the To func to return a DialProxy value),

2) TargetListener, making the matched connection available via a net.Listener.Accept call.

But Target is an interface, so you can also write your own.

Note that tcpproxy does not do any TLS encryption or decryption. It only (via DialProxy) copies bytes around. The SNI hostname in the TLS header is unencrypted, for better or worse.

This package makes no API stability promises. If you depend on it, vendor it.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func UnderlyingConn

func UnderlyingConn(c net.Conn) net.Conn

UnderlyingConn returns c.Conn if c of type *Conn, otherwise it returns c.

Types

type Conn

type Conn struct {
	// Peeked are the bytes that have been read from Conn for the
	// purposes of route matching, but have not yet been consumed
	// by Read calls. It set to nil by Read when fully consumed.
	Peeked []byte

	// Conn is the underlying connection.
	// It can be type asserted against *net.TCPConn or other types
	// as needed. It should not be read from directly unless
	// Peeked is nil.
	net.Conn
}

Conn is an incoming connection that has had some bytes read from it to determine how to route the connection. The Read method stitches the peeked bytes and unread bytes back together.

func (*Conn) Read

func (c *Conn) Read(p []byte) (n int, err error)

type DialProxy

type DialProxy struct {
	// Addr is the TCP address to proxy to.
	Addr string

	// KeepAlivePeriod sets the period between TCP keep alives.
	// If zero, a default is used. To disable, use a negative number.
	// The keep-alive is used for both the client connection and
	KeepAlivePeriod time.Duration

	// DialTimeout optionally specifies a dial timeout.
	// If zero, a default is used.
	// If negative, the timeout is disabled.
	DialTimeout time.Duration

	// DialContext optionally specifies an alternate dial function
	// for TCP targets. If nil, the standard
	// net.Dialer.DialContext method is used.
	DialContext func(ctx context.Context, network, address string) (net.Conn, error)

	// OnDialError optionally specifies an alternate way to handle errors dialing Addr.
	// If nil, the error is logged and src is closed.
	// If non-nil, src is not closed automatically.
	OnDialError func(src net.Conn, dstDialErr error)

	// ProxyProtocolVersion optionally specifies the version of
	// HAProxy's PROXY protocol to use. The PROXY protocol provides
	// connection metadata to the DialProxy target, via a header
	// inserted ahead of the client's traffic. The DialProxy target
	// must explicitly support and expect the PROXY header; there is
	// no graceful downgrade.
	// If zero, no PROXY header is sent. Currently, version 1 is supported.
	ProxyProtocolVersion int
}

DialProxy implements Target by dialing a new connection to Addr and then proxying data back and forth.

The To func is a shorthand way of creating a DialProxy.

func To

func To(addr string) *DialProxy

To is shorthand way of writing &tcpproxy.DialProxy{Addr: addr}.

func (*DialProxy) HandleConn

func (dp *DialProxy) HandleConn(src net.Conn)

HandleConn implements the Target interface.

type Matcher

type Matcher func(ctx context.Context, hostname string) (t Target, ok bool)

Matcher checks whether a hostname matches its criteria and, if true, returns the target where the incoming matched connection should be sent to.

func ConsulMatcher

func ConsulMatcher(matchSuffix, consulDnsAddr string) Matcher

ConsulMatcher returns a Matcher that directs any hostname ending with its matchSuffix to the service name registered in Consul corresponding to that host's subdomain.

For example, if the chosen suffix is "localhost" then "foo.localhost" will be directed to the consul service named "foo". Note "foo.bar.localhost" will _not_ match the suffix "localhost", only "bar.localhost".

A DNS lookup to consulDnsAddr is done to resolve the service ip:port, analogous to:

dig @127.0.0.1 -p 8600 _foo._tcp.consul SRV

func SuffixMatcher

func SuffixMatcher(suffix string, target Target) Matcher

SuffixMatcher directs all hosts with the given domain suffix to a single target.

type Proxy

type Proxy struct {

	// ListenFunc optionally specifies an alternate listen
	// function. If nil, net.Dial is used.
	// The provided net is always "tcp".
	ListenFunc func(net, laddr string) (net.Listener, error)
	// contains filtered or unexported fields
}

Proxy is a proxy. Its zero value is a valid proxy that does nothing. Call methods to add routes before calling Start or Run.

The order that routes are added in matters; each is matched in the order registered.

func (*Proxy) AddHTTPHostMatcher

func (p *Proxy) AddHTTPHostMatcher(ipPort string, match Matcher)

AddHTTPHostMatcher appends a route to the ipPort listener that routes to dest if the incoming HTTP/1.x Host header name is accepted by matcher. If it doesn't match, rule processing continues for any additional routes on ipPort.

The ipPort is any valid net.Listen TCP address.

func (*Proxy) AddHTTPHostRoute

func (p *Proxy) AddHTTPHostRoute(ipPort, httpHost string, dest Target)

AddHTTPHostRoute appends a route to the ipPort listener that routes to dest if the incoming HTTP/1.x Host header name is httpHost. If it doesn't match, rule processing continues for any additional routes on ipPort.

The ipPort is any valid net.Listen TCP address.

func (*Proxy) AddRoute

func (p *Proxy) AddRoute(ipPort string, dest Target)

AddRoute appends an always-matching route to the ipPort listener, directing any connection to dest.

This is generally used as either the only rule (for simple TCP proxies), or as the final fallback rule for an ipPort.

The ipPort is any valid net.Listen TCP address.

func (*Proxy) AddSNIMatcher

func (p *Proxy) AddSNIMatcher(ipPort string, matcher Matcher)

AddSNIMatcher appends a route to the ipPort listener that routes to dest if the incoming TLS SNI server name is accepted by matcher. If it doesn't match, rule processing continues for any additional routes on ipPort.

The ipPort is any valid net.Listen TCP address.

func (*Proxy) AddSNIRoute

func (p *Proxy) AddSNIRoute(ipPort, sni string, dest Target)

AddSNIRoute appends a route to the ipPort listener that routes to dest if the incoming TLS SNI server name is sni. If it doesn't match, rule processing continues for any additional routes on ipPort.

The ipPort is any valid net.Listen TCP address.

func (*Proxy) Close

func (p *Proxy) Close() error

Close closes all the proxy's self-opened listeners.

func (*Proxy) Run

func (p *Proxy) Run() error

Run is calls Start, and then Wait.

It blocks until there's an error. The return value is always non-nil.

func (*Proxy) Start

func (p *Proxy) Start() error

Start creates a TCP listener for each unique ipPort from the previously created routes and starts the proxy. It returns any error from starting listeners.

If it returns a non-nil error, any successfully opened listeners are closed.

func (*Proxy) Wait

func (p *Proxy) Wait() error

Wait waits for the Proxy to finish running. Currently this can only happen if a Listener is closed, or Close is called on the proxy.

It is only valid to call Wait after a successful call to Start.

type Target

type Target interface {
	// HandleConn is called when an incoming connection is
	// matched. After the call to HandleConn, the tcpproxy
	// package never touches the conn again. Implementations are
	// responsible for closing the connection when needed.
	//
	// The concrete type of conn will be of type *Conn if any
	// bytes have been consumed for the purposes of route
	// matching.
	HandleConn(net.Conn)
}

Target is what an incoming matched connection is sent to.

func HttpRedirect

func HttpRedirect(hostAddr, targetUrl string, statusCode int) (target Target, serve func() error)

HttpRedirect provides a target that will serve HTTP redirects to all incoming requests. The returned serve function will begin serving the redirects; it blocks on handling connections until started. Use it like:

redirect, serve := HttpRedirect("my.domain", "https://my.domain", http.StatusFound)
p.AddHTTPHostRoute(":80", "my.domain", redirect)
go serve()

type TargetListener

type TargetListener struct {
	Address string // Address is the string reported by TargetListener.Addr().String().
	// contains filtered or unexported fields
}

TargetListener implements both net.Listener and Target. Matched Targets become accepted connections.

func (*TargetListener) Accept

func (tl *TargetListener) Accept() (net.Conn, error)

Accept implements the Accept method in the net.Listener interface.

func (*TargetListener) Addr

func (tl *TargetListener) Addr() net.Addr

Addr returns the listener's Address field as a net.Addr.

func (*TargetListener) Close

func (tl *TargetListener) Close() error

Close stops listening for new connections. All new connections routed to this listener will be closed. Already accepted connections are not closed.

func (*TargetListener) HandleConn

func (tl *TargetListener) HandleConn(c net.Conn)

HandleConn implements the Target interface. It blocks until tl is closed or another goroutine has called Accept and received c.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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