echo

package module
v0.8.0 Latest Latest
Warning

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

Go to latest
Published: Mar 12, 2026 License: MIT Imports: 18 Imported by: 0

README

Echo

A simplest implementation of a proxy server in Go, inspired by Whistle.

Features

  • HTTP Proxy: Supports standard HTTP proxying.
  • HTTPS/TCP Tunneling: Supports CONNECT method for HTTPS and generic TCP tunneling.
  • WebSocket Support: Supports WebSocket upgrades (hijacking) and tunneling.
  • Plugin System: Flexible plugin system to modify requests and responses.

Installation

go get github.com/ltaoo/echo

Quick Start

To start the proxy server, you need to provide a Root CA certificate and private key.

package main

import (
	"log"
	"net/http"
	"os"

	"github.com/ltaoo/echo"
)

func main() {
	// 1. Load Root CA (You need to generate these or use existing ones)
	// Ensure you have 'certs/rootCA.crt' and 'certs/rootCA.key'
	certFile, err := os.ReadFile("certs/rootCA.crt")
	if err != nil {
		log.Fatalf("Failed to read cert file: %v", err)
	}
	keyFile, err := os.ReadFile("certs/rootCA.key")
	if err != nil {
		log.Fatalf("Failed to read key file: %v", err)
	}

	// 2. Initialize Echo
	e, err := echo.NewEcho(certFile, keyFile)
	if err != nil {
		log.Fatalf("Failed to initialize Echo: %v", err)
	}

	// 3. Start Server
	server := &http.Server{
		Addr:    ":8888",
		Handler: e,
	}

	log.Println("Echo Proxy listening on :8888")
	if err := server.ListenAndServe(); err != nil {
		log.Fatal(err)
	}
}

Plugins

You can add plugins to intercept and modify requests/responses.

package main

import (
	"fmt"
	"strings"

	"github.com/ltaoo/echo"
	"github.com/ltaoo/echo/plugin"
)

func main() {
	// ... (Load certs as above) ...

	e, _ := echo.NewEcho(certFile, keyFile)

	// Define a plugin
	myPlugin := &plugin.Plugin{
		Match: "example.com", // Match requests to example.com
		OnRequest: func(ctx *plugin.Context) {
			fmt.Println("Intercepted request to example.com")
			ctx.SetRequestHeader("X-Custom-Header", "MyPlugin")
		},
		OnResponse: func(ctx *plugin.Context) {
			// Modify response body
			body, _ := ctx.GetResponseBody()
			newBody := strings.ReplaceAll(body, "Example Domain", "Hacked Domain")
			ctx.SetResponseBody(newBody)
		},
	}

	// Add plugin
	e.AddPlugin(myPlugin)

	// ... (Start server) ...
}

Usage

  1. Configure your browser or client to use the proxy:

    • Proxy Host: 127.0.0.1
    • Proxy Port: 8888
  2. Test with curl:

    # HTTP
    curl -x http://127.0.0.1:8888 http://example.com
    
    # HTTPS
    curl -x http://127.0.0.1:8888 https://example.com
    

Implementation Details

  • Uses Go's net/http for server handling.
  • handleHTTP for standard proxy requests (removes hop-by-hop headers).
  • handleTunnel for CONNECT requests (hijacks connection and tunnels TCP).
  • handleWebSocket for Upgrade: websocket requests (hijacks connection and tunnels TCP).

Documentation

Overview

Package echo provides a simple proxy server implementation in Go, inspired by Whistle.

Features

  • HTTP Proxy: Supports standard HTTP proxying.
  • HTTPS/TCP Tunneling: Supports CONNECT method for HTTPS and generic TCP tunneling.
  • WebSocket Support: Supports WebSocket upgrades (hijacking) and tunneling.
  • Plugin System: Flexible plugin system to modify requests and responses.

Quick Start

To start the proxy server, provide a Root CA certificate and private key:

certFile, _ := os.ReadFile("certs/rootCA.crt")
keyFile, _ := os.ReadFile("certs/rootCA.key")

e, err := echo.NewEcho(certFile, keyFile)
if err != nil {
	log.Fatal(err)
}

server := &http.Server{
	Addr:    ":8888",
	Handler: e,
}
server.ListenAndServe()

Plugins

Add plugins to intercept and modify requests/responses:

e.AddPlugin(&echo.Plugin{
	Match: "example.com",
	OnRequest: func(ctx *echo.Context) {
		ctx.SetRequestHeader("X-Custom-Header", "value")
	},
	OnResponse: func(ctx *echo.Context) {
		body, _ := ctx.GetResponseBody()
		ctx.SetResponseBody(strings.ReplaceAll(body, "old", "new"))
	},
})

Forwarding

Use TargetConfig to forward requests to a different server:

