proxychannel

package module
v0.0.0-...-dea9669 Latest Latest
Warning

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

Go to latest
Published: Mar 22, 2021 License: MIT Imports: 23 Imported by: 0

README

proxychannel

Package proxychannel is a customizable HTTP proxy framework in Golang.

It accepts regular HTTP and CONNECT(for HTTPS) requests, and hijacks HTTPS connections as a "man in the middle"(only works under NormalMode).

The framework starts a proxy server(net/http server) and an extension manager, does something according to Delegate, and handles the graceful shutdown and logging of them. To use it, just offer some configuration, and implement the Delegate and Extensions that you need.

It has 2 modes(NormalMode and ConnPoolMode) for different scenarios and supports request-wise parent proxy selection.

Install

go get github.com/spritesprite/proxychannel

Conception & Features

  • Delegate: An interface that defines some manipulation on requests or responses in defferent stages of the proxying process.

  • Extension: An interface that provides certain functionality. All extensions are under extension manager's controll, and they basically serve for Delegate.

Relation between Delegate and Extension

  • Context: The information of each request, including the timestamps, response size, error type, user-defined data member, etc.

  • NormalMode: proxychannel forwards requests to parent proxy, and returns whatever it gets from parent proxy back to requestor.

  • ConnPoolMode: proxychannel chooses a TCP connection by given probability from the ConnPool, which is specified by p.delegate.GetConnPool(). If proxychannel fails to connect parent proxy or gets a response body in JSON format that has an "ErrType" of "PROXY_CHANNEL_INTERNAL_ERR"(when the parent proxy is also a proxychannel instance), it retries the request using another proxy chosen from ConnPool by given probability. The retry goes on until any parent proxy returns a 200 response code or every connection has been tried.

Usage

Get it Started

To start a basic HTTP proxy that listens on localhost:8080, try:

go run examples/main.go

Now check if the proxy works properly:

curl -x localhost:8080 https://www.google.com/
curl -x localhost:8080 http://www.google.com/

To try the "Man in the middle" functionality, install certification authority(CA) cert/mitm_proxy.crt first(if you happen to use CentOS, just use cert/centos_cert_install.sh to install CA), and run:

curl -x localhost:8080 https://www.google.com/ -H "MITM:Enabled" -v

The "Man in the middle" feature is functioning properly, if you could find in verbose output of curl something like: issuer: CN=go-mitm-proxy.

Customize your proxychannel
  • Customize Delegate

According to the definition of Delegate, implement the methods you need.

type Delegate interface {
	GetExtensionManager() *ExtensionManager
	SetExtensionManager(*ExtensionManager)
	Connect(ctx *Context, rw http.ResponseWriter)
	Auth(ctx *Context, rw http.ResponseWriter)
	BeforeRequest(ctx *Context)
	BeforeResponse(ctx *Context, i interface{})
	ParentProxy(ctx *Context, i interface{}) (*url.URL, error)
	DuringResponse(ctx *Context, i interface{})
	Finish(ctx *Context, rw http.ResponseWriter)
	GetConnPool(ctx *Context) ([]randutil.Choice, error)
}

For example, this Finish method generates some logs for each request:

func (d *YourDelegate) Finish(ctx *proxychannel.Context, rw http.ResponseWriter) {
    GenerateLog(ctx) // generate log based on the information stored in ctx
}
  • Customize Extension

To add an extension, just implement the Setup() and Cleanup() methods. For example, if your proxy needs some information stored, you may add a redis extension with Setup() building a connection pool to redis server and Cleanup() closing the pool.

Documentation

Index

Constants

