From e22aa91571bc14fa5caf72027fc81db6cb4992d8 Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Tue, 24 Sep 2024 20:20:15 +0800 Subject: [PATCH] add route for recorder --- chain/router.go | 15 +- config/config.go | 1 + config/parsing/node/parse.go | 1 + ctx/value.go | 20 +- go.mod | 4 +- go.sum | 8 +- handler/dns/handler.go | 6 +- handler/forward/local/handler.go | 9 +- handler/forward/remote/handler.go | 12 +- handler/http/handler.go | 5 +- handler/http2/handler.go | 4 +- handler/redirect/tcp/handler.go | 296 +++++++++++++++++++++++------- handler/redirect/udp/handler.go | 8 +- handler/relay/connect.go | 8 +- handler/relay/handler.go | 2 +- handler/sni/handler.go | 23 ++- handler/socks/v4/handler.go | 9 +- handler/socks/v5/connect.go | 8 +- handler/socks/v5/handler.go | 6 +- handler/socks/v5/udp.go | 8 +- handler/socks/v5/udp_tun.go | 8 +- handler/ss/handler.go | 5 +- handler/ss/udp/handler.go | 5 +- handler/sshd/handler.go | 5 +- internal/util/tls/tls.go | 2 + recorder/recorder.go | 3 + 26 files changed, 364 insertions(+), 117 deletions(-) diff --git a/chain/router.go b/chain/router.go index e777c1fa..69af36ec 100644 --- a/chain/router.go +++ b/chain/router.go @@ -108,14 +108,15 @@ func (r *Router) dial(ctx context.Context, network, address string, log logger.L route = r.options.Chain.Route(ctx, network, ipAddr, chain.WithHostRouteOption(address)) } - if log.IsLevelEnabled(logger.DebugLevel) { - buf := bytes.Buffer{} - for _, node := range routePath(route) { - fmt.Fprintf(&buf, "%s@%s > ", node.Name, node.Addr) - } - fmt.Fprintf(&buf, "%s", ipAddr) - log.Debugf("route(retry=%d) %s", i, buf.String()) + buf := ctxvalue.BufferFromContext(ctx) + if buf == nil { + buf = &bytes.Buffer{} } + for _, node := range routePath(route) { + fmt.Fprintf(buf, "%s@%s > ", node.Name, node.Addr) + } + fmt.Fprintf(buf, "%s", ipAddr) + log.Debugf("route(retry=%d) %s", i, buf.String()) if route == nil { route = DefaultRoute diff --git a/config/config.go b/config/config.go index 3cd58dde..15e77232 100644 --- a/config/config.go +++ b/config/config.go @@ -121,6 +121,7 @@ type TLSOptions struct { MinVersion string `yaml:"minVersion,omitempty" json:"minVersion,omitempty"` MaxVersion string `yaml:"maxVersion,omitempty" json:"maxVersion,omitempty"` CipherSuites []string `yaml:"cipherSuites,omitempty" json:"cipherSuites,omitempty"` + ALPN []string `yaml:"alpn,omitempty" json:"alpn,omitempty"` } type PluginConfig struct { diff --git a/config/parsing/node/parse.go b/config/parsing/node/parse.go index f99a305c..d758dcc9 100644 --- a/config/parsing/node/parse.go +++ b/config/parsing/node/parse.go @@ -220,6 +220,7 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No tlsCfg.Options.MinVersion = o.MinVersion tlsCfg.Options.MaxVersion = o.MaxVersion tlsCfg.Options.CipherSuites = o.CipherSuites + tlsCfg.Options.ALPN = o.ALPN } opts = append(opts, chain.TLSNodeOption(tlsCfg)) } diff --git a/ctx/value.go b/ctx/value.go index 46b3dc2c..1e1f6c3b 100644 --- a/ctx/value.go +++ b/ctx/value.go @@ -1,6 +1,9 @@ package ctx -import "context" +import ( + "bytes" + "context" +) // clientAddrKey saves the client address. type clientAddrKey struct{} @@ -74,3 +77,18 @@ func ClientIDFromContext(ctx context.Context) ClientID { v, _ := ctx.Value(keyClientID).(ClientID) return v } + +type bufferKey struct{} + +var ( + keyBuffer = &bufferKey{} +) + +func ContextWithBuffer(ctx context.Context, buffer *bytes.Buffer) context.Context { + return context.WithValue(ctx, keyBuffer, buffer) +} + +func BufferFromContext(ctx context.Context) *bytes.Buffer { + v, _ := ctx.Value(keyBuffer).(*bytes.Buffer) + return v +} diff --git a/go.mod b/go.mod index d51abbec..3fd89f25 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,12 @@ 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.4 + github.com/go-gost/core v0.1.5 github.com/go-gost/gosocks4 v0.0.1 github.com/go-gost/gosocks5 v0.4.2 github.com/go-gost/plugin v0.1.1 github.com/go-gost/relay v0.5.0 - github.com/go-gost/tls-dissector v0.1.0 + github.com/go-gost/tls-dissector v0.1.1 github.com/go-redis/redis/v8 v8.11.5 github.com/gobwas/glob v0.2.3 github.com/golang/snappy v0.0.4 diff --git a/go.sum b/go.sum index 3b92688b..01a0a883 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.4 h1:uP1r6kpi39NaFtKo6guCjMSvgJl8eTWW3Lb/8+4YDdo= -github.com/go-gost/core v0.1.4/go.mod h1:WGI43jOka7FAsSAwi/fSMaqxdR+E339ycb4NBGlFr6A= +github.com/go-gost/core v0.1.5 h1:Rbj+xq+q2RkxviUErjwyT8wbqCa5WeV+UelyIs99dFI= +github.com/go-gost/core v0.1.5/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= @@ -63,8 +63,8 @@ github.com/go-gost/plugin v0.1.1 h1:LNoc/Rqwb3ceGhhxwhpjf1SeeYBGe80MJ9E4lpM3qak= github.com/go-gost/plugin v0.1.1/go.mod h1:oN23l+yGDCIP9G3KnDl/I/0zVGOobZUDCB2Z5yYYXts= github.com/go-gost/relay v0.5.0 h1:JG1tgy/KWiVXS0ukuVXvbM0kbYuJTWxYpJ5JwzsCf/c= github.com/go-gost/relay v0.5.0/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8= -github.com/go-gost/tls-dissector v0.1.0 h1:hpgzC/Qt9MMGGie+hLeBAZ379OG72sSc1TRiG7+rahI= -github.com/go-gost/tls-dissector v0.1.0/go.mod h1:/9QfdewqmHdaE362Hv5nDaSWLx3pCmtD870d6GaquXs= +github.com/go-gost/tls-dissector v0.1.1 h1:2zUOTPzCQAUQ54Rpy0UEi3JPMQSYsIFSeFeKrzmkCoU= +github.com/go-gost/tls-dissector v0.1.1/go.mod h1:/9QfdewqmHdaE362Hv5nDaSWLx3pCmtD870d6GaquXs= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= diff --git a/handler/dns/handler.go b/handler/dns/handler.go index 34280ddf..2f3ddc28 100644 --- a/handler/dns/handler.go +++ b/handler/dns/handler.go @@ -1,6 +1,7 @@ package dns import ( + "bytes" "context" "errors" "fmt" @@ -290,7 +291,10 @@ func (h *dnsHandler) request(ctx context.Context, msg []byte, ro *xrecorder.Hand } log.Debugf("exchange message %d: %s", mq.Id, mq.Question[0].String()) - mr, err := h.exchange(ctx, ex, &mq) + + var buf bytes.Buffer + mr, err := h.exchange(ctxvalue.ContextWithBuffer(ctx, &buf), ex, &mq) + ro.Route = buf.String() if err != nil { return nil, err } diff --git a/handler/forward/local/handler.go b/handler/forward/local/handler.go index d9b1779a..a2fb6db3 100644 --- a/handler/forward/local/handler.go +++ b/handler/forward/local/handler.go @@ -196,7 +196,9 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand log.Debugf("%s >> %s", conn.RemoteAddr(), addr) - cc, err := h.options.Router.Dial(ctx, network, addr) + var buf bytes.Buffer + cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), network, addr) + ro.Route = buf.String() if err != nil { log.Error(err) // TODO: the router itself may be failed due to the failed node in the router, @@ -351,7 +353,9 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, bodyRewrites = httpSettings.RewriteBody } - cc, err = h.options.Router.Dial(ctx, "tcp", target.Addr) + var buf bytes.Buffer + cc, err = h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", target.Addr) + ro.Route = buf.String() if err != nil { // TODO: the router itself may be failed due to the failed node in the router, // the dead marker may be a wrong operation. @@ -376,6 +380,7 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, MinVersion: tlsSettings.Options.MinVersion, MaxVersion: tlsSettings.Options.MaxVersion, CipherSuites: tlsSettings.Options.CipherSuites, + ALPN: tlsSettings.Options.ALPN, }) cc = tls.Client(cc, cfg) } diff --git a/handler/forward/remote/handler.go b/handler/forward/remote/handler.go index 89de7c15..4f39e388 100644 --- a/handler/forward/remote/handler.go +++ b/handler/forward/remote/handler.go @@ -195,7 +195,9 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand log.Debugf("%s >> %s", conn.RemoteAddr(), target.Addr) - cc, err := h.options.Router.Dial(ctx, network, target.Addr) + var buf bytes.Buffer + cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), network, target.Addr) + ro.Route = buf.String() if err != nil { log.Error(err) // TODO: the router itself may be failed due to the failed node in the router, @@ -278,7 +280,8 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, if _, _, err := net.SplitHostPort(host); err != nil { host = net.JoinHostPort(strings.Trim(host, "[]"), "80") } - if bp := h.options.Bypass; bp != nil && bp.Contains(ctx, "tcp", host, bypass.WithPathOption(req.RequestURI)) { + if bp := h.options.Bypass; bp != nil && + bp.Contains(ctx, "tcp", host, bypass.WithPathOption(req.RequestURI)) { log.Debugf("bypass: %s %s", host, req.RequestURI) resp.StatusCode = http.StatusForbidden resp.Write(rw) @@ -350,7 +353,9 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, bodyRewrites = httpSettings.RewriteBody } - cc, err = h.options.Router.Dial(ctx, "tcp", target.Addr) + var buf bytes.Buffer + cc, err = h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", target.Addr) + ro.Route = buf.String() if err != nil { // TODO: the router itself may be failed due to the failed node in the router, // the dead marker may be a wrong operation. @@ -375,6 +380,7 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, MinVersion: tlsSettings.Options.MinVersion, MaxVersion: tlsSettings.Options.MaxVersion, CipherSuites: tlsSettings.Options.CipherSuites, + ALPN: tlsSettings.Options.ALPN, }) cc = tls.Client(cc, cfg) } diff --git a/handler/http/handler.go b/handler/http/handler.go index faabed6e..f840f848 100644 --- a/handler/http/handler.go +++ b/handler/http/handler.go @@ -2,6 +2,7 @@ package http import ( "bufio" + "bytes" "context" "encoding/base64" "encoding/binary" @@ -267,7 +268,9 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: addr}) } - cc, err := h.options.Router.Dial(ctx, network, addr) + var buf bytes.Buffer + cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), network, addr) + ro.Route = buf.String() if err != nil { resp.StatusCode = http.StatusServiceUnavailable diff --git a/handler/http2/handler.go b/handler/http2/handler.go index d3c21592..42002bff 100644 --- a/handler/http2/handler.go +++ b/handler/http2/handler.go @@ -243,7 +243,9 @@ func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: addr}) } - cc, err := h.options.Router.Dial(ctx, "tcp", addr) + var buf bytes.Buffer + cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", addr) + ro.Route = buf.String() if err != nil { log.Error(err) resp.StatusCode = http.StatusServiceUnavailable diff --git a/handler/redirect/tcp/handler.go b/handler/redirect/tcp/handler.go index f955a2a3..7904b0d1 100644 --- a/handler/redirect/tcp/handler.go +++ b/handler/redirect/tcp/handler.go @@ -6,6 +6,8 @@ import ( "context" "crypto/tls" "encoding/binary" + "encoding/hex" + "errors" "fmt" "io" "net" @@ -133,7 +135,7 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han "dst": fmt.Sprintf("%s/%s", dstAddr, dstAddr.Network()), }) - var rw io.ReadWriter = conn + var rw io.ReadWriteCloser = conn if h.md.sniffing { if h.md.sniffingTimeout > 0 { conn.SetReadDeadline(time.Now().Add(h.md.sniffingTimeout)) @@ -144,7 +146,7 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han if h.md.sniffingTimeout > 0 { conn.SetReadDeadline(time.Time{}) } - rw = xio.NewReadWriter(io.MultiReader(bytes.NewReader(hdr[:n]), rw), rw) + rw = xio.NewReadWriteCloser(io.MultiReader(bytes.NewReader(hdr[:n]), rw), rw, rw) tlsVersion := binary.BigEndian.Uint16(hdr[1:3]) if err == nil && hdr[0] == dissector.Handshake && @@ -154,18 +156,24 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han // try to sniff HTTP traffic if isHTTP(string(hdr[:])) { - return h.handleHTTP(ctx, rw, conn.RemoteAddr(), dstAddr, ro, log) + ro2 := &xrecorder.HandlerRecorderObject{} + *ro2 = *ro + ro.Time = time.Time{} + return h.handleHTTP(ctx, rw, conn.RemoteAddr(), dstAddr, ro2, log) } } log.Debugf("%s >> %s", conn.RemoteAddr(), dstAddr) - if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, dstAddr.Network(), dstAddr.String()) { + if h.options.Bypass != nil && + h.options.Bypass.Contains(ctx, dstAddr.Network(), dstAddr.String()) { log.Debug("bypass: ", dstAddr) return xbypass.ErrBypass } - cc, err := h.options.Router.Dial(ctx, dstAddr.Network(), dstAddr.String()) + var buf bytes.Buffer + cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), dstAddr.Network(), dstAddr.String()) + ro.Route = buf.String() if err != nil { log.Error(err) return err @@ -182,27 +190,13 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han return nil } -func (h *redirectHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, raddr, dstAddr net.Addr, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error { - req, err := http.ReadRequest(bufio.NewReader(rw)) +func (h *redirectHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, raddr, dstAddr net.Addr, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error { + br := bufio.NewReader(rw) + req, err := http.ReadRequest(br) if err != nil { return err } - ro.HTTP = &xrecorder.HTTPRecorderObject{ - Host: req.Host, - Proto: req.Proto, - Scheme: req.URL.Scheme, - Method: req.Method, - URI: req.RequestURI, - Request: xrecorder.HTTPRequestRecorderObject{ - ContentLength: req.ContentLength, - Header: req.Header, - }, - } - if clientIP := xhttp.GetClientIP(req); clientIP != nil { - ro.ClientIP = clientIP.String() - } - if log.IsLevelEnabled(logger.TraceLevel) { dump, _ := httputil.DumpRequest(req, false) log.Trace(string(dump)) @@ -217,12 +211,15 @@ func (h *redirectHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, radd "host": host, }) - if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, "tcp", host, bypass.WithPathOption(req.RequestURI)) { + if h.options.Bypass != nil && + h.options.Bypass.Contains(ctx, "tcp", host, bypass.WithPathOption(req.RequestURI)) { log.Debugf("bypass: %s %s", host, req.RequestURI) return xbypass.ErrBypass } - cc, err := h.options.Router.Dial(ctx, "tcp", host) + var buf bytes.Buffer + cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", host) + ro.Route = buf.String() if err != nil { log.Error(err) if !h.md.sniffingFallback { @@ -231,7 +228,9 @@ func (h *redirectHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, radd } if cc == nil { - cc, err = h.options.Router.Dial(ctx, "tcp", dstAddr.String()) + var buf bytes.Buffer + cc, err = h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", dstAddr.String()) + ro.Route = buf.String() if err != nil { log.Error(err) return err @@ -239,14 +238,138 @@ func (h *redirectHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, radd } defer cc.Close() - t := time.Now() - log.Infof("%s <-> %s", raddr, host) + if req.Header.Get("Upgrade") == "websocket" { + return h.handleWebsocket(ctx, raddr, rw, cc, req, ro, log) + } + + if err := h.httpRoundTrip(ctx, raddr, rw, cc, req, ro, log); err != nil { + return err + } + + for { + req, err := http.ReadRequest(br) + if err != nil { + if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) { + return nil + } + return err + } + + if log.IsLevelEnabled(logger.TraceLevel) { + dump, _ := httputil.DumpRequest(req, false) + log.Trace(string(dump)) + } + + if err = h.httpRoundTrip(ctx, raddr, rw, cc, req, ro, log); err != nil { + return err + } + } +} + +func (h *redirectHandler) handleWebsocket(ctx context.Context, raddr net.Addr, rw io.ReadWriteCloser, cc io.ReadWriter, req *http.Request, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) { + start := time.Now() + ro.Time = start + + log.Infof("%s <-> %s", raddr, req.Host) + defer func() { + ro.Duration = time.Since(start) + if err != nil { + ro.Err = err.Error() + } + if err := ro.Record(ctx, h.recorder.Recorder); err != nil { + log.Errorf("record: %v", err) + } + log.WithFields(map[string]any{ - "duration": time.Since(t), - }).Infof("%s >-< %s", raddr, host) + "duration": time.Since(start), + }).Infof("%s >-< %s", raddr, req.Host) }() + ro.HTTP = &xrecorder.HTTPRecorderObject{ + Host: req.Host, + Proto: req.Proto, + Scheme: req.URL.Scheme, + Method: req.Method, + URI: req.RequestURI, + Request: xrecorder.HTTPRequestRecorderObject{ + ContentLength: req.ContentLength, + Header: req.Header.Clone(), + }, + } + + if err = req.Write(cc); err != nil { + return + } + + res, err := http.ReadResponse(bufio.NewReader(cc), req) + if err != nil { + return + } + defer res.Body.Close() + + ro.HTTP.StatusCode = res.StatusCode + ro.HTTP.Response.Header = res.Header + ro.HTTP.Response.ContentLength = res.ContentLength + + if log.IsLevelEnabled(logger.TraceLevel) { + dump, _ := httputil.DumpResponse(res, false) + log.Trace(string(dump)) + } + + if err = res.Write(rw); err != nil { + return + } + + netpkg.Transport(rw, cc) + + return +} + +func (h *redirectHandler) httpRoundTrip(ctx context.Context, raddr net.Addr, rw io.ReadWriteCloser, cc io.ReadWriter, req *http.Request, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) { + if req == nil { + return nil + } + + start := time.Now() + ro.Time = start + + log.Infof("%s <-> %s", raddr, req.Host) + defer func() { + if err != nil { + ro.Duration = time.Since(start) + ro.Err = err.Error() + if err := ro.Record(ctx, h.recorder.Recorder); err != nil { + log.Errorf("record: %v", err) + } + + log.WithFields(map[string]any{ + "duration": time.Since(start), + }).Infof("%s >-< %s", raddr, req.Host) + } + }() + + ro.HTTP = &xrecorder.HTTPRecorderObject{ + Host: req.Host, + Proto: req.Proto, + Scheme: req.URL.Scheme, + Method: req.Method, + URI: req.RequestURI, + Request: xrecorder.HTTPRequestRecorderObject{ + ContentLength: req.ContentLength, + Header: req.Header.Clone(), + }, + } + + // HTTP/1.0 + if req.ProtoMajor == 1 && req.ProtoMinor == 0 { + if strings.ToLower(req.Header.Get("Connection")) == "keep-alive" { + req.Header.Del("Connection") + } else { + req.Header.Set("Connection", "close") + } + } + var reqBody *xhttp.Body if opts := h.recorder.Options; opts != nil && opts.HTTPBody { if req.Body != nil { @@ -258,8 +381,8 @@ func (h *redirectHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, radd req.Body = reqBody } } - if err := req.Write(cc); err != nil { - log.Error(err) + + if err = req.Write(cc); err != nil { return err } @@ -268,44 +391,68 @@ func (h *redirectHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, radd ro.HTTP.Request.ContentLength = reqBody.Length() } - br := bufio.NewReader(cc) - resp, err := http.ReadResponse(br, req) - if err != nil { - log.Error(err) - return err - } - defer resp.Body.Close() + go func() { + var err error + var res *http.Response + var respBody *xhttp.Body - ro.HTTP.Response.Header = resp.Header - ro.HTTP.StatusCode = resp.StatusCode - ro.HTTP.Response.ContentLength = resp.ContentLength + defer func() { + ro.Duration = time.Since(start) + if err != nil { + ro.Err = err.Error() + } + 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) + }() - 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 + res, err = http.ReadResponse(bufio.NewReader(cc), req) + if err != nil { + rw.Close() + log.Errorf("read response: %v", err) + return } - respBody = xhttp.NewBody(resp.Body, maxSize) - resp.Body = respBody - } + defer res.Body.Close() - if err := resp.Write(rw); err != nil { - log.Error(err) - return err - } + if log.IsLevelEnabled(logger.TraceLevel) { + dump, _ := httputil.DumpResponse(res, false) + log.Trace(string(dump)) + } - if respBody != nil { - ro.HTTP.Response.Body = respBody.Content() - ro.HTTP.Response.ContentLength = resp.ContentLength - } + if res.Close { + defer rw.Close() + } - netpkg.Transport(rw, xio.NewReadWriter(br, cc)) + // HTTP/1.0 + if req.ProtoMajor == 1 && req.ProtoMinor == 0 { + if !res.Close { + res.Header.Set("Connection", "keep-alive") + } + res.ProtoMajor = req.ProtoMajor + 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) + } + }() return nil } @@ -318,7 +465,8 @@ func (h *redirectHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, rad return err } ro.TLS = &xrecorder.TLSRecorderObject{ - ServerName: clientHello.ServerName, + ServerName: clientHello.ServerName, + ClientHello: hex.EncodeToString(buf.Bytes()), } if len(clientHello.SupportedProtos) > 0 { ro.TLS.Proto = clientHello.SupportedProtos[0] @@ -340,12 +488,15 @@ func (h *redirectHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, rad }) ro.Host = host - if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, "tcp", host) { + if h.options.Bypass != nil && + h.options.Bypass.Contains(ctx, "tcp", host) { log.Debug("bypass: ", host) return xbypass.ErrBypass } - cc, err = h.options.Router.Dial(ctx, "tcp", host) + var routeBuf bytes.Buffer + cc, err = h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &routeBuf), "tcp", host) + ro.Route = routeBuf.String() if err != nil { log.Error(err) @@ -356,7 +507,9 @@ func (h *redirectHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, rad } if cc == nil { - cc, err = h.options.Router.Dial(ctx, "tcp", dstAddr.String()) + var routeBuf bytes.Buffer + cc, err = h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &routeBuf), "tcp", dstAddr.String()) + ro.Route = routeBuf.String() if err != nil { log.Error(err) return err @@ -368,7 +521,8 @@ func (h *redirectHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, rad return err } - if serverHello, _ := dissector.ParseServerHello(io.TeeReader(cc, buf)); serverHello != nil { + serverHello, err := dissector.ParseServerHello(io.TeeReader(cc, buf)) + if serverHello != nil { ro.TLS.CipherSuite = tls_util.CipherSuite(serverHello.CipherSuite).String() ro.TLS.CompressionMethod = serverHello.CompressionMethod if serverHello.Proto != "" { @@ -379,6 +533,10 @@ func (h *redirectHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, rad } } + if buf.Len() > 0 { + ro.TLS.ServerHello = hex.EncodeToString(buf.Bytes()) + } + if _, err := buf.WriteTo(rw); err != nil { return err } @@ -390,7 +548,7 @@ func (h *redirectHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, rad "duration": time.Since(t), }).Infof("%s >-< %s", raddr, host) - return nil + return err } func (h *redirectHandler) checkRateLimit(addr net.Addr) bool { diff --git a/handler/redirect/udp/handler.go b/handler/redirect/udp/handler.go index d8a8e547..b2c5118b 100644 --- a/handler/redirect/udp/handler.go +++ b/handler/redirect/udp/handler.go @@ -1,6 +1,7 @@ package redirect import ( + "bytes" "context" "fmt" "net" @@ -100,12 +101,15 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han log.Debugf("%s >> %s", conn.RemoteAddr(), dstAddr) - if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, dstAddr.Network(), dstAddr.String()) { + if h.options.Bypass != nil && + h.options.Bypass.Contains(ctx, dstAddr.Network(), dstAddr.String()) { log.Debug("bypass: ", dstAddr) return xbypass.ErrBypass } - cc, err := h.options.Router.Dial(ctx, dstAddr.Network(), dstAddr.String()) + var buf bytes.Buffer + cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), dstAddr.Network(), dstAddr.String()) + ro.Route = buf.String() if err != nil { log.Error(err) return err diff --git a/handler/relay/connect.go b/handler/relay/connect.go index 42f1b73a..643e709a 100644 --- a/handler/relay/connect.go +++ b/handler/relay/connect.go @@ -1,6 +1,7 @@ package relay import ( + "bytes" "context" "errors" "fmt" @@ -18,9 +19,10 @@ import ( serial "github.com/go-gost/x/internal/util/serial" traffic_wrapper "github.com/go-gost/x/limiter/traffic/wrapper" stats_wrapper "github.com/go-gost/x/observer/stats/wrapper" + xrecorder "github.com/go-gost/x/recorder" ) -func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (err error) { +func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network, address string, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) { if network == "unix" || network == "serial" { if host, _, _ := net.SplitHostPort(address); host != "" { address = host @@ -67,7 +69,9 @@ func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network case "serial": cc, err = serial.OpenPort(serial.ParseConfigFromAddr(address)) default: - cc, err = h.options.Router.Dial(ctx, network, address) + var buf bytes.Buffer + cc, err = h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), network, address) + ro.Route = buf.String() } if err != nil { resp.Status = relay.StatusNetworkUnreachable diff --git a/handler/relay/handler.go b/handler/relay/handler.go index 7580392f..64386050 100644 --- a/handler/relay/handler.go +++ b/handler/relay/handler.go @@ -193,7 +193,7 @@ func (h *relayHandler) Handle(ctx context.Context, conn net.Conn, opts ...handle switch req.Cmd & relay.CmdMask { case 0, relay.CmdConnect: - return h.handleConnect(ctx, conn, network, address, log) + return h.handleConnect(ctx, conn, network, address, ro, log) case relay.CmdBind: return h.handleBind(ctx, conn, network, address, log) default: diff --git a/handler/sni/handler.go b/handler/sni/handler.go index d2204a26..ae3e728a 100644 --- a/handler/sni/handler.go +++ b/handler/sni/handler.go @@ -7,6 +7,7 @@ import ( "crypto/tls" "encoding/base64" "encoding/binary" + "encoding/hex" "errors" "hash/crc32" "io" @@ -176,7 +177,9 @@ func (h *sniHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, raddr net ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: host}) } - cc, err := h.options.Router.Dial(ctx, "tcp", host) + var buf bytes.Buffer + cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", host) + ro.Route = buf.String() if err != nil { log.Error(err) return err @@ -247,7 +250,7 @@ func (h *sniHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, raddr net if respBody != nil { ro.HTTP.Response.Body = respBody.Content() - ro.HTTP.Response.ContentLength = resp.ContentLength + ro.HTTP.Response.ContentLength = respBody.Length() } netpkg.Transport(rw, xio.NewReadWriter(br, cc)) @@ -268,7 +271,8 @@ func (h *sniHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, raddr ne return err } ro.TLS = &xrecorder.TLSRecorderObject{ - ServerName: clientHello.ServerName, + ServerName: clientHello.ServerName, + ClientHello: hex.EncodeToString(buf.Bytes()), } if len(clientHello.SupportedProtos) > 0 { ro.TLS.Proto = clientHello.SupportedProtos[0] @@ -295,7 +299,9 @@ func (h *sniHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, raddr ne ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: host}) } - cc, err := h.options.Router.Dial(ctx, "tcp", host) + var routeBuf bytes.Buffer + cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &routeBuf), "tcp", host) + ro.Route = routeBuf.String() if err != nil { log.Error(err) return err @@ -306,7 +312,8 @@ func (h *sniHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, raddr ne return err } - if serverHello, _ := dissector.ParseServerHello(io.TeeReader(cc, buf)); serverHello != nil { + serverHello, err := dissector.ParseServerHello(io.TeeReader(cc, buf)) + if serverHello != nil { ro.TLS.CipherSuite = tls_util.CipherSuite(serverHello.CipherSuite).String() ro.TLS.CompressionMethod = serverHello.CompressionMethod if serverHello.Proto != "" { @@ -317,6 +324,10 @@ func (h *sniHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, raddr ne } } + if buf.Len() > 0 { + ro.TLS.ServerHello = hex.EncodeToString(buf.Bytes()) + } + if _, err := buf.WriteTo(rw); err != nil { return err } @@ -328,7 +339,7 @@ func (h *sniHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, raddr ne "duration": time.Since(t), }).Infof("%s >-< %s", raddr, host) - return nil + return err } func (h *sniHandler) decodeHost(r io.Reader) (host string, err error) { diff --git a/handler/socks/v4/handler.go b/handler/socks/v4/handler.go index 451b87e6..88e81427 100644 --- a/handler/socks/v4/handler.go +++ b/handler/socks/v4/handler.go @@ -1,6 +1,7 @@ package v4 import ( + "bytes" "context" "errors" "net" @@ -148,7 +149,7 @@ func (h *socks4Handler) Handle(ctx context.Context, conn net.Conn, opts ...handl switch req.Cmd { case gosocks4.CmdConnect: - return h.handleConnect(ctx, conn, req, log) + return h.handleConnect(ctx, conn, req, ro, log) case gosocks4.CmdBind: return h.handleBind(ctx, conn, req) default: @@ -165,7 +166,7 @@ func (h *socks4Handler) Close() error { return nil } -func (h *socks4Handler) handleConnect(ctx context.Context, conn net.Conn, req *gosocks4.Request, log logger.Logger) error { +func (h *socks4Handler) handleConnect(ctx context.Context, conn net.Conn, req *gosocks4.Request, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error { addr := req.Addr.String() log = log.WithFields(map[string]any{ @@ -185,7 +186,9 @@ func (h *socks4Handler) handleConnect(ctx context.Context, conn net.Conn, req *g ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: addr}) } - cc, err := h.options.Router.Dial(ctx, "tcp", addr) + var buf bytes.Buffer + cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", addr) + ro.Route = buf.String() if err != nil { resp := gosocks4.NewReply(gosocks4.Failed, nil) log.Trace(resp) diff --git a/handler/socks/v5/connect.go b/handler/socks/v5/connect.go index b532439a..c40fe282 100644 --- a/handler/socks/v5/connect.go +++ b/handler/socks/v5/connect.go @@ -1,6 +1,7 @@ package v5 import ( + "bytes" "context" "fmt" "net" @@ -14,9 +15,10 @@ import ( netpkg "github.com/go-gost/x/internal/net" traffic_wrapper "github.com/go-gost/x/limiter/traffic/wrapper" stats_wrapper "github.com/go-gost/x/observer/stats/wrapper" + xrecorder "github.com/go-gost/x/recorder" ) -func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) error { +func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, network, address string, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error { log = log.WithFields(map[string]any{ "dst": fmt.Sprintf("%s/%s", address, network), "cmd": "connect", @@ -35,7 +37,9 @@ func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, networ ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: address}) } - cc, err := h.options.Router.Dial(ctx, network, address) + var buf bytes.Buffer + cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), network, address) + ro.Route = buf.String() if err != nil { resp := gosocks5.NewReply(gosocks5.NetUnreachable, nil) log.Trace(resp) diff --git a/handler/socks/v5/handler.go b/handler/socks/v5/handler.go index 444653fc..fe82d4b3 100644 --- a/handler/socks/v5/handler.go +++ b/handler/socks/v5/handler.go @@ -148,17 +148,17 @@ func (h *socks5Handler) Handle(ctx context.Context, conn net.Conn, opts ...handl switch req.Cmd { case gosocks5.CmdConnect: - return h.handleConnect(ctx, conn, "tcp", address, log) + return h.handleConnect(ctx, conn, "tcp", address, ro, log) case gosocks5.CmdBind: return h.handleBind(ctx, conn, "tcp", address, log) case socks.CmdMuxBind: return h.handleMuxBind(ctx, conn, "tcp", address, log) case gosocks5.CmdUdp: ro.Network = "udp" - return h.handleUDP(ctx, conn, log) + return h.handleUDP(ctx, conn, ro, log) case socks.CmdUDPTun: ro.Network = "udp" - return h.handleUDPTun(ctx, conn, "udp", address, log) + return h.handleUDPTun(ctx, conn, "udp", address, ro, log) default: err = ErrUnknownCmd log.Error(err) diff --git a/handler/socks/v5/udp.go b/handler/socks/v5/udp.go index 95f26697..9221b6d6 100644 --- a/handler/socks/v5/udp.go +++ b/handler/socks/v5/udp.go @@ -1,6 +1,7 @@ package v5 import ( + "bytes" "context" "errors" "fmt" @@ -16,9 +17,10 @@ import ( "github.com/go-gost/x/internal/net/udp" "github.com/go-gost/x/internal/util/socks" stats_wrapper "github.com/go-gost/x/observer/stats/wrapper" + xrecorder "github.com/go-gost/x/recorder" ) -func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn, log logger.Logger) error { +func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error { log = log.WithFields(map[string]any{ "cmd": "udp", }) @@ -59,7 +61,9 @@ func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn, log logger log.Debugf("bind on %s OK", cc.LocalAddr()) // obtain a udp connection - c, err := h.options.Router.Dial(ctx, "udp", "") // UDP association + var buf bytes.Buffer + c, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "udp", "") // UDP association + ro.Route = buf.String() if err != nil { log.Error(err) return err diff --git a/handler/socks/v5/udp_tun.go b/handler/socks/v5/udp_tun.go index b9d1ccbf..81df6d35 100644 --- a/handler/socks/v5/udp_tun.go +++ b/handler/socks/v5/udp_tun.go @@ -1,6 +1,7 @@ package v5 import ( + "bytes" "context" "errors" "net" @@ -14,9 +15,10 @@ import ( "github.com/go-gost/x/internal/net/udp" "github.com/go-gost/x/internal/util/socks" stats_wrapper "github.com/go-gost/x/observer/stats/wrapper" + xrecorder "github.com/go-gost/x/recorder" ) -func (h *socks5Handler) handleUDPTun(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) error { +func (h *socks5Handler) handleUDPTun(ctx context.Context, conn net.Conn, network, address string, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error { log = log.WithFields(map[string]any{ "cmd": "udp-tun", }) @@ -37,7 +39,9 @@ func (h *socks5Handler) handleUDPTun(ctx context.Context, conn net.Conn, network } // obtain a udp connection - c, err := h.options.Router.Dial(ctx, "udp", "") // UDP association + var buf bytes.Buffer + c, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "udp", "") // UDP association + ro.Route = buf.String() if err != nil { log.Error(err) return err diff --git a/handler/ss/handler.go b/handler/ss/handler.go index 1f8ce0a3..e4b190f8 100644 --- a/handler/ss/handler.go +++ b/handler/ss/handler.go @@ -1,6 +1,7 @@ package ss import ( + "bytes" "context" "io" "net" @@ -136,7 +137,9 @@ func (h *ssHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.H ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: addr.String()}) } - cc, err := h.options.Router.Dial(ctx, "tcp", addr.String()) + var buf bytes.Buffer + cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", addr.String()) + ro.Route = buf.String() if err != nil { return err } diff --git a/handler/ss/udp/handler.go b/handler/ss/udp/handler.go index 686bbf33..7395d570 100644 --- a/handler/ss/udp/handler.go +++ b/handler/ss/udp/handler.go @@ -1,6 +1,7 @@ package ss import ( + "bytes" "context" "errors" "net" @@ -120,7 +121,9 @@ func (h *ssuHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler. } // obtain a udp connection - c, err := h.options.Router.Dial(ctx, "udp", "") // UDP association + var buf bytes.Buffer + c, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "udp", "") // UDP association + ro.Route = buf.String() if err != nil { log.Error(err) return err diff --git a/handler/sshd/handler.go b/handler/sshd/handler.go index 1f21201d..98c18f04 100644 --- a/handler/sshd/handler.go +++ b/handler/sshd/handler.go @@ -1,6 +1,7 @@ package ssh import ( + "bytes" "context" "encoding/binary" "errors" @@ -132,7 +133,9 @@ func (h *forwardHandler) handleDirectForward(ctx context.Context, conn *sshd_uti return xbypass.ErrBypass } - cc, err := h.options.Router.Dial(ctx, "tcp", targetAddr) + var buf bytes.Buffer + cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", targetAddr) + ro.Route = buf.String() if err != nil { return err } diff --git a/internal/util/tls/tls.go b/internal/util/tls/tls.go index df62aa31..64b48ec6 100644 --- a/internal/util/tls/tls.go +++ b/internal/util/tls/tls.go @@ -291,6 +291,8 @@ func SetTLSOptions(cfg *tls.Config, opts *config.TLSOptions) { cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256) } } + + cfg.NextProtos = opts.ALPN } func loadCA(caFile string) (cp *x509.CertPool, err error) { diff --git a/recorder/recorder.go b/recorder/recorder.go index 782d71c8..4fb25f91 100644 --- a/recorder/recorder.go +++ b/recorder/recorder.go @@ -44,6 +44,8 @@ type TLSRecorderObject struct { CompressionMethod uint8 `json:"compressionMethod"` Proto string `json:"proto"` Version string `json:"version"` + ClientHello string `json:"clientHello"` + ServerHello string `json:"serverHello"` } type DNSRecorderObject struct { @@ -68,6 +70,7 @@ type HandlerRecorderObject struct { HTTP *HTTPRecorderObject `json:"http,omitempty"` TLS *TLSRecorderObject `json:"tls,omitempty"` DNS *DNSRecorderObject `json:"dns,omitempty"` + Route string `json:"route,omitempty"` Err string `json:"err,omitempty"` Duration time.Duration `json:"duration"` Time time.Time `json:"time"`