diff --git a/config/parsing/parse.go b/config/parsing/parse.go index 864da502..fc28dc81 100644 --- a/config/parsing/parse.go +++ b/config/parsing/parse.go @@ -15,4 +15,6 @@ const ( MDKeyRecorderDirection = "direction" MDKeyRecorderTimestampFormat = "timeStampFormat" MDKeyRecorderHexdump = "hexdump" + MDKeyRecorderHTTPBody = "http.body" + MDKeyRecorderHTTPMaxBodySize = "http.maxBodySize" ) diff --git a/config/parsing/service/parse.go b/config/parsing/service/parse.go index 4436a0bc..20a3896f 100644 --- a/config/parsing/service/parse.go +++ b/config/parsing/service/parse.go @@ -246,6 +246,8 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) { Direction: mdutil.GetBool(md, parsing.MDKeyRecorderDirection), TimestampFormat: mdutil.GetString(md, parsing.MDKeyRecorderTimestampFormat), Hexdump: mdutil.GetBool(md, parsing.MDKeyRecorderHexdump), + HTTPBody: mdutil.GetBool(md, parsing.MDKeyRecorderHTTPBody), + MaxBodySize: mdutil.GetInt(md, parsing.MDKeyRecorderHTTPMaxBodySize), }, }) } diff --git a/go.mod b/go.mod index 203c8c47..b073dff3 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/gin-contrib/cors v1.6.0 github.com/gin-gonic/gin v1.9.1 - github.com/go-gost/core v0.1.3 + github.com/go-gost/core v0.1.4 github.com/go-gost/gosocks4 v0.0.1 github.com/go-gost/gosocks5 v0.4.2 github.com/go-gost/plugin v0.1.1 diff --git a/go.sum b/go.sum index 4626c733..a7b0fd90 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-gost/core v0.1.3 h1:Azq50of/OEv89/4THFbwNLidXK1fIwsMRVcgQUlvJug= -github.com/go-gost/core v0.1.3/go.mod h1:WGI43jOka7FAsSAwi/fSMaqxdR+E339ycb4NBGlFr6A= +github.com/go-gost/core v0.1.4 h1:uP1r6kpi39NaFtKo6guCjMSvgJl8eTWW3Lb/8+4YDdo= +github.com/go-gost/core v0.1.4/go.mod h1:WGI43jOka7FAsSAwi/fSMaqxdR+E339ycb4NBGlFr6A= github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s= github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc= github.com/go-gost/gosocks5 v0.4.2 h1:IianxHTkACPqCwiOAT3MHoMdSUl+SEPSRu1ikawC1Pc= diff --git a/handler/forward/local/handler.go b/handler/forward/local/handler.go index 990cd912..3fe3b37b 100644 --- a/handler/forward/local/handler.go +++ b/handler/forward/local/handler.go @@ -35,6 +35,10 @@ import ( "github.com/go-gost/x/registry" ) +const ( + defaultBodySize = 1024 * 1024 * 10 // 10MB +) + func init() { registry.HandlerRegistry().Register("tcp", NewHandler) registry.HandlerRegistry().Register("udp", NewHandler) @@ -375,6 +379,18 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, cc = tls.Client(cc, cfg) } + var reqBody *xhttp.Body + if opts := h.recorder.Options; opts != nil && opts.HTTPBody { + if req.Body != nil { + maxSize := opts.MaxBodySize + if maxSize <= 0 { + maxSize = defaultBodySize + } + reqBody = xhttp.NewBody(req.Body, maxSize) + req.Body = reqBody + } + } + if err = req.Write(cc); err != nil { cc.Close() log.Warnf("send request to node %s(%s): %v", target.Name, target.Addr, err) @@ -382,6 +398,11 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, return err } + if reqBody != nil { + ro.HTTP.Request.Body = reqBody.Content() + ro.HTTP.Request.ContentLength = reqBody.Length() + } + if req.Header.Get("Upgrade") == "websocket" { err = xnet.Transport(cc, xio.NewReadWriter(br, rw)) if err == nil { @@ -395,16 +416,21 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, var err error var res *http.Response + var respBody *xhttp.Body defer func() { ro.Duration = time.Since(start) if err != nil { ro.Err = err.Error() } - if res != nil && ro.HTTP != nil { - ro.HTTP.Response.ContentLength = res.ContentLength - ro.HTTP.Response.Header = res.Header + if res != nil { ro.HTTP.StatusCode = res.StatusCode + ro.HTTP.Response.Header = res.Header + ro.HTTP.Response.ContentLength = res.ContentLength + if respBody != nil { + ro.HTTP.Response.Body = respBody.Content() + ro.HTTP.Response.ContentLength = respBody.Length() + } } ro.Record(ctx, h.recorder.Recorder) }() @@ -432,6 +458,15 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, return } + if opts := h.recorder.Options; opts != nil && opts.HTTPBody { + maxSize := opts.MaxBodySize + if maxSize <= 0 { + maxSize = defaultBodySize + } + respBody = xhttp.NewBody(res.Body, maxSize) + res.Body = respBody + } + if err = res.Write(rw); err != nil { rw.Close() log.Errorf("write response from node %s(%s): %v", target.Name, target.Addr, err) diff --git a/handler/forward/remote/handler.go b/handler/forward/remote/handler.go index 6d1774e1..cefc51b3 100644 --- a/handler/forward/remote/handler.go +++ b/handler/forward/remote/handler.go @@ -37,6 +37,10 @@ import ( "github.com/go-gost/x/registry" ) +const ( + defaultBodySize = 1024 * 1024 * 10 // 10MB +) + func init() { registry.HandlerRegistry().Register("rtcp", NewHandler) registry.HandlerRegistry().Register("rudp", NewHandler) @@ -376,6 +380,18 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, cc = proxyproto.WrapClientConn(h.md.proxyProtocol, remoteAddr, localAddr, cc) + var reqBody *xhttp.Body + if opts := h.recorder.Options; opts != nil && opts.HTTPBody { + if req.Body != nil { + maxSize := opts.MaxBodySize + if maxSize <= 0 { + maxSize = defaultBodySize + } + reqBody = xhttp.NewBody(req.Body, maxSize) + req.Body = reqBody + } + } + if err = req.Write(cc); err != nil { cc.Close() log.Warnf("send request to node %s(%s): %v", target.Name, target.Addr, err) @@ -383,6 +399,11 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, return err } + if reqBody != nil { + ro.HTTP.Request.Body = reqBody.Content() + ro.HTTP.Request.ContentLength = reqBody.Length() + } + if req.Header.Get("Upgrade") == "websocket" { err := xnet.Transport(cc, xio.NewReadWriter(br, rw)) if err == nil { @@ -396,16 +417,21 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, var err error var res *http.Response + var respBody *xhttp.Body defer func() { ro.Duration = time.Since(start) if err != nil { ro.Err = err.Error() } - if res != nil && ro.HTTP != nil { - ro.HTTP.Response.ContentLength = res.ContentLength - ro.HTTP.Response.Header = res.Header + if res != nil { ro.HTTP.StatusCode = res.StatusCode + ro.HTTP.Response.Header = res.Header + ro.HTTP.Response.ContentLength = res.ContentLength + if respBody != nil { + ro.HTTP.Response.Body = respBody.Content() + ro.HTTP.Response.ContentLength = respBody.Length() + } } ro.Record(ctx, h.recorder.Recorder) }() @@ -433,6 +459,15 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, return } + if opts := h.recorder.Options; opts != nil && opts.HTTPBody { + maxSize := opts.MaxBodySize + if maxSize <= 0 { + maxSize = defaultBodySize + } + respBody = xhttp.NewBody(res.Body, maxSize) + res.Body = respBody + } + if err = res.Write(rw); err != nil { rw.Close() log.Errorf("write response from node %s(%s): %v", target.Name, target.Addr, err) diff --git a/handler/http/handler.go b/handler/http/handler.go index 15f1ac88..438b26f0 100644 --- a/handler/http/handler.go +++ b/handler/http/handler.go @@ -39,6 +39,10 @@ import ( "github.com/go-gost/x/registry" ) +const ( + defaultBodySize = 1024 * 1024 * 10 // 10MB +) + func init() { registry.HandlerRegistry().Register("http", NewHandler) } @@ -341,19 +345,17 @@ func (h *httpHandler) handleProxy(ctx context.Context, rw io.ReadWriteCloser, cc start := time.Now() ro.Time = start - if ro.HTTP != nil { - ro.HTTP = &xrecorder.HTTPRecorderObject{ - Host: req.Host, - Proto: req.Proto, - Scheme: req.URL.Scheme, - Method: req.Method, - URI: req.RequestURI, - StatusCode: resp.StatusCode, - Request: xrecorder.HTTPRequestRecorderObject{ - ContentLength: req.ContentLength, - Header: req.Header.Clone(), - }, - } + ro.HTTP = &xrecorder.HTTPRecorderObject{ + Host: req.Host, + Proto: req.Proto, + Scheme: req.URL.Scheme, + Method: req.Method, + URI: req.RequestURI, + StatusCode: resp.StatusCode, + Request: xrecorder.HTTPRequestRecorderObject{ + ContentLength: req.ContentLength, + Header: req.Header.Clone(), + }, } // HTTP/1.0 @@ -370,6 +372,18 @@ func (h *httpHandler) handleProxy(ctx context.Context, rw io.ReadWriteCloser, cc req.Header.Del("Gost-Target") req.Header.Del("X-Gost-Target") + var reqBody *xhttp.Body + if opts := h.recorder.Options; opts != nil && opts.HTTPBody { + if req.Body != nil { + maxSize := opts.MaxBodySize + if maxSize <= 0 { + maxSize = defaultBodySize + } + reqBody = xhttp.NewBody(req.Body, maxSize) + req.Body = reqBody + } + } + if err = req.Write(cc); err != nil { resp.Write(rw) @@ -380,19 +394,29 @@ func (h *httpHandler) handleProxy(ctx context.Context, rw io.ReadWriteCloser, cc return err } + if reqBody != nil { + ro.HTTP.Request.Body = reqBody.Content() + ro.HTTP.Request.ContentLength = reqBody.Length() + } + go func() { var err error var res *http.Response + var respBody *xhttp.Body defer func() { ro.Duration = time.Since(start) if err != nil { ro.Err = err.Error() } - if res != nil && ro.HTTP != nil { - ro.HTTP.Response.ContentLength = res.ContentLength - ro.HTTP.Response.Header = res.Header + if res != nil { ro.HTTP.StatusCode = res.StatusCode + ro.HTTP.Response.Header = res.Header + ro.HTTP.Response.ContentLength = res.ContentLength + if respBody != nil { + ro.HTTP.Response.Body = respBody.Content() + ro.HTTP.Response.ContentLength = respBody.Length() + } } ro.Record(ctx, h.recorder.Recorder) }() @@ -423,6 +447,15 @@ func (h *httpHandler) handleProxy(ctx context.Context, rw io.ReadWriteCloser, cc res.ProtoMinor = req.ProtoMinor } + if opts := h.recorder.Options; opts != nil && opts.HTTPBody { + maxSize := opts.MaxBodySize + if maxSize <= 0 { + maxSize = defaultBodySize + } + respBody = xhttp.NewBody(res.Body, maxSize) + res.Body = respBody + } + if err = res.Write(rw); err != nil { rw.Close() log.Errorf("write response: %v", err) diff --git a/handler/redirect/tcp/handler.go b/handler/redirect/tcp/handler.go index 64b4eeb5..7b03ecf9 100644 --- a/handler/redirect/tcp/handler.go +++ b/handler/redirect/tcp/handler.go @@ -30,6 +30,10 @@ import ( "github.com/go-gost/x/registry" ) +const ( + defaultBodySize = 1024 * 1024 * 10 // 10MB +) + func init() { registry.HandlerRegistry().Register("red", NewHandler) registry.HandlerRegistry().Register("redir", NewHandler) @@ -240,11 +244,27 @@ func (h *redirectHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, radd }).Infof("%s >-< %s", raddr, host) }() + var reqBody *xhttp.Body + if opts := h.recorder.Options; opts != nil && opts.HTTPBody { + if req.Body != nil { + maxSize := opts.MaxBodySize + if maxSize <= 0 { + maxSize = defaultBodySize + } + reqBody = xhttp.NewBody(req.Body, maxSize) + req.Body = reqBody + } + } if err := req.Write(cc); err != nil { log.Error(err) return err } + if reqBody != nil { + ro.HTTP.Request.Body = reqBody.Content() + ro.HTTP.Request.ContentLength = reqBody.Length() + } + br := bufio.NewReader(cc) resp, err := http.ReadResponse(br, req) if err != nil { @@ -253,20 +273,35 @@ func (h *redirectHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, radd } defer resp.Body.Close() + ro.HTTP.Response.Header = resp.Header ro.HTTP.StatusCode = resp.StatusCode ro.HTTP.Response.ContentLength = resp.ContentLength - ro.HTTP.Response.Header = resp.Header if log.IsLevelEnabled(logger.TraceLevel) { dump, _ := httputil.DumpResponse(resp, false) log.Trace(string(dump)) } + var respBody *xhttp.Body + if opts := h.recorder.Options; opts != nil && opts.HTTPBody { + maxSize := opts.MaxBodySize + if maxSize <= 0 { + maxSize = defaultBodySize + } + respBody = xhttp.NewBody(resp.Body, maxSize) + resp.Body = respBody + } + if err := resp.Write(rw); err != nil { log.Error(err) return err } + if respBody != nil { + ro.HTTP.Response.Body = respBody.Content() + ro.HTTP.Response.ContentLength = resp.ContentLength + } + netpkg.Transport(rw, xio.NewReadWriter(br, cc)) return nil diff --git a/handler/redirect/tcp/metadata.go b/handler/redirect/tcp/metadata.go index cc9d995b..3f251462 100644 --- a/handler/redirect/tcp/metadata.go +++ b/handler/redirect/tcp/metadata.go @@ -8,9 +8,9 @@ import ( ) type metadata struct { - tproxy bool - sniffing bool - sniffingTimeout time.Duration + tproxy bool + sniffing bool + sniffingTimeout time.Duration sniffingFallback bool } diff --git a/handler/sni/handler.go b/handler/sni/handler.go index f71f1f6a..abdccb71 100644 --- a/handler/sni/handler.go +++ b/handler/sni/handler.go @@ -32,6 +32,10 @@ import ( "github.com/go-gost/x/registry" ) +const ( + defaultBodySize = 1024 * 1024 * 10 // 10MB +) + func init() { registry.HandlerRegistry().Register("sni", NewHandler) } @@ -186,11 +190,28 @@ func (h *sniHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, raddr net }).Infof("%s >-< %s", raddr, host) }() + var reqBody *xhttp.Body + if opts := h.recorder.Options; opts != nil && opts.HTTPBody { + if req.Body != nil { + maxSize := opts.MaxBodySize + if maxSize <= 0 { + maxSize = defaultBodySize + } + reqBody = xhttp.NewBody(req.Body, maxSize) + req.Body = reqBody + } + } + if err := req.Write(cc); err != nil { log.Error(err) return err } + if reqBody != nil { + ro.HTTP.Request.Body = reqBody.Content() + ro.HTTP.Request.ContentLength = reqBody.Length() + } + br := bufio.NewReader(cc) resp, err := http.ReadResponse(br, req) if err != nil { @@ -208,11 +229,26 @@ func (h *sniHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, raddr net log.Trace(string(dump)) } + var respBody *xhttp.Body + if opts := h.recorder.Options; opts != nil && opts.HTTPBody { + maxSize := opts.MaxBodySize + if maxSize <= 0 { + maxSize = defaultBodySize + } + respBody = xhttp.NewBody(resp.Body, maxSize) + resp.Body = respBody + } + if err := resp.Write(rw); err != nil { log.Error(err) return err } + if respBody != nil { + ro.HTTP.Response.Body = respBody.Content() + ro.HTTP.Response.ContentLength = resp.ContentLength + } + netpkg.Transport(rw, xio.NewReadWriter(br, cc)) return nil diff --git a/handler/tunnel/entrypoint.go b/handler/tunnel/entrypoint.go index 84b1b696..0ced1560 100644 --- a/handler/tunnel/entrypoint.go +++ b/handler/tunnel/entrypoint.go @@ -30,6 +30,10 @@ import ( xrecorder "github.com/go-gost/x/recorder" ) +const ( + defaultBodySize = 1024 * 1024 * 10 // 10MB +) + type entrypoint struct { node string service string @@ -219,6 +223,18 @@ func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error { } } + var reqBody *xhttp.Body + if opts := ep.recorder.Options; opts != nil && opts.HTTPBody { + if req.Body != nil { + maxSize := opts.MaxBodySize + if maxSize <= 0 { + maxSize = defaultBodySize + } + reqBody = xhttp.NewBody(req.Body, maxSize) + req.Body = reqBody + } + } + if err = req.Write(c); err != nil { c.Close() log.Errorf("send request: %v", err) @@ -226,6 +242,11 @@ func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error { return err } + if reqBody != nil { + ro.HTTP.Request.Body = reqBody.Content() + ro.HTTP.Request.ContentLength = reqBody.Length() + } + if req.Header.Get("Upgrade") == "websocket" { err = xnet.Transport(c, xio.NewReadWriter(br, conn)) if err == nil { @@ -241,6 +262,7 @@ func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error { var err error var res *http.Response + var respBody *xhttp.Body defer func() { d := time.Since(start) @@ -252,10 +274,14 @@ func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error { if err != nil { ro.Err = err.Error() } - if res != nil && ro.HTTP != nil { + if res != nil { ro.HTTP.StatusCode = res.StatusCode - ro.HTTP.Response.ContentLength = res.ContentLength ro.HTTP.Response.Header = res.Header + ro.HTTP.Response.ContentLength = res.ContentLength + if respBody != nil { + ro.HTTP.Response.Body = respBody.Content() + ro.HTTP.Response.ContentLength = respBody.Length() + } } ro.Record(ctx, ep.recorder.Recorder) }() @@ -286,6 +312,15 @@ func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error { res.ProtoMinor = req.ProtoMinor } + if opts := ep.recorder.Options; opts != nil && opts.HTTPBody { + maxSize := opts.MaxBodySize + if maxSize <= 0 { + maxSize = defaultBodySize + } + respBody = xhttp.NewBody(res.Body, maxSize) + res.Body = respBody + } + if err = res.Write(conn); err != nil { conn.Close() log.Errorf("write response: %v", err) diff --git a/internal/net/http/http.go b/internal/net/http/http.go index 520d8073..4cd48be6 100644 --- a/internal/net/http/http.go +++ b/internal/net/http/http.go @@ -1,6 +1,8 @@ package http import ( + "bytes" + "io" "net" "net/http" "strings" @@ -24,3 +26,47 @@ func GetClientIP(req *http.Request) net.IP { return net.ParseIP(sip) } + +type Body struct { + r io.ReadCloser + buf bytes.Buffer + length int64 + recordSize int +} + +func NewBody(r io.ReadCloser, maxRecordSize int) *Body { + p := &Body{ + r: r, + recordSize: maxRecordSize, + } + + return p +} + +func (p *Body) Read(b []byte) (n int, err error) { + n, err = p.r.Read(b) + p.length += int64(n) + + if p.recordSize > 0 { + b = b[:n] + if n > p.recordSize { + b = b[:p.recordSize] + } + p.buf.Write(b) + p.recordSize -= n + } + + return +} + +func (p *Body) Close() error { + return p.r.Close() +} + +func (p *Body) Content() []byte { + return p.buf.Bytes() +} + +func (p *Body) Length() int64 { + return p.length +} diff --git a/recorder/recorder.go b/recorder/recorder.go index 7e90481a..752819c3 100644 --- a/recorder/recorder.go +++ b/recorder/recorder.go @@ -18,11 +18,13 @@ const ( type HTTPRequestRecorderObject struct { ContentLength int64 `json:"contentLength"` Header http.Header `json:"header"` + Body []byte `json:"body"` } type HTTPResponseRecorderObject struct { ContentLength int64 `json:"contentLength"` Header http.Header `json:"header"` + Body []byte `json:"body"` } type HTTPRecorderObject struct {