View Source
const (
	ConnectFail        = "CONNECT_FAIL"
	AuthFail           = "AUTH_FAIL"
	BeforeRequestFail  = "BEFORE_REQUEST_FAIL"
	BeforeResponseFail = "BEFORE_RESPONSE_FAIL"
	ParentProxyFail    = "PARENT_PROXY_FAIL"

	HTTPDoRequestFail               = "HTTP_DO_REQUEST_FAIL"
	HTTPWriteClientFail             = "HTTP_WRITE_CLIENT_FAIL"
	HTTPSGenerateTLSConfigFail      = "HTTPS_GENERATE_TLS_CONFIG_FAIL"
	HTTPSHijackClientConnFail       = "HTTPS_HIJACK_CLIENT_CONN_FAIL"
	HTTPSWriteEstRespFail           = "HTTPS_WRITE_EST_RESP_FAIL"
	HTTPSTLSClientConnHandshakeFail = "HTTPSTLS_CLIENT_CONN_HANDSHAKE_FAIL"
	HTTPSReadReqFromBufFail         = "HTTPS_READ_REQ_FROM_BUF_FAIL"
	HTTPSDoRequestFail              = "HTTPS_DO_REQUEST_FAIL"
	HTTPSWriteRespFail              = "HTTPS_WRITE_RESP_FAIL"
	TunnelHijackClientConnFail      = "TUNNEL_HIJACK_CLIENT_CONN_FAIL"
	TunnelDialRemoteServerFail      = "TUNNEL_DIAL_REMOTE_SERVER_FAIL"
	TunnelWriteEstRespFail          = "TUNNEL_WRITE_EST_RESP_FAIL"
	TunnelConnectRemoteFail         = "TUNNEL_CONNECT_REMOTE_FAIL"
	TunnelWriteTargetConnFinish     = "TUNNEL_WRITE_TARGET_CONN_FINISH"
	TunnelWriteClientConnFinish     = "TUNNEL_WRITE_CLIENT_CONN_FINISH"

	PoolGetParentProxyFail         = "POOL_GET_PARENT_PROXY_FAIL"
	PoolReadRemoteFail             = "POOL_READ_REMOTE_FAIL"
	PoolWriteClientFail            = "POOL_WRITE_CLIENT_FAIL"
	PoolGetConnPoolFail            = "POOL_GET_CONN_POOL_FAIL"
	PoolNoAvailableParentProxyFail = "POOL_NO_AVAILABLE_PARENT_PROXY_FAIL"
	PoolRoundTripFail              = "POOL_ROUND_TRIP_FAIL"
	PoolParentProxyFail            = "POOL_PARENT_PROXY_FAIL"
	PoolHTTPRegularFinish          = "POOL_HTTP_REGULAR_FINISH"
	PoolGetConnFail                = "POOL_GET_CONN_FAIL"
	PoolWriteTargetConnFail        = "POOL_WRITE_TARGET_CONN_FAIL"
	PoolReadTargetFail             = "POOL_READ_TARGET_FAIL"

	HTTPWebsocketDailFail                    = "HTTP_WEBSOCKET_DAIL_FAIL"
	HTTPWebsocketHijackFail                  = "HTTP_WEBSOCKET_HIJACK_FAIL"
	HTTPWebsocketHandshakeFail               = "HTTP_WEBSOCKET_HANDSHAKE_FAIL"
	HTTPSWebsocketGenerateTLSConfigFail      = "HTTPS_WEBSOCKET_GENERATE_TLS_CONFIG_FAIL"
	HTTPSWebsocketHijackFail                 = "HTTPS_WEBSOCKET_HIJACK_FAIL"
	HTTPSWebsocketWriteEstRespFail           = "HTTPS_WEBSOCKET_WRITE_EST_RESP_FAIL"
	HTTPSWebsocketTLSClientConnHandshakeFail = "HTTPS_WEBSOCKET_TLS_CLIENT_CONN_HANDSHAKE_FAIL"
	HTTPSWebsocketReadReqFromBufFail         = "HTTPS_WEBSOCKET_READ_REQ_FROM_BUF_FAIL"
	HTTPSWebsocketDailFail                   = "HTTPS_WEBSOCKET_DAIL_FAIL"
	HTTPSWebsocketHandshakeFail              = "HTTPS_WEBSOCKET_HANDSHAKE_FAIL"

	HTTPRedialCancelTimeout   = "HTTP_REDIAL_CANCEL_TIMEOUT"
	HTTPSRedialCancelTimeout  = "HTTPS_REDIAL_CANCEL_TIMEOUT"
	TunnelRedialCancelTimeout = "TUNNEL_REDIAL_CANCEL_TIMEOUT"
)