e.AddPlugin(&echo.Plugin{
	Match:  "example.com",
	Target: &echo.TargetConfig{Protocol: "http", Host: "localhost", Port: 3000},
})

Mock Response

Use MockResponse to return a static response:

e.AddPlugin(&echo.Plugin{
	Match: "example.com/api",
	MockResponse: &echo.MockResponse{
		StatusCode: 200,
		Headers:    map[string]string{"Content-Type": "application/json"},
		Body:       `{"status":"ok"}`,
	},
})

Index

Constants

This section is empty.

Variables

View Source
var BypassDomains = []string{

	"*.openai.com",
	"*.chatgpt.com",
	"chat.openai.com",
	"api.openai.com",
	"auth0.openai.com",

	"*.apple.com",
	"*.icloud.com",
	"*.mzstatic.com",
	"*.apple-cloudkit.com",
	"*.cdn-apple.com",
	"*.itunes.com",
	"*.appleimg.com",

	"*.google.com",
	"*.googleapis.com",
	"*.gstatic.com",
	"*.googleusercontent.com",
	"*.googlevideo.com",
	"*.youtube.com",
	"*.ytimg.com",
	"*.ggpht.com",
	"*.android.com",

	"*.microsoft.com",
	"*.microsoftonline.com",
	"*.live.com",
	"*.office.com",
	"*.office365.com",
	"*.windows.com",
	"*.windowsupdate.com",
	"*.azure.com",
	"*.bing.com",
	"*.msn.com",

	"*.amazon.com",
	"*.amazonaws.com",
	"*.cloudfront.net",

	"*.paypal.com",
	"*.stripe.com",
	"*.visa.com",
	"*.mastercard.com",
	"*.americanexpress.com",

	"*.facebook.com",
	"*.instagram.com",
	"*.whatsapp.com",
	"*.twitter.com",
	"*.x.com",

	"*.okta.com",
	"*.auth0.com",
	"*.duo.com",

	"*.dropbox.com",
	"*.slack.com",
	"*.zoom.us",
	"*.netflix.com",
	"*.spotify.com",
}

BypassDomains contains domains that should bypass MITM interception. These services typically use certificate pinning or have strict security requirements.

Functions

func CopyHeader

func CopyHeader(dst, src http.Header)

CopyHeader copies headers from source to destination

func DecompressBody

func DecompressBody(res *http.Response) (io.ReadCloser, error)

DecompressBody returns a reader that decompresses the response body if needed

func DelHopHeaders

func DelHopHeaders(header http.Header)

DelHopHeaders removes hop-by-hop headers

func IsMatch

func IsMatch(hostname, pattern string) bool

IsMatch checks if a hostname matches a pattern Supports: - Exact match: "example.com" - Wildcard: "*.example.com" - Substring: "example" (matches "example.com", "test.example.com", etc.)

func IsWebSocketRequest

func IsWebSocketRequest(r *http.Request) bool

IsWebSocketRequest checks if the request is a WebSocket upgrade

func SetLogEnabled

func SetLogEnabled(enabled bool)

Types

type ConnectHandler

type ConnectHandler struct {
	CertManager          *cert.Manager
	PluginLoader         *PluginLoader
	HTTPHandler          *HTTPHandler // Shared HTTP handler
	InterceptOnlyMatched bool         // Only intercept if plugin matches
	// contains filtered or unexported fields
}

ConnectHandler handles CONNECT requests and MITM

func (*ConnectHandler) HandleTunnel

func (h *ConnectHandler) HandleTunnel(w http.ResponseWriter, r *http.Request)

HandleTunnel handles the CONNECT request

type Context

type Context struct {
	Req *http.Request
	Res *http.Response // Nil in OnRequest
	// contains filtered or unexported fields
}

Context provides access to the request and response for plugins

func (*Context) DelRequestHeader

func (c *Context) DelRequestHeader(key string)

DelRequestHeader deletes a header from the request

func (*Context) DelResponseHeader

func (c *Context) DelResponseHeader(key string)

DelResponseHeader deletes a header from the response

func (*Context) GetMockResponse

func (c *Context) GetMockResponse() *MockResponse

GetMockResponse returns the set mock response

func (*Context) GetRequestHeader

func (c *Context) GetRequestHeader(key string) string

GetRequestHeader gets a header from the request

func (*Context) GetResponseBody

func (c *Context) GetResponseBody() (string, error)

GetResponseBody reads and returns the response body as a string It automatically decompresses the body if needed and updates the response to be uncompressed for subsequent reads.

func (*Context) GetResponseHeader

func (c *Context) GetResponseHeader(key string) string

GetResponseHeader gets a header from the response

func (*Context) Mock

func (c *Context) Mock(status int, headers map[string]string, body interface{})

Mock sets a mock response to be returned immediately

func (*Context) SetRequestHeader

