echo

package module
v0.11.1 Latest Latest
Warning

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

Go to latest
Published: Jun 14, 2026 License: MIT Imports: 22 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
    

Upstream Proxy Support

Echo supports forwarding requests through an upstream proxy, enabling it to work with other proxy software:

echo_proxy, err := echo.NewEchoWithOptions(certFile, keyFile, &echo.Options{
    UpstreamProxy: "http://127.0.0.1:7890", // HTTP/HTTPS proxy
    // Or SOCKS5:
    // UpstreamProxy: "socks5://127.0.0.1:1080",
})

Usage with other proxies:

  1. Set your system/network proxy to another proxy (e.g., port 8899)
  2. Configure Echo's UpstreamProxy to point to itself or another forward proxy
  3. Traffic flow: App → Other Proxy → Echo → UpstreamProxy → Target

This allows Echo to coexist with VPN clients, Clash, V2Ray, or other proxy tools.

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
	UpstreamProxy        string       // Upstream proxy URL
	// 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) Close added in v0.10.0

func (e *Echo) Close() error

Close shuts down all components (TCP relay, TUN).

func (*Echo) ListenTCP added in v0.10.0

func (e *Echo) ListenTCP(listenAddr, echoAddr string) error

ListenTCP starts a TCP relay on listenAddr that accepts raw TCP connections and forwards them to echoAddr using HTTP proxy protocol. The relay infers the target destination from TLS SNI or HTTP Host header.

func (*Echo) ServeHTTP

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

func (*Echo) ShutdownTCP added in v0.10.0

func (e *Echo) ShutdownTCP()

ShutdownTCP stops the TCP relay if one is running.

func (*Echo) ShutdownTUN added in v0.10.0

func (e *Echo) ShutdownTUN()

ShutdownTUN stops the TUN forwarder if one is running.

type HTTPHandler

type HTTPHandler struct {
	PluginLoader      *PluginLoader
	Transport         *http.Transport
	FallbackTransport *http.Transport // 直连,用于上游代理不可用时 fallback
	UpstreamProxy     string
}

HTTPHandler handles standard HTTP proxy requests

func NewHTTPHandler

func NewHTTPHandler(loader *PluginLoader) *HTTPHandler

NewHTTPHandler creates a new HTTP handler with a custom transport

func NewHTTPHandlerWithUpstream added in v0.9.0

func NewHTTPHandlerWithUpstream(loader *PluginLoader, upstreamProxy string) *HTTPHandler

NewHTTPHandlerWithUpstream creates a new HTTP handler with upstream proxy support

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

	// UpstreamProxy specifies an upstream proxy to forward requests to.
	// Format: "http://proxy:port" or "socks5://proxy:port"
	// When set, echo will forward all outbound requests through this proxy
	// instead of connecting directly to targets.
	UpstreamProxy string

	// Tun enables TUN-level traffic forwarding (process-based routing).
	// When true, TunConfig is required.
	Tun bool

	// TunConfig is the TUN configuration. Only used when Tun is true.
	// Can be loaded from a file via tun.LoadConfig() or built programmatically.
	TunConfig *tun.TunConfig

	// TunDefaultInterface overrides TunConfig.Route.DefaultInterface.
	// Use this as an initialization-level fallback when automatic default
	// interface detection fails on multi-adapter Windows machines.
	TunDefaultInterface string
}

Options configures Echo behavior

type Plugin

type Plugin struct {
	Match        string
	Target       *TargetConfig
	MockResponse *MockResponse
	Bypass       bool // If true, skip MITM and tunnel directly
	Disabled     bool // If true, the plugin is ignored by matchers

	// 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 TCPRelay added in v0.10.0

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

TCPRelay accepts raw TCP connections on a listen port, infers the target destination from TLS SNI or HTTP Host header, and forwards traffic to an upstream Echo HTTP proxy using standard HTTP proxy protocol.

Chrome → ProxyBridge (raw TCP) → TCPRelay :9900 → (HTTP proxy) → Echo :8899

func NewTCPRelay added in v0.10.0

func NewTCPRelay(listenAddr, echoAddr string) *TCPRelay

NewTCPRelay creates a new TCP relay.

func (*TCPRelay) Start added in v0.10.0

func (r *TCPRelay) Start() error

Start begins listening for raw TCP connections.

func (*TCPRelay) Stop added in v0.10.0

func (r *TCPRelay) Stop()

Stop shuts down the relay and waits for all connections to finish.

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
tun
Package windows provides transparent per-process TCP interception for the Echo HTTP MITM proxy on Windows.
Package windows provides transparent per-process TCP interception for the Echo HTTP MITM proxy on Windows.

Jump to

Keyboard shortcuts

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