FailEventType . When a request is aborted, the event should be one of the following.

View Source
const (
	DefaultLoggerName    = "ProxyChannel"
	DefaultLogTimeFormat = "2006-01-02 15:04:05"
	DefaultLogLevel      = "debug"
	DefaultLogOut        = "stderr"
	DefaultLogFormat     = `[%{time:` + DefaultLogTimeFormat + `}] [%{module}] [%{level}] %{message}`
)

Default Settings

View Source
const (
	NormalMode = iota
	ConnPoolMode
)

Below are the modes supported.

Variables

View Source
var Logger *logging.Logger = logging.MustGetLogger(rootLoggerName)

Logger is used to print log in proxychannel

Functions

func CloneBody

func CloneBody(b io.ReadCloser) (r io.ReadCloser, body []byte, err error)

CloneBody deep copy.

func CloneHeader

func CloneHeader(h http.Header) http.Header

CloneHeader deep copy.

func ConfigLogging

func ConfigLogging(conf *LogConfig) error

ConfigLogging sets the log style.

func CopyHeader

func CopyHeader(dst, src http.Header)

CopyHeader shallow copy.

func NewServer

func NewServer(hconf *HandlerConfig, sconf *ServerConfig, em *ExtensionManager) *http.Server

NewServer returns an http.Server that defined by user config

func SetLoggingBackend

func SetLoggingBackend(out string) error

SetLoggingBackend .

func SetLoggingFormat

func SetLoggingFormat(format string) error

SetLoggingFormat .

func SetLoggingLevel

func SetLoggingLevel(level string, debug bool) error

SetLoggingLevel .

func WriteProxyErrorToResponseBody

func WriteProxyErrorToResponseBody(ctx *Context, respWriter Writer, httpcode int32, msg string, optionalPrefix string)

WriteProxyErrorToResponseBody is the standard function to call when errors occur due to this proxy's behavior, which does not include the behavior of parent proxies.

Types

type Cache

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

Cache is a concurrent map.

func (*Cache) Get

func (c *Cache) Get(host string) *tls.Certificate

Get gets the certificate stored.

func (*Cache) Set

func (c *Cache) Set(host string, cert *tls.Certificate)

Set stores the certificates of hosts that have been seen.

type ConnPool

type ConnPool interface {
	Get() (net.Conn, error)
	GetWithTimeout(timeout time.Duration) (net.Conn, error)
	Close() error
	GetTag() string             // get the human readable tag of the remote
	GetWeight() int             // get the weight of this connection pool
	GetRemoteAddrURL() *url.URL // get the remote addr of this connection pool
}

ConnPool .

type ConnWrapper

type ConnWrapper struct {
	Conn net.Conn
	Err  error
}

ConnWrapper .

type Context

type Context struct {
	Req  *http.Request
	Data map[interface{}]interface{}

	Hijack     bool
	MITM       bool
	ReqLength  int64
	RespLength int64
	ErrType    string
	Err        error
	Closed     bool
	Lock       sync.RWMutex
	// contains filtered or unexported fields
}

Context stores what methods of Delegate would need as input.

func (*Context) Abort

func (c *Context) Abort()

Abort sets abort to true.

func (*Context) AbortWithError

func (c *Context) AbortWithError(err error)

AbortWithError sets Err and abort to true.

func (*Context) GetContextError

func (c *Context) GetContextError() (errType string, err error)

GetContextError .

func (*Context) IsAborted

func (c *Context) IsAborted() bool

IsAborted checks whether abort is set to true.

func (*Context) SetContextErrType

func (c *Context) SetContextErrType(errType string)

SetContextErrType .

func (*Context) SetContextError

func (c *Context) SetContextError(err error)

