From 729f9afcdadfcb540141ea18d3e76ca2e5d3fdf8 Mon Sep 17 00:00:00 2001 From: eyedeekay Date: Mon, 10 Feb 2025 00:32:29 -0500 Subject: [PATCH] Implement a basic HTTP proxy --- go.mod | 2 ++ go.sum | 7 ++++- lib/http/client/httpclient.go | 55 +++++++++++++++++++++++++++++++++-- lib/http/client/new.go | 16 ++++++++-- 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 195dae9..8cb275d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/go-i2p/go-i2ptunnel go 1.23.5 require ( + github.com/elazarl/goproxy v1.7.0 github.com/go-i2p/go-connfilter v0.0.0-20250205023438-0f2b889a80f6 github.com/go-i2p/go-forward v0.0.0-20250202052226-ee8a43dcb664 github.com/go-i2p/go-i2ptunnel-config v0.0.0-20250209030407-ba90db65df97 @@ -27,6 +28,7 @@ require ( golang.org/x/net v0.34.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.29.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index fa37779..537b3fc 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbe github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elazarl/goproxy v1.7.0 h1:EXv2nV4EjM60ZtsEVLYJG4oBXhDGutMKperpHsZ/v+0= +github.com/elazarl/goproxy v1.7.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= github.com/go-i2p/go-connfilter v0.0.0-20250205023438-0f2b889a80f6 h1:kOJH77NTMYNrG1+dX/T/ZO2RX1xp7ZA1eViqy4uZy5M= github.com/go-i2p/go-connfilter v0.0.0-20250205023438-0f2b889a80f6/go.mod h1:qSZ3m4cEeyQc391rRXIGYEq9zakEPMJG9WfeQ49gByU= github.com/go-i2p/go-forward v0.0.0-20250202052226-ee8a43dcb664 h1:j+RzLt8jZPT9CeiLFDWEXvJPb6Orn3UQgywTx8iL1O4= @@ -42,8 +44,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0= github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM= github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 h1:d/Wr/Vl/wiJHc3AHYbYs5I3PucJvRuw3SvbmlIRf+oM= @@ -91,6 +94,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/lib/http/client/httpclient.go b/lib/http/client/httpclient.go index 992ece2..d02a07e 100644 --- a/lib/http/client/httpclient.go +++ b/lib/http/client/httpclient.go @@ -23,13 +23,18 @@ Key features: **/ import ( + "context" "net" + "net/http" "strconv" + "sync" httpinspector "github.com/go-i2p/go-connfilter/http" i2pconv "github.com/go-i2p/go-i2ptunnel-config/lib" i2ptunnel "github.com/go-i2p/go-i2ptunnel/lib/core" "github.com/go-i2p/onramp" + + "github.com/elazarl/goproxy" ) var implementHTTPClient i2ptunnel.I2PTunnel = &HTTPClient{} @@ -43,11 +48,19 @@ type HTTPClient struct { i2ptunnel.I2PTunnelStatus // The http filtering configuration httpinspector.Config + // The proxy server + *goproxy.ProxyHttpServer + // The http server + *http.Server // Channel for shutdown signaling done chan struct{} - + // Mutex for server operations + mu sync.Mutex // Error history of the tunnel Errors []i2ptunnel.I2PTunnelError + // Context for cleanup + ctx context.Context + cancel context.CancelFunc } func (h *HTTPClient) recordError(err error) { @@ -80,7 +93,29 @@ func (h *HTTPClient) Name() string { // Start the tunnel func (h *HTTPClient) Start() error { - panic("unimplemented") + h.mu.Lock() + defer h.mu.Unlock() + + if h.ProxyHttpServer != nil { + return nil // Already started + } + + // Create context for managing goroutines + h.ctx, h.cancel = context.WithCancel(context.Background()) + h.done = make(chan struct{}) + proxy := goproxy.NewProxyHttpServer() + h.ProxyHttpServer = proxy + h.ProxyHttpServer.Tr.DialContext = h.DialContext + // set up local listener + listener, err := net.Listen("tcp", net.JoinHostPort(h.Interface, strconv.Itoa(h.Port))) + if err != nil { + return err + } + // set up httpinspector listener + listenerInspector := httpinspector.New(listener, h.Config) + h.Server = &http.Server{} + h.Server.Handler = h.ProxyHttpServer + return h.Server.Serve(listenerInspector) } // Get the tunnel's status @@ -90,7 +125,21 @@ func (h *HTTPClient) Status() i2ptunnel.I2PTunnelStatus { // Stop the tunnel func (h *HTTPClient) Stop() error { - panic("unimplemented") + h.mu.Lock() + defer h.mu.Unlock() + + if h.Server != nil { + h.I2PTunnelStatus = i2ptunnel.I2PTunnelStatusStopping + close(h.done) + if err := h.Server.Shutdown(h.ctx); err != nil { + h.recordError(err) + return err + } + h.cancel() + h.Server = nil + h.I2PTunnelStatus = i2ptunnel.I2PTunnelStatusStopped + } + return nil } // Get the tunnel's I2P target. Nil in the case of one-to-many clients like SOCKS5 and HTTP diff --git a/lib/http/client/new.go b/lib/http/client/new.go index 40d8622..bb7fe23 100644 --- a/lib/http/client/new.go +++ b/lib/http/client/new.go @@ -1,6 +1,8 @@ package httpclient import ( + "context" + "net" "strings" i2pconv "github.com/go-i2p/go-i2ptunnel-config/lib" @@ -20,10 +22,20 @@ func NewHTTPClient(config i2pconv.TunnelConfig, samAddr string) (*HTTPClient, er return nil, err } garlic.ServiceKeys = keys - return &HTTPClient{ + h := &HTTPClient{ TunnelConfig: config, Garlic: garlic, I2PTunnelStatus: i2ptunnel.I2PTunnelStatusStopped, done: make(chan struct{}), - }, nil + } + + return h, nil +} + +func (h *HTTPClient) DialContext(ctx context.Context, network, addr string) (c net.Conn, err error) { + return h.Garlic.DialContext(ctx, network, addr) +} + +func (h *HTTPClient) Dial(network, addr string) (c net.Conn, err error) { + return h.Garlic.Dial(network, addr) }