func (c *Context) SetRequestHeader(key, value string)

SetRequestHeader sets a header on the request

func (*Context) SetResponseBody

func (c *Context) SetResponseBody(body string)

SetResponseBody sets the response body

func (*Context) SetResponseHeader

func (c *Context) SetResponseHeader(key, value string)

SetResponseHeader sets a header on the response

type Echo

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

func NewEcho

func NewEcho(certFile []byte, certKey []byte) (*Echo, error)

func NewEchoWithOptions added in v0.7.0

func NewEchoWithOptions(certFile []byte, certKey []byte, opts *Options) (*Echo, error)

NewEchoWithOptions creates a new Echo instance with custom options

func (*Echo) AddPlugin

func (e *Echo) AddPlugin(plugin *Plugin)

func (*Echo) ServeHTTP

func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request)

type HTTPHandler

type HTTPHandler struct {
	PluginLoader *PluginLoader
	Transport    *http.Transport
}

HTTPHandler handles standard HTTP proxy requests

func NewHTTPHandler

func NewHTTPHandler(loader *PluginLoader) *HTTPHandler

NewHTTPHandler creates a new HTTP handler with a custom transport

func (*HTTPHandler) HandleRequest

func (h *HTTPHandler) HandleRequest(w http.ResponseWriter, r *http.Request)

HandleRequest processes the HTTP request

type MitmServer

type MitmServer struct {
	Port     int
	Listener net.Listener
}

type MockResponse

type MockResponse struct {
	StatusCode int
	Headers    map[string]string
	Body       interface{} // string or []byte
}

MockResponse defines a static response to return

type Options added in v0.7.0

type Options struct {
	// EnableBuiltinBypass enables built-in bypass rules for common services
	// that use certificate pinning (Apple, Google, ChatGPT, etc.)
	EnableBuiltinBypass bool

	// InterceptOnlyMatched if true, only intercept requests that match a plugin.
	// By default (false), all HTTPS traffic on port 443 is intercepted.
	// When enabled, unmatched requests are tunneled directly without MITM.
	InterceptOnlyMatched bool
}

Options configures Echo behavior

type Plugin

type Plugin struct {
	Match        string
	Target       *TargetConfig
	MockResponse *MockResponse
	Bypass       bool // If true, skip MITM and tunnel directly

	// Hooks
	OnRequest  func(ctx *Context)
	OnResponse func(ctx *Context)
}

Plugin represents a forwarding rule configuration

type PluginLoader

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

PluginLoader handles loading and managing plugins

func NewPluginLoader

func NewPluginLoader(plugins []*Plugin) (*PluginLoader, error)

NewPluginLoader creates a new plugin loader

func (*PluginLoader) AddPlugin

func (l *PluginLoader) AddPlugin(plugin *Plugin)

func (*PluginLoader) GetPlugins

func (l *PluginLoader) GetPlugins() []*Plugin

GetPlugins returns all loaded plugins

func (*PluginLoader) Load

func (l *PluginLoader) Load(plugins []*Plugin) error

Load loads plugins from the hardcoded registry

func (*PluginLoader) MatchPlugin

func (l *PluginLoader) MatchPlugin(hostname string) *Plugin

MatchPlugin finds the first plugin that matches the given hostname

func (*PluginLoader) MatchPluginForRequest

func (l *PluginLoader) MatchPluginForRequest(r *http.Request) *Plugin

func (*PluginLoader) MatchPlugins

func (l *PluginLoader) MatchPlugins(hostname string) []*Plugin

MatchPlugins returns all plugins that match the given hostname, in order

func (*PluginLoader) MatchPluginsForRequest

func (l *PluginLoader) MatchPluginsForRequest(r *http.Request) []*Plugin

MatchPluginsForRequest returns all plugins that match the given request URL/host, in order

type TargetConfig

type TargetConfig struct {
	Protocol string // http, https, ws, wss
	Host     string
	Port     int
}

TargetConfig defines where to forward requests

func (*TargetConfig) GetDefaultPort

func (t *TargetConfig) GetDefaultPort() int

GetDefaultPort returns the default port for the protocol

func (*TargetConfig) GetHostPort

func (t *TargetConfig) GetHostPort() string

GetHostPort returns the host:port combination

func (*TargetConfig) GetTargetURL

func (t *TargetConfig) GetTargetURL(path string) string

GetTargetURL returns the full target URL for forwarding

type WebSocketHandler

type WebSocketHandler struct {
	PluginLoader *PluginLoader
}

WebSocketHandler handles WebSocket upgrades

func (*WebSocketHandler) HandleUpgrade

func (h *WebSocketHandler) HandleUpgrade(w http.ResponseWriter, r *http.Request, isSecure bool)

HandleUpgrade handles the WebSocket upgrade request

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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