SetContextError .

func (*Context) SetContextErrorWithType

func (c *Context) SetContextErrorWithType(err error, errType string)

SetContextErrorWithType .

func (*Context) SetPoolContextErrorWithType

func (c *Context) SetPoolContextErrorWithType(err error, errType string, parentProxy ...string)

SetPoolContextErrorWithType .

type DefaultDelegate

type DefaultDelegate struct {
	Delegate
}

DefaultDelegate basically does nothing.

func (*DefaultDelegate) Auth

func (h *DefaultDelegate) Auth(ctx *Context, rw http.ResponseWriter)

Auth .

func (*DefaultDelegate) BeforeRequest

func (h *DefaultDelegate) BeforeRequest(ctx *Context)

BeforeRequest .

func (*DefaultDelegate) BeforeResponse

func (h *DefaultDelegate) BeforeResponse(ctx *Context, i interface{})

BeforeResponse .

func (*DefaultDelegate) Connect

func (h *DefaultDelegate) Connect(ctx *Context, rw http.ResponseWriter)

Connect .

func (*DefaultDelegate) DuringResponse

func (h *DefaultDelegate) DuringResponse(ctx *Context, i interface{})

DuringResponse .

func (*DefaultDelegate) Finish

func (h *DefaultDelegate) Finish(ctx *Context, rw http.ResponseWriter)

Finish .

func (*DefaultDelegate) GetConnPool

func (h *DefaultDelegate) GetConnPool(ctx *Context) ([]randutil.Choice, error)

GetConnPool .

func (*DefaultDelegate) GetExtensionManager

func (h *DefaultDelegate) GetExtensionManager() *ExtensionManager

GetExtensionManager .

func (*DefaultDelegate) ParentProxy

func (h *DefaultDelegate) ParentProxy(ctx *Context, i interface{}) (*url.URL, error)

ParentProxy .

func (*DefaultDelegate) SetExtensionManager

func (h *DefaultDelegate) SetExtensionManager(em *ExtensionManager)

SetExtensionManager .

type Delegate

type Delegate interface {
	GetExtensionManager() *ExtensionManager
	SetExtensionManager(*ExtensionManager)
	Connect(ctx *Context, rw http.ResponseWriter)
	Auth(ctx *Context, rw http.ResponseWriter)
	BeforeRequest(ctx *Context)
	BeforeResponse(ctx *Context, i interface{})
	ParentProxy(ctx *Context, i interface{}) (*url.URL, error)
	DuringResponse(ctx *Context, i interface{})
	Finish(ctx *Context, rw http.ResponseWriter)
	GetConnPool(ctx *Context) ([]randutil.Choice, error)
}

Delegate defines some extra manipulation on requests set by user.

type Extension

type Extension interface {
	Setup() error
	Cleanup() error
	GetExtensionManager() *ExtensionManager
	SetExtensionManager(*ExtensionManager)
}

Extension python version __init__(self, engine, **kwargs)

type ExtensionManager

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

ExtensionManager manage extensions

func NewExtensionManager

func NewExtensionManager(m map[string]Extension) *ExtensionManager

NewExtensionManager initialize an extension

func (*ExtensionManager) Cleanup

func (em *ExtensionManager) Cleanup()

Cleanup cleanup all extensions one by one, dont know if the order matters

func (*ExtensionManager) GetExtension

func (em *ExtensionManager) GetExtension(name string) (Extension, error)

GetExtension get extension by name

func (*ExtensionManager) Setup

func (em *ExtensionManager) Setup()

Setup setup all extensions one by one

type HandlerConfig

type HandlerConfig struct {
	DisableKeepAlive bool
	Delegate         Delegate
	DecryptHTTPS     bool
	CertCache        cert.Cache
	Transport        *http.Transport
	Mode             int
}

HandlerConfig .

