sniffing websocket frame

This commit is contained in:
ginuerzh
2024-10-19 19:36:06 +08:00
parent a0cbee8817
commit 6b932e35bf
28 changed files with 1238 additions and 301 deletions
+10 -8
View File
@@ -157,14 +157,16 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
return cc, err return cc, err
} }
sniffer := &forwarder.Sniffer{ sniffer := &forwarder.Sniffer{
Recorder: h.recorder.Recorder, Websocket: h.md.sniffingWebsocket,
RecorderOptions: h.recorder.Options, WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Certificate: h.md.certificate, Recorder: h.recorder.Recorder,
PrivateKey: h.md.privateKey, RecorderOptions: h.recorder.Options,
NegotiatedProtocol: h.md.alpn, Certificate: h.md.certificate,
CertPool: h.certPool, PrivateKey: h.md.privateKey,
MitmBypass: h.md.mitmBypass, NegotiatedProtocol: h.md.alpn,
ReadTimeout: h.md.readTimeout, CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
} }
conn = xnet.NewReadWriteConn(br, conn, conn) conn = xnet.NewReadWriteConn(br, conn, conn)
+10 -6
View File
@@ -13,11 +13,13 @@ import (
) )
type metadata struct { type metadata struct {
readTimeout time.Duration readTimeout time.Duration
httpKeepalive bool
sniffing bool sniffing bool
sniffingTimeout time.Duration sniffingTimeout time.Duration
httpKeepalive bool sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate certificate *x509.Certificate
privateKey crypto.PrivateKey privateKey crypto.PrivateKey
@@ -31,10 +33,12 @@ func (h *forwardHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.readTimeout = 15 * time.Second h.md.readTimeout = 15 * time.Second
} }
h.md.httpKeepalive = mdutil.GetBool(md, "http.keepalive")
h.md.sniffing = mdutil.GetBool(md, "sniffing") h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout") h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.httpKeepalive = mdutil.GetBool(md, "http.keepalive") h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile") certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile") keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+10 -8
View File
@@ -158,14 +158,16 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
return cc, err return cc, err
} }
sniffer := &forwarder.Sniffer{ sniffer := &forwarder.Sniffer{
Recorder: h.recorder.Recorder, Websocket: h.md.sniffingWebsocket,
RecorderOptions: h.recorder.Options, WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Certificate: h.md.certificate, Recorder: h.recorder.Recorder,
PrivateKey: h.md.privateKey, RecorderOptions: h.recorder.Options,
NegotiatedProtocol: h.md.alpn, Certificate: h.md.certificate,
CertPool: h.certPool, PrivateKey: h.md.privateKey,
MitmBypass: h.md.mitmBypass, NegotiatedProtocol: h.md.alpn,
ReadTimeout: h.md.readTimeout, CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
} }
conn = xnet.NewReadWriteConn(br, conn, conn) conn = xnet.NewReadWriteConn(br, conn, conn)
+7 -3
View File
@@ -15,10 +15,12 @@ import (
type metadata struct { type metadata struct {
readTimeout time.Duration readTimeout time.Duration
proxyProtocol int proxyProtocol int
httpKeepalive bool
sniffing bool sniffing bool
sniffingTimeout time.Duration sniffingTimeout time.Duration
httpKeepalive bool sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate certificate *x509.Certificate
privateKey crypto.PrivateKey privateKey crypto.PrivateKey
@@ -35,6 +37,8 @@ func (h *forwardHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.sniffing = mdutil.GetBool(md, "sniffing") h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout") h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
h.md.httpKeepalive = mdutil.GetBool(md, "http.keepalive") h.md.httpKeepalive = mdutil.GetBool(md, "http.keepalive")
+185 -38
View File
@@ -29,12 +29,15 @@ import (
"github.com/go-gost/core/recorder" "github.com/go-gost/core/recorder"
xbypass "github.com/go-gost/x/bypass" xbypass "github.com/go-gost/x/bypass"
ctxvalue "github.com/go-gost/x/ctx" ctxvalue "github.com/go-gost/x/ctx"
ctx_internal "github.com/go-gost/x/internal/ctx"
xio "github.com/go-gost/x/internal/io"
xnet "github.com/go-gost/x/internal/net" xnet "github.com/go-gost/x/internal/net"
xhttp "github.com/go-gost/x/internal/net/http" xhttp "github.com/go-gost/x/internal/net/http"
limiter_util "github.com/go-gost/x/internal/util/limiter" limiter_util "github.com/go-gost/x/internal/util/limiter"
"github.com/go-gost/x/internal/util/sniffing" "github.com/go-gost/x/internal/util/sniffing"
stats_util "github.com/go-gost/x/internal/util/stats" stats_util "github.com/go-gost/x/internal/util/stats"
tls_util "github.com/go-gost/x/internal/util/tls" tls_util "github.com/go-gost/x/internal/util/tls"
ws_util "github.com/go-gost/x/internal/util/ws"
rate_limiter "github.com/go-gost/x/limiter/rate" rate_limiter "github.com/go-gost/x/limiter/rate"
traffic_wrapper "github.com/go-gost/x/limiter/traffic/wrapper" traffic_wrapper "github.com/go-gost/x/limiter/traffic/wrapper"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper" stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
@@ -43,21 +46,6 @@ import (
"golang.org/x/net/http/httpguts" "golang.org/x/net/http/httpguts"
) )
type recorderObjectCtxKey struct{}
var (
ctxKeyRecorderObject = &recorderObjectCtxKey{}
)
func contextWithRecorderObject(ctx context.Context, ro *xrecorder.HandlerRecorderObject) context.Context {
return context.WithValue(ctx, ctxKeyRecorderObject, ro)
}
func recorderObjectFromContext(ctx context.Context) *xrecorder.HandlerRecorderObject {
v, _ := ctx.Value(ctxKeyRecorderObject).(*xrecorder.HandlerRecorderObject)
return v
}
func init() { func init() {
registry.HandlerRegistry().Register("http", NewHandler) registry.HandlerRegistry().Register("http", NewHandler)
} }
@@ -295,7 +283,8 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
} }
ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(clientID)) ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(clientID))
if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, network, addr) { if h.options.Bypass != nil &&
h.options.Bypass.Contains(ctx, network, addr) {
resp.StatusCode = http.StatusForbidden resp.StatusCode = http.StatusForbidden
if log.IsLevelEnabled(logger.TraceLevel) { if log.IsLevelEnabled(logger.TraceLevel) {
@@ -338,7 +327,7 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
return h.handleProxy(ctx, conn, req, ro, log) return h.handleProxy(ctx, conn, req, ro, log)
} }
ctx = contextWithRecorderObject(ctx, ro) ctx = ctx_internal.ContextWithRecorderObject(ctx, ro)
ctx = ctxvalue.ContextWithLogger(ctx, log) ctx = ctxvalue.ContextWithLogger(ctx, log)
cc, err := h.dial(ctx, "tcp", addr) cc, err := h.dial(ctx, "tcp", addr)
@@ -386,14 +375,16 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
return cc, nil return cc, nil
} }
sniffer := &sniffing.Sniffer{ sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder, Websocket: h.md.sniffingWebsocket,
RecorderOptions: h.recorder.Options, WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Certificate: h.md.certificate, Recorder: h.recorder.Recorder,
PrivateKey: h.md.privateKey, RecorderOptions: h.recorder.Options,
NegotiatedProtocol: h.md.alpn, Certificate: h.md.certificate,
CertPool: h.certPool, PrivateKey: h.md.privateKey,
MitmBypass: h.md.mitmBypass, NegotiatedProtocol: h.md.alpn,
ReadTimeout: h.md.readTimeout, CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
} }
conn = xnet.NewReadWriteConn(br, conn, conn) conn = xnet.NewReadWriteConn(br, conn, conn)
@@ -452,7 +443,7 @@ func (h *httpHandler) handleProxy(ctx context.Context, conn net.Conn, req *http.
log.Trace(string(dump)) log.Trace(string(dump))
} }
if err := h.proxyRoundTrip(ctx, conn, req, ro, &pStats, log); err != nil { if err := h.proxyRoundTrip(ctx, xio.NewReadWriter(br, conn), req, ro, &pStats, log); err != nil {
return err return err
} }
} }
@@ -537,20 +528,36 @@ func (h *httpHandler) proxyRoundTrip(ctx context.Context, rw io.ReadWriter, req
} }
ro.HTTP.StatusCode = res.StatusCode ro.HTTP.StatusCode = res.StatusCode
if h.options.Bypass != nil &&
h.options.Bypass.Contains(ctx, "tcp", host) {
res.StatusCode = http.StatusForbidden
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(res, false)
log.Trace(string(dump))
}
log.Debug("bypass: ", host)
res.Write(rw)
return xbypass.ErrBypass
}
var reqBody *xhttp.Body var reqBody *xhttp.Body
if opts := h.recorder.Options; opts != nil && opts.HTTPBody { if opts := h.recorder.Options; opts != nil && opts.HTTPBody {
if req.Body != nil { if req.Body != nil {
maxSize := opts.MaxBodySize bodySize := opts.MaxBodySize
if maxSize <= 0 { if bodySize <= 0 {
maxSize = sniffing.DefaultBodySize bodySize = sniffing.DefaultBodySize
} }
reqBody = xhttp.NewBody(req.Body, maxSize) if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody req.Body = reqBody
} }
} }
ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(clientAddr)) ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(clientAddr))
ctx = contextWithRecorderObject(ctx, ro) ctx = ctx_internal.ContextWithRecorderObject(ctx, ro)
ctx = ctxvalue.ContextWithLogger(ctx, log) ctx = ctxvalue.ContextWithLogger(ctx, log)
resp, err := h.transport.RoundTrip(req.WithContext(ctx)) resp, err := h.transport.RoundTrip(req.WithContext(ctx))
@@ -584,16 +591,19 @@ func (h *httpHandler) proxyRoundTrip(ctx context.Context, rw io.ReadWriter, req
} }
if resp.StatusCode == http.StatusSwitchingProtocols { if resp.StatusCode == http.StatusSwitchingProtocols {
return h.handleUpgradeResponse(rw, req, resp) return h.handleUpgradeResponse(ctx, rw, req, resp, ro, log)
} }
var respBody *xhttp.Body var respBody *xhttp.Body
if opts := h.recorder.Options; opts != nil && opts.HTTPBody { if opts := h.recorder.Options; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize bodySize := opts.MaxBodySize
if maxSize <= 0 { if bodySize <= 0 {
maxSize = sniffing.DefaultBodySize bodySize = sniffing.DefaultBodySize
} }
respBody = xhttp.NewBody(resp.Body, maxSize) if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody resp.Body = respBody
} }
@@ -621,7 +631,7 @@ func (h *httpHandler) dial(ctx context.Context, network, addr string) (conn net.
var buf bytes.Buffer var buf bytes.Buffer
conn, err = h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), network, addr) conn, err = h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), network, addr)
if ro := recorderObjectFromContext(ctx); ro != nil { if ro := ctx_internal.RecorderObjectFromContext(ctx); ro != nil {
ro.Route = buf.String() ro.Route = buf.String()
} }
@@ -635,7 +645,7 @@ func upgradeType(h http.Header) string {
return h.Get("Upgrade") return h.Get("Upgrade")
} }
func (h *httpHandler) handleUpgradeResponse(rw io.ReadWriter, req *http.Request, res *http.Response) error { func (h *httpHandler) handleUpgradeResponse(ctx context.Context, rw io.ReadWriter, req *http.Request, res *http.Response, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
reqUpType := upgradeType(req.Header) reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header) resUpType := upgradeType(res.Header)
if !strings.EqualFold(reqUpType, resUpType) { if !strings.EqualFold(reqUpType, resUpType) {
@@ -652,9 +662,146 @@ func (h *httpHandler) handleUpgradeResponse(rw io.ReadWriter, req *http.Request,
return fmt.Errorf("response write: %v", err) return fmt.Errorf("response write: %v", err)
} }
if reqUpType == "websocket" && h.md.sniffingWebsocket {
return h.sniffingWebsocketFrame(ctx, rw, backConn, ro, log)
}
return xnet.Transport(rw, backConn) return xnet.Transport(rw, backConn)
} }
func (h *httpHandler) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWriter, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
errc := make(chan error, 1)
sampleRate := h.md.sniffingWebsocketSampleRate
if sampleRate <= 0 {
sampleRate = sniffing.DefaultSampleRate
}
if sampleRate > sniffing.MaxSampleRate {
sampleRate = sniffing.MaxSampleRate
}
d := time.Duration(1 / sampleRate * 1e9)
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
ticker := time.NewTicker(d)
defer ticker.Stop()
buf := &bytes.Buffer{}
for {
start := time.Now()
err := h.copyWebsocketFrame(cc, rw, buf, "client", ro)
select {
case <-ticker.C:
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.recorder.Recorder); err != nil {
log.Errorf("record: %v", err)
}
default:
}
if err != nil {
errc <- err
return
}
}
}()
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
ticker := time.NewTicker(d)
defer ticker.Stop()
buf := &bytes.Buffer{}
for {
start := time.Now()
err := h.copyWebsocketFrame(rw, cc, buf, "server", ro)
select {
case <-ticker.C:
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.recorder.Recorder); err != nil {
log.Errorf("record: %v", err)
}
default:
}
if err != nil {
errc <- err
return
}
}
}()
<-errc
return nil
}
func (h *httpHandler) copyWebsocketFrame(w io.Writer, r io.Reader, buf *bytes.Buffer, from string, ro *xrecorder.HandlerRecorderObject) (err error) {
fr := ws_util.Frame{}
if _, err = fr.ReadFrom(r); err != nil {
return err
}
ws := &xrecorder.WebsocketRecorderObject{
From: from,
Fin: fr.Header.Fin,
Rsv1: fr.Header.Rsv1,
Rsv2: fr.Header.Rsv2,
Rsv3: fr.Header.Rsv3,
OpCode: int(fr.Header.OpCode),
Masked: fr.Header.Masked,
MaskKey: fr.Header.MaskKey,
Length: fr.Header.PayloadLength,
}
if opts := h.recorder.Options; opts != nil && opts.HTTPBody {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
buf.Reset()
if _, err := io.Copy(buf, io.LimitReader(fr.Data, int64(bodySize))); err != nil {
return err
}
ws.Payload = buf.Bytes()
}
ro.Websocket = ws
length := uint64(fr.Header.Length()) + uint64(fr.Header.PayloadLength)
if from == "client" {
ro.InputBytes = length
ro.OutputBytes = 0
} else {
ro.InputBytes = 0
ro.OutputBytes = length
}
fr.Data = io.MultiReader(bytes.NewReader(buf.Bytes()), fr.Data)
if _, err := fr.WriteTo(w); err != nil {
return err
}
return nil
}
func (h *httpHandler) decodeServerName(s string) (string, error) { func (h *httpHandler) decodeServerName(s string) (string, error) {
b, err := base64.RawURLEncoding.DecodeString(s) b, err := base64.RawURLEncoding.DecodeString(s)
if err != nil { if err != nil {
+6 -2
View File
@@ -30,8 +30,10 @@ type metadata struct {
observePeriod time.Duration observePeriod time.Duration
proxyAgent string proxyAgent string
sniffing bool sniffing bool
sniffingTimeout time.Duration sniffingTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate certificate *x509.Certificate
privateKey crypto.PrivateKey privateKey crypto.PrivateKey
@@ -80,6 +82,8 @@ func (h *httpHandler) parseMetadata(md mdata.Metadata) error {
h.md.sniffing = mdutil.GetBool(md, "sniffing") h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout") h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile") certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile") keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+12 -9
View File
@@ -128,9 +128,10 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han
} }
ro.Host = dstAddr.String() ro.Host = dstAddr.String()
ro.Dst = dstAddr.String()
log = log.WithFields(map[string]any{ log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", dstAddr, dstAddr.Network()), "dst": fmt.Sprintf("%s/%s", dstAddr, dstAddr.Network()),
"host": dstAddr.String(), "host": dstAddr.String(),
}) })
@@ -184,14 +185,16 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han
} }
sniffer := &sniffing.Sniffer{ sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder, Websocket: h.md.sniffingWebsocket,
RecorderOptions: h.recorder.Options, WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Certificate: h.md.certificate, Recorder: h.recorder.Recorder,
PrivateKey: h.md.privateKey, RecorderOptions: h.recorder.Options,
NegotiatedProtocol: h.md.alpn, Certificate: h.md.certificate,
CertPool: h.certPool, PrivateKey: h.md.privateKey,
MitmBypass: h.md.mitmBypass, NegotiatedProtocol: h.md.alpn,
ReadTimeout: h.md.readTimeout, CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
} }
conn = xnet.NewReadWriteConn(br, conn, conn) conn = xnet.NewReadWriteConn(br, conn, conn)
+7 -3
View File
@@ -16,9 +16,11 @@ type metadata struct {
readTimeout time.Duration readTimeout time.Duration
tproxy bool tproxy bool
sniffing bool sniffing bool
sniffingTimeout time.Duration sniffingTimeout time.Duration
sniffingFallback bool sniffingFallback bool
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate certificate *x509.Certificate
privateKey crypto.PrivateKey privateKey crypto.PrivateKey
@@ -36,6 +38,8 @@ func (h *redirectHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.sniffing = mdutil.GetBool(md, "sniffing") h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout") h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingFallback = mdutil.GetBool(md, "sniffing.fallback") h.md.sniffingFallback = mdutil.GetBool(md, "sniffing.fallback")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile") certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile") keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+10 -8
View File
@@ -166,14 +166,16 @@ func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network
return cc, nil return cc, nil
} }
sniffer := &sniffing.Sniffer{ sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder, Websocket: h.md.sniffingWebsocket,
RecorderOptions: h.recorder.Options, WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Certificate: h.md.certificate, Recorder: h.recorder.Recorder,
PrivateKey: h.md.privateKey, RecorderOptions: h.recorder.Options,
NegotiatedProtocol: h.md.alpn, Certificate: h.md.certificate,
CertPool: h.certPool, PrivateKey: h.md.privateKey,
MitmBypass: h.md.mitmBypass, NegotiatedProtocol: h.md.alpn,
ReadTimeout: h.md.readTimeout, CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
} }
conn = xnet.NewReadWriteConn(br, conn, conn) conn = xnet.NewReadWriteConn(br, conn, conn)
+7 -3
View File
@@ -9,8 +9,8 @@ import (
"github.com/go-gost/core/bypass" "github.com/go-gost/core/bypass"
mdata "github.com/go-gost/core/metadata" mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/internal/util/mux" "github.com/go-gost/x/internal/util/mux"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
) )
@@ -23,8 +23,10 @@ type metadata struct {
muxCfg *mux.Config muxCfg *mux.Config
observePeriod time.Duration observePeriod time.Duration
sniffing bool sniffing bool
sniffingTimeout time.Duration sniffingTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate certificate *x509.Certificate
privateKey crypto.PrivateKey privateKey crypto.PrivateKey
@@ -63,6 +65,8 @@ func (h *relayHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.sniffing = mdutil.GetBool(md, "sniffing") h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout") h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile") certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile") keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+10 -8
View File
@@ -143,14 +143,16 @@ func (h *sniHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.
} }
sniffer := &sniffing.Sniffer{ sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder, Websocket: h.md.sniffingWebsocket,
RecorderOptions: h.recorder.Options, WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Certificate: h.md.certificate, Recorder: h.recorder.Recorder,
PrivateKey: h.md.privateKey, RecorderOptions: h.recorder.Options,
NegotiatedProtocol: h.md.alpn, Certificate: h.md.certificate,
CertPool: h.certPool, PrivateKey: h.md.privateKey,
MitmBypass: h.md.mitmBypass, NegotiatedProtocol: h.md.alpn,
ReadTimeout: h.md.readTimeout, CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
} }
conn = xnet.NewReadWriteConn(br, conn, conn) conn = xnet.NewReadWriteConn(br, conn, conn)
switch proto { switch proto {
+6
View File
@@ -16,6 +16,9 @@ type metadata struct {
readTimeout time.Duration readTimeout time.Duration
hash string hash string
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate certificate *x509.Certificate
privateKey crypto.PrivateKey privateKey crypto.PrivateKey
alpn string alpn string
@@ -29,6 +32,9 @@ func (h *sniHandler) parseMetadata(md mdata.Metadata) (err error) {
} }
h.md.hash = mdutil.GetString(md, "hash") h.md.hash = mdutil.GetString(md, "hash")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile") certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile") keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
if certFile != "" && keyFile != "" { if certFile != "" && keyFile != "" {
+10 -8
View File
@@ -275,14 +275,16 @@ func (h *socks4Handler) handleConnect(ctx context.Context, conn net.Conn, req *g
return cc, nil return cc, nil
} }
sniffer := &sniffing.Sniffer{ sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder, Websocket: h.md.sniffingWebsocket,
RecorderOptions: h.recorder.Options, WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Certificate: h.md.certificate, Recorder: h.recorder.Recorder,
PrivateKey: h.md.privateKey, RecorderOptions: h.recorder.Options,
NegotiatedProtocol: h.md.alpn, Certificate: h.md.certificate,
CertPool: h.certPool, PrivateKey: h.md.privateKey,
MitmBypass: h.md.mitmBypass, NegotiatedProtocol: h.md.alpn,
ReadTimeout: h.md.readTimeout, CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
} }
conn = xnet.NewReadWriteConn(br, conn, conn) conn = xnet.NewReadWriteConn(br, conn, conn)
+6 -2
View File
@@ -17,8 +17,10 @@ type metadata struct {
hash string hash string
observePeriod time.Duration observePeriod time.Duration
sniffing bool sniffing bool
sniffingTimeout time.Duration sniffingTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate certificate *x509.Certificate
privateKey crypto.PrivateKey privateKey crypto.PrivateKey
@@ -37,6 +39,8 @@ func (h *socks4Handler) parseMetadata(md mdata.Metadata) (err error) {
h.md.sniffing = mdutil.GetBool(md, "sniffing") h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout") h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile") certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile") keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+10 -8
View File
@@ -103,14 +103,16 @@ func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, networ
return cc, nil return cc, nil
} }
sniffer := &sniffing.Sniffer{ sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder, Websocket: h.md.sniffingWebsocket,
RecorderOptions: h.recorder.Options, WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Certificate: h.md.certificate, Recorder: h.recorder.Recorder,
PrivateKey: h.md.privateKey, RecorderOptions: h.recorder.Options,
NegotiatedProtocol: h.md.alpn, Certificate: h.md.certificate,
CertPool: h.certPool, PrivateKey: h.md.privateKey,
MitmBypass: h.md.mitmBypass, NegotiatedProtocol: h.md.alpn,
ReadTimeout: h.md.readTimeout, CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
} }
conn = xnet.NewReadWriteConn(br, conn, conn) conn = xnet.NewReadWriteConn(br, conn, conn)
+7 -3
View File
@@ -9,8 +9,8 @@ import (
"github.com/go-gost/core/bypass" "github.com/go-gost/core/bypass"
mdata "github.com/go-gost/core/metadata" mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/internal/util/mux" "github.com/go-gost/x/internal/util/mux"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
) )
@@ -25,8 +25,10 @@ type metadata struct {
muxCfg *mux.Config muxCfg *mux.Config
observePeriod time.Duration observePeriod time.Duration
sniffing bool sniffing bool
sniffingTimeout time.Duration sniffingTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate certificate *x509.Certificate
privateKey crypto.PrivateKey privateKey crypto.PrivateKey
@@ -67,6 +69,8 @@ func (h *socks5Handler) parseMetadata(md mdata.Metadata) (err error) {
h.md.sniffing = mdutil.GetBool(md, "sniffing") h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout") h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile") certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile") keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+10 -8
View File
@@ -192,14 +192,16 @@ func (h *ssHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.H
return cc, nil return cc, nil
} }
sniffer := &sniffing.Sniffer{ sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder, Websocket: h.md.sniffingWebsocket,
RecorderOptions: h.recorder.Options, WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Certificate: h.md.certificate, Recorder: h.recorder.Recorder,
PrivateKey: h.md.privateKey, RecorderOptions: h.recorder.Options,
NegotiatedProtocol: h.md.alpn, Certificate: h.md.certificate,
CertPool: h.certPool, PrivateKey: h.md.privateKey,
MitmBypass: h.md.mitmBypass, NegotiatedProtocol: h.md.alpn,
ReadTimeout: h.md.readTimeout, CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
} }
conn = xnet.NewReadWriteConn(br, conn, conn) conn = xnet.NewReadWriteConn(br, conn, conn)
+6 -2
View File
@@ -17,8 +17,10 @@ type metadata struct {
hash string hash string
readTimeout time.Duration readTimeout time.Duration
sniffing bool sniffing bool
sniffingTimeout time.Duration sniffingTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate certificate *x509.Certificate
privateKey crypto.PrivateKey privateKey crypto.PrivateKey
@@ -37,6 +39,8 @@ func (h *ssHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.sniffing = mdutil.GetBool(md, "sniffing") h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout") h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile") certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile") keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+10 -8
View File
@@ -180,14 +180,16 @@ func (h *forwardHandler) handleDirectForward(ctx context.Context, conn *sshd_uti
return cc, nil return cc, nil
} }
sniffer := &sniffing.Sniffer{ sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder, Websocket: h.md.sniffingWebsocket,
RecorderOptions: h.recorder.Options, WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Certificate: h.md.certificate, Recorder: h.recorder.Recorder,
PrivateKey: h.md.privateKey, RecorderOptions: h.recorder.Options,
NegotiatedProtocol: h.md.alpn, Certificate: h.md.certificate,
CertPool: h.certPool, PrivateKey: h.md.privateKey,
MitmBypass: h.md.mitmBypass, NegotiatedProtocol: h.md.alpn,
ReadTimeout: h.md.readTimeout, CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
} }
switch proto { switch proto {
+6 -2
View File
@@ -15,8 +15,10 @@ import (
type metadata struct { type metadata struct {
readTimeout time.Duration readTimeout time.Duration
sniffing bool sniffing bool
sniffingTimeout time.Duration sniffingTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate certificate *x509.Certificate
privateKey crypto.PrivateKey privateKey crypto.PrivateKey
@@ -32,6 +34,8 @@ func (h *forwardHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.sniffing = mdutil.GetBool(md, "sniffing") h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout") h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile") certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile") keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+222 -89
View File
@@ -2,6 +2,7 @@ package tunnel
import ( import (
"bufio" "bufio"
"bytes"
"context" "context"
"errors" "errors"
"fmt" "fmt"
@@ -24,9 +25,13 @@ import (
"github.com/go-gost/relay" "github.com/go-gost/relay"
admission "github.com/go-gost/x/admission/wrapper" admission "github.com/go-gost/x/admission/wrapper"
ctxvalue "github.com/go-gost/x/ctx" ctxvalue "github.com/go-gost/x/ctx"
ctx_internal "github.com/go-gost/x/internal/ctx"
xio "github.com/go-gost/x/internal/io"
xnet "github.com/go-gost/x/internal/net" xnet "github.com/go-gost/x/internal/net"
xhttp "github.com/go-gost/x/internal/net/http" xhttp "github.com/go-gost/x/internal/net/http"
"github.com/go-gost/x/internal/net/proxyproto" "github.com/go-gost/x/internal/net/proxyproto"
"github.com/go-gost/x/internal/util/sniffing"
ws_util "github.com/go-gost/x/internal/util/ws"
climiter "github.com/go-gost/x/limiter/conn/wrapper" climiter "github.com/go-gost/x/limiter/conn/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper" metrics "github.com/go-gost/x/metrics/wrapper"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper" stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
@@ -34,25 +39,6 @@ import (
"golang.org/x/net/http/httpguts" "golang.org/x/net/http/httpguts"
) )
const (
defaultBodySize = 1024 * 1024 // 1MB
)
type recorderObjectCtxKey struct{}
var (
ctxKeyRecorderObject = &recorderObjectCtxKey{}
)
func contextWithRecorderObject(ctx context.Context, ro *xrecorder.HandlerRecorderObject) context.Context {
return context.WithValue(ctx, ctxKeyRecorderObject, ro)
}
func recorderObjectFromContext(ctx context.Context) *xrecorder.HandlerRecorderObject {
v, _ := ctx.Value(ctxKeyRecorderObject).(*xrecorder.HandlerRecorderObject)
return v
}
type entrypoint struct { type entrypoint struct {
node string node string
service string service string
@@ -62,6 +48,9 @@ type entrypoint struct {
log logger.Logger log logger.Logger
recorder recorder.RecorderObject recorder recorder.RecorderObject
transport http.RoundTripper transport http.RoundTripper
sniffingWebsocket bool
websocketSampleRate float64
} }
func (ep *entrypoint) Handle(ctx context.Context, conn net.Conn) (err error) { func (ep *entrypoint) Handle(ctx context.Context, conn net.Conn) (err error) {
@@ -119,6 +108,74 @@ func (ep *entrypoint) Handle(ctx context.Context, conn net.Conn) (err error) {
return ep.handleHTTP(ctx, conn, ro, log) return ep.handleHTTP(ctx, conn, ro, log)
} }
func (h *entrypoint) dial(ctx context.Context, network, addr string) (conn net.Conn, err error) {
var tunnelID relay.TunnelID
if h.ingress != nil {
if rule := h.ingress.GetRule(ctx, addr); rule != nil {
tunnelID = parseTunnelID(rule.Endpoint)
}
}
log := ctxvalue.LoggerFromContext(ctx)
if log == nil {
log = h.log
}
log.Debugf("dial: new connection to host %s", addr)
if tunnelID.IsZero() {
return nil, fmt.Errorf("%w %s", ErrTunnelRoute, addr)
}
if ro := ctx_internal.RecorderObjectFromContext(ctx); ro != nil {
ro.ClientID = tunnelID.String()
}
if tunnelID.IsPrivate() {
return nil, fmt.Errorf("%w: tunnel %s is private for host %s", ErrPrivateTunnel, tunnelID, addr)
}
log = log.WithFields(map[string]any{
"tunnel": tunnelID.String(),
})
d := &Dialer{
node: h.node,
pool: h.pool,
sd: h.sd,
retry: 3,
timeout: 15 * time.Second,
log: log,
}
conn, node, cid, err := d.Dial(ctx, "tcp", tunnelID.String())
if err != nil {
return
}
log.Debugf("dial: connected to host %s, tunnel: %s, connector: %s", addr, tunnelID, cid)
if node == h.node {
clientAddr := ctxvalue.ClientAddrFromContext(ctx)
var features []relay.Feature
af := &relay.AddrFeature{}
af.ParseFrom(string(clientAddr))
features = append(features, af) // src address
af = &relay.AddrFeature{}
af.ParseFrom(addr)
features = append(features, af) // dst address
if _, err = (&relay.Response{
Version: relay.Version1,
Status: relay.StatusOK,
Features: features,
}).WriteTo(conn); err != nil {
conn.Close()
return nil, err
}
}
return conn, nil
}
func (ep *entrypoint) handleHTTP(ctx context.Context, conn net.Conn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) { func (ep *entrypoint) handleHTTP(ctx context.Context, conn net.Conn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) {
pStats := stats.Stats{} pStats := stats.Stats{}
conn = stats_wrapper.WrapConn(conn, &pStats) conn = stats_wrapper.WrapConn(conn, &pStats)
@@ -148,7 +205,7 @@ func (ep *entrypoint) handleHTTP(ctx context.Context, conn net.Conn, ro *xrecord
ro.Time = time.Time{} ro.Time = time.Time{}
if err := ep.httpRoundTrip(ctx, conn, req, ro, &pStats, log); err != nil { if err := ep.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), req, ro, &pStats, log); err != nil {
log.Error(err) log.Error(err)
return err return err
} }
@@ -169,7 +226,7 @@ func (ep *entrypoint) handleHTTP(ctx context.Context, conn net.Conn, ro *xrecord
log.Trace(string(dump)) log.Trace(string(dump))
} }
if err := ep.httpRoundTrip(ctx, conn, req, ro, &pStats, log); err != nil { if err := ep.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), req, ro, &pStats, log); err != nil {
return err return err
} }
} }
@@ -250,17 +307,20 @@ func (ep *entrypoint) httpRoundTrip(ctx context.Context, rw io.ReadWriter, req *
var reqBody *xhttp.Body var reqBody *xhttp.Body
if opts := ep.recorder.Options; opts != nil && opts.HTTPBody { if opts := ep.recorder.Options; opts != nil && opts.HTTPBody {
if req.Body != nil { if req.Body != nil {
maxSize := opts.MaxBodySize bodySize := opts.MaxBodySize
if maxSize <= 0 { if bodySize <= 0 {
maxSize = defaultBodySize bodySize = sniffing.DefaultBodySize
} }
reqBody = xhttp.NewBody(req.Body, maxSize) if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody req.Body = reqBody
} }
} }
ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(clientAddr)) ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(clientAddr))
ctx = contextWithRecorderObject(ctx, ro) ctx = ctx_internal.ContextWithRecorderObject(ctx, ro)
ctx = ctxvalue.ContextWithLogger(ctx, log) ctx = ctxvalue.ContextWithLogger(ctx, log)
resp, err := ep.transport.RoundTrip(req.WithContext(ctx)) resp, err := ep.transport.RoundTrip(req.WithContext(ctx))
@@ -289,16 +349,19 @@ func (ep *entrypoint) httpRoundTrip(ctx context.Context, rw io.ReadWriter, req *
} }
if resp.StatusCode == http.StatusSwitchingProtocols { if resp.StatusCode == http.StatusSwitchingProtocols {
return ep.handleUpgradeResponse(rw, req, resp) return ep.handleUpgradeResponse(ctx, rw, req, resp, ro, log)
} }
var respBody *xhttp.Body var respBody *xhttp.Body
if opts := ep.recorder.Options; opts != nil && opts.HTTPBody { if opts := ep.recorder.Options; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize bodySize := opts.MaxBodySize
if maxSize <= 0 { if bodySize <= 0 {
maxSize = defaultBodySize bodySize = sniffing.DefaultBodySize
} }
respBody = xhttp.NewBody(resp.Body, maxSize) if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody resp.Body = respBody
} }
@@ -321,7 +384,7 @@ func upgradeType(h http.Header) string {
return h.Get("Upgrade") return h.Get("Upgrade")
} }
func (ep *entrypoint) handleUpgradeResponse(rw io.ReadWriter, req *http.Request, res *http.Response) error { func (ep *entrypoint) handleUpgradeResponse(ctx context.Context, rw io.ReadWriter, req *http.Request, res *http.Response, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
reqUpType := upgradeType(req.Header) reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header) resUpType := upgradeType(res.Header)
if !strings.EqualFold(reqUpType, resUpType) { if !strings.EqualFold(reqUpType, resUpType) {
@@ -332,81 +395,151 @@ func (ep *entrypoint) handleUpgradeResponse(rw io.ReadWriter, req *http.Request,
if !ok { if !ok {
return fmt.Errorf("internal error: 101 switching protocols response with non-writable body") return fmt.Errorf("internal error: 101 switching protocols response with non-writable body")
} }
defer backConn.Close()
res.Body = nil res.Body = nil
if err := res.Write(rw); err != nil { if err := res.Write(rw); err != nil {
return fmt.Errorf("response write: %v", err) return fmt.Errorf("response write: %v", err)
} }
if reqUpType == "websocket" && ep.sniffingWebsocket {
return ep.sniffingWebsocketFrame(ctx, rw, backConn, ro, log)
}
return xnet.Transport(rw, backConn) return xnet.Transport(rw, backConn)
} }
func (h *entrypoint) dial(ctx context.Context, network, addr string) (conn net.Conn, err error) { func (ep *entrypoint) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWriter, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
var tunnelID relay.TunnelID errc := make(chan error, 1)
if h.ingress != nil {
if rule := h.ingress.GetRule(ctx, addr); rule != nil { sampleRate := ep.websocketSampleRate
tunnelID = parseTunnelID(rule.Endpoint) if sampleRate <= 0 {
sampleRate = sniffing.DefaultSampleRate
}
if sampleRate > sniffing.MaxSampleRate {
sampleRate = sniffing.MaxSampleRate
}
d := time.Duration(1 / sampleRate * 1e9)
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
ticker := time.NewTicker(d)
defer ticker.Stop()
buf := &bytes.Buffer{}
for {
start := time.Now()
err := ep.copyWebsocketFrame(cc, rw, buf, "client", ro)
select {
case <-ticker.C:
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, ep.recorder.Recorder); err != nil {
log.Errorf("record: %v", err)
}
default:
}
if err != nil {
errc <- err
return
}
} }
} }()
log := ctxvalue.LoggerFromContext(ctx) go func() {
if log == nil { ro2 := &xrecorder.HandlerRecorderObject{}
log = h.log *ro2 = *ro
} ro = ro2
log.Debugf("dial: new connection to host %s", addr)
if tunnelID.IsZero() { ticker := time.NewTicker(d)
return nil, fmt.Errorf("%w %s", ErrTunnelRoute, addr) defer ticker.Stop()
}
if ro := recorderObjectFromContext(ctx); ro != nil { buf := &bytes.Buffer{}
ro.ClientID = tunnelID.String() for {
} start := time.Now()
if tunnelID.IsPrivate() { err := ep.copyWebsocketFrame(rw, cc, buf, "server", ro)
return nil, fmt.Errorf("%w: tunnel %s is private for host %s", ErrPrivateTunnel, tunnelID, addr) select {
} case <-ticker.C:
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, ep.recorder.Recorder); err != nil {
log.Errorf("record: %v", err)
}
default:
}
log = log.WithFields(map[string]any{ if err != nil {
"tunnel": tunnelID.String(), errc <- err
}) return
}
d := &Dialer{
node: h.node,
pool: h.pool,
sd: h.sd,
retry: 3,
timeout: 15 * time.Second,
log: log,
}
conn, node, cid, err := d.Dial(ctx, "tcp", tunnelID.String())
if err != nil {
return
}
log.Debugf("dial: connected to host %s, tunnel: %s, connector: %s", addr, tunnelID, cid)
if node == h.node {
clientAddr := ctxvalue.ClientAddrFromContext(ctx)
var features []relay.Feature
af := &relay.AddrFeature{}
af.ParseFrom(string(clientAddr))
features = append(features, af) // src address
af = &relay.AddrFeature{}
af.ParseFrom(addr)
features = append(features, af) // dst address
if _, err = (&relay.Response{
Version: relay.Version1,
Status: relay.StatusOK,
Features: features,
}).WriteTo(conn); err != nil {
conn.Close()
return nil, err
} }
}()
<-errc
return nil
}
func (ep *entrypoint) copyWebsocketFrame(w io.Writer, r io.Reader, buf *bytes.Buffer, from string, ro *xrecorder.HandlerRecorderObject) (err error) {
fr := ws_util.Frame{}
if _, err = fr.ReadFrom(r); err != nil {
return err
} }
return conn, nil ws := &xrecorder.WebsocketRecorderObject{
From: from,
Fin: fr.Header.Fin,
Rsv1: fr.Header.Rsv1,
Rsv2: fr.Header.Rsv2,
Rsv3: fr.Header.Rsv3,
OpCode: int(fr.Header.OpCode),
Masked: fr.Header.Masked,
MaskKey: fr.Header.MaskKey,
Length: fr.Header.PayloadLength,
}
if opts := ep.recorder.Options; opts != nil && opts.HTTPBody {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
buf.Reset()
if _, err := io.Copy(buf, io.LimitReader(fr.Data, int64(bodySize))); err != nil {
return err
}
ws.Payload = buf.Bytes()
}
ro.Websocket = ws
length := uint64(fr.Header.Length()) + uint64(fr.Header.PayloadLength)
if from == "client" {
ro.InputBytes = length
ro.OutputBytes = 0
} else {
ro.InputBytes = 0
ro.OutputBytes = length
}
fr.Data = io.MultiReader(bytes.NewReader(buf.Bytes()), fr.Data)
if _, err := fr.WriteTo(w); err != nil {
return err
}
return nil
} }
func (ep *entrypoint) handleConnect(ctx context.Context, conn net.Conn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) { func (ep *entrypoint) handleConnect(ctx context.Context, conn net.Conn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) {
+2
View File
@@ -90,6 +90,8 @@ func (h *tunnelHandler) Init(md md.Metadata) (err error) {
log: h.log.WithFields(map[string]any{ log: h.log.WithFields(map[string]any{
"kind": "entrypoint", "kind": "entrypoint",
}), }),
sniffingWebsocket: h.md.sniffingWebsocket,
websocketSampleRate: h.md.sniffingWebsocketSampleRate,
} }
h.ep.transport = &http.Transport{ h.ep.transport = &http.Transport{
DialContext: h.ep.dial, DialContext: h.ep.dial,
+10 -5
View File
@@ -22,11 +22,13 @@ const (
type metadata struct { type metadata struct {
readTimeout time.Duration readTimeout time.Duration
entryPoint string entryPoint string
entryPointID relay.TunnelID entryPointID relay.TunnelID
entryPointProxyProtocol int entryPointProxyProtocol int
entryPointKeepalive bool entryPointKeepalive bool
entryPointReadTimeout time.Duration entryPointReadTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
directTunnel bool directTunnel bool
tunnelTTL time.Duration tunnelTTL time.Duration
@@ -53,6 +55,9 @@ func (h *tunnelHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.entryPointReadTimeout = 15 * time.Second h.md.entryPointReadTimeout = 15 * time.Second
} }
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
h.md.tunnelTTL = mdutil.GetDuration(md, "tunnel.ttl") h.md.tunnelTTL = mdutil.GetDuration(md, "tunnel.ttl")
if h.md.tunnelTTL <= 0 { if h.md.tunnelTTL <= 0 {
h.md.tunnelTTL = defaultTTL h.md.tunnelTTL = defaultTTL
+22
View File
@@ -0,0 +1,22 @@
package ctx
import (
"context"
xrecorder "github.com/go-gost/x/recorder"
)
type recorderObjectCtxKey struct{}
var (
ctxKeyRecorderObject = &recorderObjectCtxKey{}
)
func ContextWithRecorderObject(ctx context.Context, ro *xrecorder.HandlerRecorderObject) context.Context {
return context.WithValue(ctx, ctxKeyRecorderObject, ro)
}
func RecorderObjectFromContext(ctx context.Context) *xrecorder.HandlerRecorderObject {
v, _ := ctx.Value(ctxKeyRecorderObject).(*xrecorder.HandlerRecorderObject)
return v
}
+198 -26
View File
@@ -32,16 +32,13 @@ import (
xhttp "github.com/go-gost/x/internal/net/http" xhttp "github.com/go-gost/x/internal/net/http"
"github.com/go-gost/x/internal/util/sniffing" "github.com/go-gost/x/internal/util/sniffing"
tls_util "github.com/go-gost/x/internal/util/tls" tls_util "github.com/go-gost/x/internal/util/tls"
ws_util "github.com/go-gost/x/internal/util/ws"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper" stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
xrecorder "github.com/go-gost/x/recorder" xrecorder "github.com/go-gost/x/recorder"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
const (
// Default max body size to record.
DefaultBodySize = 1024 * 1024 // 1MB
)
var ( var (
DefaultCertPool = tls_util.NewMemoryCertPool() DefaultCertPool = tls_util.NewMemoryCertPool()
) )
@@ -102,6 +99,9 @@ func WithLog(log logger.Logger) HandleOption {
} }
type Sniffer struct { type Sniffer struct {
Websocket bool
WebsocketSampleRate float64
Recorder recorder.Recorder Recorder recorder.Recorder
RecorderOptions *recorder.Options RecorderOptions *recorder.Options
@@ -177,7 +177,7 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
ro.Time = time.Time{} ro.Time = time.Time{}
shouldClose, err := h.httpRoundTrip(ctx, conn, cc, node, req, &pStats, &ho) shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, node, req, &pStats, &ho)
if err != nil || shouldClose { if err != nil || shouldClose {
return err return err
} }
@@ -198,7 +198,7 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
log.Trace(string(dump)) log.Trace(string(dump))
} }
if shouldClose, err := h.httpRoundTrip(ctx, conn, cc, node, req, &pStats, &ho); err != nil || shouldClose { if shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, node, req, &pStats, &ho); err != nil || shouldClose {
return err return err
} }
} }
@@ -441,11 +441,14 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, node
var reqBody *xhttp.Body var reqBody *xhttp.Body
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody { if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
if req.Body != nil { if req.Body != nil {
maxSize := opts.MaxBodySize bodySize := opts.MaxBodySize
if maxSize <= 0 { if bodySize <= 0 {
maxSize = DefaultBodySize bodySize = sniffing.DefaultBodySize
} }
reqBody = xhttp.NewBody(req.Body, maxSize) if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody req.Body = reqBody
} }
} }
@@ -479,6 +482,11 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, node
log.Trace(string(dump)) log.Trace(string(dump))
} }
if resp.StatusCode == http.StatusSwitchingProtocols {
h.handleUpgradeResponse(ctx, rw, cc, req, resp, ro, log)
return
}
// HTTP/1.0 // HTTP/1.0
if req.ProtoMajor == 1 && req.ProtoMinor == 0 { if req.ProtoMajor == 1 && req.ProtoMinor == 0 {
if !resp.Close { if !resp.Close {
@@ -495,11 +503,14 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, node
var respBody *xhttp.Body var respBody *xhttp.Body
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody { if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize bodySize := opts.MaxBodySize
if maxSize <= 0 { if bodySize <= 0 {
maxSize = DefaultBodySize bodySize = sniffing.DefaultBodySize
} }
respBody = xhttp.NewBody(resp.Body, maxSize) if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody resp.Body = respBody
} }
@@ -513,11 +524,166 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, node
ro.HTTP.Response.ContentLength = respBody.Length() ro.HTTP.Response.ContentLength = respBody.Length()
} }
if resp.StatusCode == http.StatusSwitchingProtocols { return resp.Close, nil
xnet.Transport(rw, cc) }
func upgradeType(h http.Header) string {
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
return ""
}
return h.Get("Upgrade")
}
func (h *Sniffer) handleUpgradeResponse(ctx context.Context, rw io.ReadWriter, cc io.ReadWriter, req *http.Request, res *http.Response, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header)
if !strings.EqualFold(reqUpType, resUpType) {
return fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)
} }
return resp.Close, nil res.Body = nil
if err := res.Write(rw); err != nil {
return fmt.Errorf("response write: %v", err)
}
if reqUpType == "websocket" && h.Websocket {
return h.sniffingWebsocketFrame(ctx, rw, cc, ro, log)
}
return xnet.Transport(rw, cc)
}
func (h *Sniffer) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWriter, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
errc := make(chan error, 1)
sampleRate := h.WebsocketSampleRate
if sampleRate <= 0 {
sampleRate = sniffing.DefaultSampleRate
}
if sampleRate > sniffing.MaxSampleRate {
sampleRate = sniffing.MaxSampleRate
}
d := time.Duration(1 / sampleRate * 1e9)
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
ticker := time.NewTicker(d)
defer ticker.Stop()
buf := &bytes.Buffer{}
for {
start := time.Now()
err := h.copyWebsocketFrame(cc, rw, buf, "client", ro)
select {
case <-ticker.C:
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
default:
}
if err != nil {
errc <- err
return
}
}
}()
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
ticker := time.NewTicker(d)
defer ticker.Stop()
buf := &bytes.Buffer{}
for {
start := time.Now()
err := h.copyWebsocketFrame(rw, cc, buf, "server", ro)
select {
case <-ticker.C:
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
default:
}
if err != nil {
errc <- err
return
}
}
}()
<-errc
return nil
}
func (h *Sniffer) copyWebsocketFrame(w io.Writer, r io.Reader, buf *bytes.Buffer, from string, ro *xrecorder.HandlerRecorderObject) (err error) {
fr := ws_util.Frame{}
if _, err = fr.ReadFrom(r); err != nil {
return err
}
ws := &xrecorder.WebsocketRecorderObject{
From: from,
Fin: fr.Header.Fin,
Rsv1: fr.Header.Rsv1,
Rsv2: fr.Header.Rsv2,
Rsv3: fr.Header.Rsv3,
OpCode: int(fr.Header.OpCode),
Masked: fr.Header.Masked,
MaskKey: fr.Header.MaskKey,
Length: fr.Header.PayloadLength,
}
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
buf.Reset()
if _, err := io.Copy(buf, io.LimitReader(fr.Data, int64(bodySize))); err != nil {
return err
}
ws.Payload = buf.Bytes()
}
ro.Websocket = ws
length := uint64(fr.Header.Length()) + uint64(fr.Header.PayloadLength)
if from == "client" {
ro.InputBytes = length
ro.OutputBytes = 0
} else {
ro.InputBytes = 0
ro.OutputBytes = length
}
fr.Data = io.MultiReader(bytes.NewReader(buf.Bytes()), fr.Data)
if _, err := fr.WriteTo(w); err != nil {
return err
}
return nil
} }
func (h *Sniffer) rewriteBody(resp *http.Response, rewrites ...chain.HTTPBodyRewriteSettings) error { func (h *Sniffer) rewriteBody(resp *http.Response, rewrites ...chain.HTTPBodyRewriteSettings) error {
@@ -914,11 +1080,14 @@ func (h *h2Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var reqBody *xhttp.Body var reqBody *xhttp.Body
if opts := h.recorderOptions; opts != nil && opts.HTTPBody { if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
if req.Body != nil { if req.Body != nil {
maxSize := opts.MaxBodySize bodySize := opts.MaxBodySize
if maxSize <= 0 { if bodySize <= 0 {
maxSize = DefaultBodySize bodySize = sniffing.DefaultBodySize
} }
reqBody = xhttp.NewBody(req.Body, maxSize) if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody req.Body = reqBody
} }
} }
@@ -949,11 +1118,14 @@ func (h *h2Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var respBody *xhttp.Body var respBody *xhttp.Body
if opts := h.recorderOptions; opts != nil && opts.HTTPBody { if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize bodySize := opts.MaxBodySize
if maxSize <= 0 { if bodySize <= 0 {
maxSize = DefaultBodySize bodySize = sniffing.DefaultBodySize
} }
respBody = xhttp.NewBody(resp.Body, maxSize) if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody resp.Body = respBody
} }
+209 -25
View File
@@ -28,14 +28,22 @@ import (
xnet "github.com/go-gost/x/internal/net" xnet "github.com/go-gost/x/internal/net"
xhttp "github.com/go-gost/x/internal/net/http" xhttp "github.com/go-gost/x/internal/net/http"
tls_util "github.com/go-gost/x/internal/util/tls" tls_util "github.com/go-gost/x/internal/util/tls"
ws_util "github.com/go-gost/x/internal/util/ws"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper" stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
xrecorder "github.com/go-gost/x/recorder" xrecorder "github.com/go-gost/x/recorder"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
const ( const (
// Default max body size to record. // DefaultBodySize is the default HTTP body or websocket frame size to record.
DefaultBodySize = 1024 * 1024 // 1MB DefaultBodySize = 64 * 1024 // 64KB
// MaxBodySize is the maximum HTTP body or websocket frame size to record.
MaxBodySize = 1024 * 1024 // 1MB
// DeafultSampleRate is the default websocket sample rate (samples per second).
DefaultSampleRate = 10.0
// MaxSampleRate is the maximum websocket sample rate (samples per second).
MaxSampleRate = 100.0
) )
var ( var (
@@ -84,6 +92,9 @@ func WithLog(log logger.Logger) HandleOption {
} }
type Sniffer struct { type Sniffer struct {
Websocket bool
WebsocketSampleRate float64
Recorder recorder.Recorder Recorder recorder.Recorder
RecorderOptions *recorder.Options RecorderOptions *recorder.Options
@@ -176,7 +187,7 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
ro.Time = time.Time{} ro.Time = time.Time{}
shouldClose, err := h.httpRoundTrip(ctx, conn, cc, req, ro, &pStats, log) shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, req, ro, &pStats, log)
if err != nil || shouldClose { if err != nil || shouldClose {
return err return err
} }
@@ -197,7 +208,7 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
log.Trace(string(dump)) log.Trace(string(dump))
} }
if shouldClose, err := h.httpRoundTrip(ctx, conn, cc, req, ro, &pStats, log); err != nil || shouldClose { if shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, req, ro, &pStats, log); err != nil || shouldClose {
return err return err
} }
} }
@@ -302,11 +313,14 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *
var reqBody *xhttp.Body var reqBody *xhttp.Body
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody { if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
if req.Body != nil { if req.Body != nil {
maxSize := opts.MaxBodySize bodySize := opts.MaxBodySize
if maxSize <= 0 { if bodySize <= 0 {
maxSize = DefaultBodySize bodySize = DefaultBodySize
} }
reqBody = xhttp.NewBody(req.Body, maxSize) if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody req.Body = reqBody
} }
} }
@@ -323,7 +337,7 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *
xio.SetReadDeadline(cc, time.Now().Add(h.ReadTimeout)) xio.SetReadDeadline(cc, time.Now().Add(h.ReadTimeout))
resp, err := http.ReadResponse(bufio.NewReader(cc), req) resp, err := http.ReadResponse(bufio.NewReader(cc), req)
if err != nil { if err != nil {
log.Errorf("read response: %v", err) err = fmt.Errorf("read response: %w", err)
return return
} }
defer resp.Body.Close() defer resp.Body.Close()
@@ -338,6 +352,11 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *
log.Trace(string(dump)) log.Trace(string(dump))
} }
if resp.StatusCode == http.StatusSwitchingProtocols {
h.handleUpgradeResponse(ctx, rw, cc, req, resp, ro, log)
return
}
// HTTP/1.0 // HTTP/1.0
if req.ProtoMajor == 1 && req.ProtoMinor == 0 { if req.ProtoMajor == 1 && req.ProtoMinor == 0 {
if !resp.Close { if !resp.Close {
@@ -349,16 +368,19 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *
var respBody *xhttp.Body var respBody *xhttp.Body
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody { if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize bodySize := opts.MaxBodySize
if maxSize <= 0 { if bodySize <= 0 {
maxSize = DefaultBodySize bodySize = DefaultBodySize
} }
respBody = xhttp.NewBody(resp.Body, maxSize) if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody resp.Body = respBody
} }
if err = resp.Write(rw); err != nil { if err = resp.Write(rw); err != nil {
log.Errorf("write response: %v", err) err = fmt.Errorf("write response: %w", err)
return return
} }
@@ -367,11 +389,166 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *
ro.HTTP.Response.ContentLength = respBody.Length() ro.HTTP.Response.ContentLength = respBody.Length()
} }
if resp.StatusCode == http.StatusSwitchingProtocols { return resp.Close, nil
xnet.Transport(rw, cc) }
func upgradeType(h http.Header) string {
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
return ""
}
return h.Get("Upgrade")
}
func (h *Sniffer) handleUpgradeResponse(ctx context.Context, rw io.ReadWriter, cc io.ReadWriter, req *http.Request, res *http.Response, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header)
if !strings.EqualFold(reqUpType, resUpType) {
return fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)
} }
return resp.Close, nil res.Body = nil
if err := res.Write(rw); err != nil {
return fmt.Errorf("response write: %v", err)
}
if reqUpType == "websocket" && h.Websocket {
return h.sniffingWebsocketFrame(ctx, rw, cc, ro, log)
}
return xnet.Transport(rw, cc)
}
func (h *Sniffer) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWriter, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
errc := make(chan error, 1)
sampleRate := h.WebsocketSampleRate
if sampleRate <= 0 {
sampleRate = DefaultSampleRate
}
if sampleRate > MaxSampleRate {
sampleRate = MaxSampleRate
}
d := time.Duration(1 / sampleRate * 1e9)
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
ticker := time.NewTicker(d)
defer ticker.Stop()
buf := &bytes.Buffer{}
for {
start := time.Now()
err := h.copyWebsocketFrame(cc, rw, buf, "client", ro)
select {
case <-ticker.C:
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
default:
}
if err != nil {
errc <- err
return
}
}
}()
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
ticker := time.NewTicker(d)
defer ticker.Stop()
buf := &bytes.Buffer{}
for {
start := time.Now()
err := h.copyWebsocketFrame(rw, cc, buf, "server", ro)
select {
case <-ticker.C:
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
default:
}
if err != nil {
errc <- err
return
}
}
}()
<-errc
return nil
}
func (h *Sniffer) copyWebsocketFrame(w io.Writer, r io.Reader, buf *bytes.Buffer, from string, ro *xrecorder.HandlerRecorderObject) (err error) {
fr := ws_util.Frame{}
if _, err = fr.ReadFrom(r); err != nil {
return err
}
ws := &xrecorder.WebsocketRecorderObject{
From: from,
Fin: fr.Header.Fin,
Rsv1: fr.Header.Rsv1,
Rsv2: fr.Header.Rsv2,
Rsv3: fr.Header.Rsv3,
OpCode: int(fr.Header.OpCode),
Masked: fr.Header.Masked,
MaskKey: fr.Header.MaskKey,
Length: fr.Header.PayloadLength,
}
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
buf.Reset()
if _, err := io.Copy(buf, io.LimitReader(fr.Data, int64(bodySize))); err != nil {
return err
}
ws.Payload = buf.Bytes()
}
ro.Websocket = ws
length := uint64(fr.Header.Length()) + uint64(fr.Header.PayloadLength)
if from == "client" {
ro.InputBytes = length
ro.OutputBytes = 0
} else {
ro.InputBytes = 0
ro.OutputBytes = length
}
fr.Data = io.MultiReader(bytes.NewReader(buf.Bytes()), fr.Data)
if _, err := fr.WriteTo(w); err != nil {
return err
}
return nil
} }
func (h *Sniffer) HandleTLS(ctx context.Context, conn net.Conn, opts ...HandleOption) error { func (h *Sniffer) HandleTLS(ctx context.Context, conn net.Conn, opts ...HandleOption) error {
@@ -644,11 +821,15 @@ func (h *h2Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var reqBody *xhttp.Body var reqBody *xhttp.Body
if opts := h.recorderOptions; opts != nil && opts.HTTPBody { if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
if req.Body != nil { if req.Body != nil {
maxSize := opts.MaxBodySize bodySize := opts.MaxBodySize
if maxSize <= 0 { if bodySize <= 0 {
maxSize = DefaultBodySize bodySize = DefaultBodySize
} }
reqBody = xhttp.NewBody(req.Body, maxSize) if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody req.Body = reqBody
} }
} }
@@ -679,11 +860,14 @@ func (h *h2Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var respBody *xhttp.Body var respBody *xhttp.Body
if opts := h.recorderOptions; opts != nil && opts.HTTPBody { if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize bodySize := opts.MaxBodySize
if maxSize <= 0 { if bodySize <= 0 {
maxSize = DefaultBodySize bodySize = DefaultBodySize
} }
respBody = xhttp.NewBody(resp.Body, maxSize) if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody resp.Body = respBody
} }
+196
View File
@@ -0,0 +1,196 @@
package ws
import (
"encoding/binary"
"fmt"
"io"
"math"
)
// OpCode represents a WebSocket opcode.
type OpCode int
// https://tools.ietf.org/html/rfc6455#section-11.8.
const (
OpContinuation OpCode = iota
OpText
OpBinary
// 3 - 7 are reserved for further non-control frames.
_
_
_
_
_
OpClose
OpPing
OpPong
// 11-16 are reserved for further control frames.
)
// FrameHeader represents a WebSocket frame header.
// See https://tools.ietf.org/html/rfc6455#section-5.2.
type FrameHeader struct {
Fin bool
Rsv1 bool
Rsv2 bool
Rsv3 bool
OpCode OpCode
PayloadLength int64
Masked bool
MaskKey uint32
}
// ReadFrom reads a header from the reader.
// See https://tools.ietf.org/html/rfc6455#section-5.2.
func (h *FrameHeader) ReadFrom(r io.Reader) (n int64, err error) {
var buf [8]byte
// First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
nn, err := io.ReadFull(r, buf[:2])
n += int64(n)
if err != nil {
return
}
b := buf[0]
h.Fin = b&(1<<7) != 0
h.Rsv1 = b&(1<<6) != 0
h.Rsv2 = b&(1<<5) != 0
h.Rsv3 = b&(1<<4) != 0
h.OpCode = OpCode(b & 0xf)
b = buf[1]
h.Masked = b&(1<<7) != 0
payloadLength := b &^ (1 << 7)
switch {
case payloadLength < 126:
h.PayloadLength = int64(payloadLength)
case payloadLength == 126:
nn, err = io.ReadFull(r, buf[:2])
h.PayloadLength = int64(binary.BigEndian.Uint16(buf[:]))
case payloadLength == 127:
nn, err = io.ReadFull(r, buf[:])
h.PayloadLength = int64(binary.BigEndian.Uint64(buf[:]))
}
n += int64(nn)
if err != nil {
return
}
if h.PayloadLength < 0 {
err = fmt.Errorf("received negative payload length: %v", h.PayloadLength)
return
}
if h.Masked {
nn, err = io.ReadFull(r, buf[:4])
n += int64(nn)
if err != nil {
return
}
h.MaskKey = binary.LittleEndian.Uint32(buf[:])
}
return
}
func (h FrameHeader) Length() int {
n := 2
switch {
case h.PayloadLength > math.MaxUint16:
n += 8
case h.PayloadLength > 125:
n += 2
}
if h.Masked {
n += 4
}
return n
}
func (h *FrameHeader) WriteTo(w io.Writer) (n int64, err error) {
var buf [14]byte
pos := 0
var b byte
if h.Fin {
b |= 1 << 7
}
if h.Rsv1 {
b |= 1 << 6
}
if h.Rsv2 {
b |= 1 << 5
}
if h.Rsv3 {
b |= 1 << 4
}
b |= byte(h.OpCode)
buf[0] = b
lengthByte := byte(0)
if h.Masked {
lengthByte |= 1 << 7
}
switch {
case h.PayloadLength > math.MaxUint16:
lengthByte |= 127
case h.PayloadLength > 125:
lengthByte |= 126
case h.PayloadLength >= 0:
lengthByte |= byte(h.PayloadLength)
}
buf[1] = lengthByte
pos = 2
switch {
case h.PayloadLength > math.MaxUint16:
binary.BigEndian.PutUint64(buf[2:], uint64(h.PayloadLength))
pos += 8
case h.PayloadLength > 125:
binary.BigEndian.PutUint16(buf[2:], uint16(h.PayloadLength))
pos += 2
}
if h.Masked {
binary.LittleEndian.PutUint32(buf[pos:], h.MaskKey)
pos += 4
}
nn, err := w.Write(buf[:pos])
n = int64(nn)
return
}
type Frame struct {
Header FrameHeader
Data io.Reader
}
func (fr *Frame) ReadFrom(r io.Reader) (n int64, err error) {
if n, err = fr.Header.ReadFrom(r); err != nil {
return
}
fr.Data = io.LimitReader(r, fr.Header.PayloadLength)
return
}
func (fr *Frame) WriteTo(w io.Writer) (n int64, err error) {
n, err = fr.Header.WriteTo(w)
if err != nil {
return
}
nn, err := io.Copy(w, fr.Data)
n += nn
return
}
+34 -19
View File
@@ -38,6 +38,19 @@ type HTTPRecorderObject struct {
Response HTTPResponseRecorderObject `json:"response"` Response HTTPResponseRecorderObject `json:"response"`
} }
type WebsocketRecorderObject struct {
From string `json:"from"`
Fin bool `json:"fin"`
Rsv1 bool `json:"rsv1"`
Rsv2 bool `json:"rsv2"`
Rsv3 bool `json:"rsv3"`
OpCode int `json:"opcode"`
Masked bool `json:"masked"`
MaskKey uint32 `json:"maskKey"`
Length int64 `json:"length"`
Payload []byte `json:"payload"`
}
type TLSRecorderObject struct { type TLSRecorderObject struct {
ServerName string `json:"serverName"` ServerName string `json:"serverName"`
CipherSuite string `json:"cipherSuite"` CipherSuite string `json:"cipherSuite"`
@@ -59,25 +72,27 @@ type DNSRecorderObject struct {
} }
type HandlerRecorderObject struct { type HandlerRecorderObject struct {
Node string `json:"node,omitempty"` Node string `json:"node,omitempty"`
Service string `json:"service"` Service string `json:"service"`
Network string `json:"network"` Network string `json:"network"`
RemoteAddr string `json:"remote"` RemoteAddr string `json:"remote"`
LocalAddr string `json:"local"` LocalAddr string `json:"local"`
Host string `json:"host"` Host string `json:"host"`
Proto string `json:"proto,omitempty"` Dst string `json:"dst"`
ClientIP string `json:"clientIP"` Proto string `json:"proto,omitempty"`
ClientID string `json:"clientID,omitempty"` ClientIP string `json:"clientIP"`
HTTP *HTTPRecorderObject `json:"http,omitempty"` ClientID string `json:"clientID,omitempty"`
TLS *TLSRecorderObject `json:"tls,omitempty"` HTTP *HTTPRecorderObject `json:"http,omitempty"`
DNS *DNSRecorderObject `json:"dns,omitempty"` Websocket *WebsocketRecorderObject `json:"websocket,omitempty"`
Route string `json:"route,omitempty"` TLS *TLSRecorderObject `json:"tls,omitempty"`
InputBytes uint64 `json:"inputBytes"` DNS *DNSRecorderObject `json:"dns,omitempty"`
OutputBytes uint64 `json:"outputBytes"` Route string `json:"route,omitempty"`
Err string `json:"err,omitempty"` InputBytes uint64 `json:"inputBytes"`
SID string `json:"sid"` OutputBytes uint64 `json:"outputBytes"`
Duration time.Duration `json:"duration"` Err string `json:"err,omitempty"`
Time time.Time `json:"time"` SID string `json:"sid"`
Duration time.Duration `json:"duration"`
Time time.Time `json:"time"`
} }
func (p *HandlerRecorderObject) Record(ctx context.Context, r recorder.Recorder) error { func (p *HandlerRecorderObject) Record(ctx context.Context, r recorder.Recorder) error {