var DefaultHandlerConfig *HandlerConfig = &HandlerConfig{
	DisableKeepAlive: false,
	Delegate:         &DefaultDelegate{},
	DecryptHTTPS:     false,
	CertCache:        &Cache{},
	Transport: &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
		DialContext: (&net.Dialer{

			DualStack: true,
		}).DialContext,
		MaxIdleConns:          100,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	},
}

DefaultHandlerConfig .

type LogConfig

type LogConfig struct {
	LoggerName string
	LogLevel   string
	LogOut     string
	LogFormat  string
}

LogConfig .

type Proxy

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

Proxy is a struct that implements ServeHTTP() method

func NewProxy

func NewProxy(hconf *HandlerConfig, em *ExtensionManager) *Proxy

NewProxy creates a Proxy instance (an HTTP handler)

func (*Proxy) ClientConnNum

func (p *Proxy) ClientConnNum() int32

ClientConnNum gets the Client

func (*Proxy) DoRequest

func (p *Proxy) DoRequest(ctx *Context, rw http.ResponseWriter, responseFunc func(*http.Response, error), conn ...interface{})

DoRequest makes a request to remote server as a clent through given proxy, and calls responseFunc before returning the response. The "conn" is needed when it comes to https request, and only one conn is accepted.

func (*Proxy) ServeHTTP

func (p *Proxy) ServeHTTP(rw http.ResponseWriter, req *http.Request)

ServeHTTP .

type ProxyError

type ProxyError struct {
	ErrType string `json:"errType"`
	ErrCode int32  `json:"errCode"`
	ErrMsg  string `json:"errMsg"`
}

ProxyError specifies all the possible errors that can occur due to this proxy's behavior, which does not include the behavior of parent proxies.

type Proxychannel

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

Proxychannel is a prxoy server that manages data transmission between http clients and destination servers. With the "Extensions" provided by user, Proxychannel is able to do authentication, communicate with databases, manipulate the requests/responses, etc.

func NewProxychannel

func NewProxychannel(hconf *HandlerConfig, sconf *ServerConfig, m map[string]Extension) *Proxychannel

NewProxychannel returns a new Proxychannel

func (*Proxychannel) Run

func (pc *Proxychannel) Run()

Run launches the ExtensionManager and the HTTP server

type Reader

type Reader interface {
	Read([]byte) (int, error)
}

Reader .

type ReaderWithProtocol

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

ReaderWithProtocol .

func (*ReaderWithProtocol) Read

func (r *ReaderWithProtocol) Read(b []byte) (n int, err error)

type ResponseInfo

type ResponseInfo struct {
	Resp        *http.Response
	Err         error
	ParentProxy *url.URL
	Pool        ConnPool
}

ResponseInfo .

type ResponseWrapper

type ResponseWrapper struct {
	Resp *http.Response
	Err  error
}

ResponseWrapper is simply a wrapper for http.Response and error.

type ServerConfig

type ServerConfig struct {
	ProxyAddr    string
	ReadTimeout  time.Duration
	WriteTimeout time.Duration
	TLSConfig    *tls.Config
}

ServerConfig .

var DefaultServerConfig *ServerConfig = &ServerConfig{
	ProxyAddr:    ":8080",
	ReadTimeout:  60 * time.Second,
	WriteTimeout: 60 * time.Second,
}

DefaultServerConfig .

type TunnelConn

type TunnelConn struct {
	Client net.Conn
	Target net.Conn
}

TunnelConn .

type TunnelInfo

type TunnelInfo struct {
	Client      net.Conn
	Target      net.Conn
	Err         error
	ParentProxy *url.URL
	Pool        ConnPool
}

TunnelInfo .

type Writer

type Writer interface {
	Write([]byte) (int, error)
}

Writer .

type WriterWithLength

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

WriterWithLength .

func (*WriterWithLength) Length

func (w *WriterWithLength) Length() int

Length .

func (*WriterWithLength) Write

func (w *WriterWithLength) Write(b []byte) (n int, err error)

type WriterWithProtocol

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

WriterWithProtocol .

func (*WriterWithProtocol) Write

func (w *WriterWithProtocol) Write(b []byte) (n int, err error)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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