mirror of
https://github.com/go-gost/x.git
synced 2026-10-19 16:16:32 +00:00
The cache-hit path (serveCachedResponse) recorded on the same HandlerRecorderObject as the calling handler's deferred Record(), producing two records per request. Copy ro at HandleHTTP entry so the sniffer's internal recording stays isolated from the caller's defer. Also zero ro.Time when the sniffer takes over, so the handler's defer Record() skips the now-redundant connection-level record. Fixes 10 files across sniffer and all 8 handlers that support sniffed HTTP forwarding.
280 lines
7.0 KiB
Go
280 lines
7.0 KiB
Go
package ss
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-gost/core/bypass"
|
|
"github.com/go-gost/core/handler"
|
|
md "github.com/go-gost/core/metadata"
|
|
"github.com/go-gost/core/observer/stats"
|
|
"github.com/go-gost/core/recorder"
|
|
"github.com/go-gost/go-shadowsocks2/core"
|
|
"github.com/go-gost/go-shadowsocks2/socks"
|
|
"github.com/go-gost/go-shadowsocks2/utils"
|
|
xctx "github.com/go-gost/x/ctx"
|
|
ictx "github.com/go-gost/x/internal/ctx"
|
|
xnet "github.com/go-gost/x/internal/net"
|
|
"github.com/go-gost/x/internal/util/sniffing"
|
|
"github.com/go-gost/x/internal/util/ss/none"
|
|
tls_util "github.com/go-gost/x/internal/util/tls"
|
|
rate_limiter "github.com/go-gost/x/limiter/rate"
|
|
xstats "github.com/go-gost/x/observer/stats"
|
|
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
|
|
xrecorder "github.com/go-gost/x/recorder"
|
|
"github.com/go-gost/x/registry"
|
|
)
|
|
|
|
func init() {
|
|
registry.HandlerRegistry().Register("ss", NewHandler)
|
|
}
|
|
|
|
type ssHandler struct {
|
|
md metadata
|
|
options handler.Options
|
|
recorder recorder.RecorderObject
|
|
certPool tls_util.CertPool
|
|
server core.TCPServer
|
|
}
|
|
|
|
func NewHandler(opts ...handler.Option) handler.Handler {
|
|
options := handler.Options{}
|
|
for _, opt := range opts {
|
|
opt(&options)
|
|
}
|
|
|
|
return &ssHandler{
|
|
options: options,
|
|
}
|
|
}
|
|
|
|
func (h *ssHandler) Init(md md.Metadata) (err error) {
|
|
if err = h.parseMetadata(md); err != nil {
|
|
return
|
|
}
|
|
|
|
if h.options.Auth == nil {
|
|
return errors.New("ss: auth is required")
|
|
}
|
|
|
|
method := h.options.Auth.Username()
|
|
password, _ := h.options.Auth.Password()
|
|
|
|
if strings.EqualFold(method, "none") || strings.EqualFold(method, "dummy") {
|
|
h.server = core.NewTCPServer(core.ServerConfig{Cipher: none.Cipher, Users: h.md.users})
|
|
err = h.server.Init()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
serverConfig, err := utils.NewServerConfig(method, password, h.md.users)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h.server = core.NewTCPServer(serverConfig)
|
|
err = h.server.Init()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, ro := range h.options.Recorders {
|
|
if ro.Record == xrecorder.RecorderServiceHandler {
|
|
h.recorder = ro
|
|
break
|
|
}
|
|
}
|
|
|
|
if h.md.certificate != nil && h.md.privateKey != nil {
|
|
h.certPool = tls_util.NewMemoryCertPool()
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (h *ssHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
|
|
defer conn.Close()
|
|
|
|
start := time.Now()
|
|
|
|
ro := &xrecorder.HandlerRecorderObject{
|
|
Network: "tcp",
|
|
Service: h.options.Service,
|
|
RemoteAddr: conn.RemoteAddr().String(),
|
|
LocalAddr: conn.LocalAddr().String(),
|
|
SID: xctx.SidFromContext(ctx).String(),
|
|
Time: start,
|
|
}
|
|
|
|
if srcAddr := xctx.SrcAddrFromContext(ctx); srcAddr != nil {
|
|
ro.ClientAddr = srcAddr.String()
|
|
}
|
|
|
|
log := h.options.Logger.WithFields(map[string]any{
|
|
"remote": conn.RemoteAddr().String(),
|
|
"local": conn.LocalAddr().String(),
|
|
"client": ro.ClientAddr,
|
|
"network": ro.Network,
|
|
"sid": ro.SID,
|
|
})
|
|
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
|
|
|
|
pStats := xstats.Stats{}
|
|
conn = stats_wrapper.WrapConn(conn, &pStats)
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
ro.Err = err.Error()
|
|
}
|
|
ro.InputBytes = pStats.Get(stats.KindInputBytes)
|
|
ro.OutputBytes = pStats.Get(stats.KindOutputBytes)
|
|
ro.Duration = time.Since(start)
|
|
if err := ro.Record(ctx, h.recorder.Recorder); err != nil {
|
|
log.Errorf("record: %v", err)
|
|
}
|
|
|
|
log.WithFields(map[string]any{
|
|
"duration": time.Since(start),
|
|
"inputBytes": ro.InputBytes,
|
|
"outputBytes": ro.OutputBytes,
|
|
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
|
|
}()
|
|
|
|
if !h.checkRateLimit(conn.RemoteAddr()) {
|
|
return rate_limiter.ErrRateLimit
|
|
}
|
|
|
|
wrappedConn, err := h.server.WrapConn(conn)
|
|
if err != nil {
|
|
return
|
|
}
|
|
var target socks.Addr
|
|
if tc, ok := wrappedConn.(interface{ Target() socks.Addr }); ok {
|
|
target = tc.Target()
|
|
}
|
|
if len(target) == 0 {
|
|
return errors.New("ss: target not available")
|
|
}
|
|
conn = wrappedConn
|
|
|
|
conn.SetReadDeadline(time.Now().Add(h.md.readTimeout))
|
|
|
|
ro.Host = target.String()
|
|
|
|
conn.SetReadDeadline(time.Time{})
|
|
|
|
log = log.WithFields(map[string]any{
|
|
"dst": target.String(),
|
|
"host": target.String(),
|
|
})
|
|
|
|
log.Debugf("%s >> %s", conn.RemoteAddr(), target)
|
|
|
|
if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, "tcp", target.String(), bypass.WithService(h.options.Service)) {
|
|
log.Debug("bypass: ", target.String())
|
|
return nil
|
|
}
|
|
|
|
switch h.md.hash {
|
|
case "host":
|
|
ctx = xctx.ContextWithHash(ctx, &xctx.Hash{Source: target.String()})
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
cc, err := h.options.Router.Dial(ictx.ContextWithBuffer(ctx, &buf), "tcp", target.String())
|
|
ro.Route = buf.String()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cc.Close()
|
|
|
|
log = log.WithFields(map[string]any{"src": cc.LocalAddr().String(), "dst": cc.RemoteAddr().String()})
|
|
ro.SrcAddr = cc.LocalAddr().String()
|
|
ro.DstAddr = cc.RemoteAddr().String()
|
|
|
|
if h.md.sniffing {
|
|
if h.md.sniffingTimeout > 0 {
|
|
conn.SetReadDeadline(time.Now().Add(h.md.sniffingTimeout))
|
|
}
|
|
|
|
br := bufio.NewReader(conn)
|
|
proto, _ := sniffing.Sniff(ctx, br)
|
|
ro.Proto = proto
|
|
|
|
if h.md.sniffingTimeout > 0 {
|
|
conn.SetReadDeadline(time.Time{})
|
|
}
|
|
|
|
dial := func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
return cc, nil
|
|
}
|
|
dialTLS := func(ctx context.Context, network, address string, cfg *tls.Config) (net.Conn, error) {
|
|
return cc, nil
|
|
}
|
|
sniffer := &sniffing.Sniffer{
|
|
Websocket: h.md.sniffingWebsocket,
|
|
WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
|
|
Recorder: h.recorder.Recorder,
|
|
RecorderOptions: h.recorder.Options,
|
|
Certificate: h.md.certificate,
|
|
PrivateKey: h.md.privateKey,
|
|
NegotiatedProtocol: h.md.alpn,
|
|
CertPool: h.certPool,
|
|
MitmBypass: h.md.mitmBypass,
|
|
ReadTimeout: h.md.readTimeout,
|
|
}
|
|
|
|
conn = xnet.NewReadWriteConn(br, conn, conn)
|
|
switch proto {
|
|
case sniffing.ProtoHTTP:
|
|
ro.Time = time.Time{}
|
|
return sniffer.HandleHTTP(ctx, "tcp", conn,
|
|
sniffing.WithService(h.options.Service),
|
|
sniffing.WithDial(dial),
|
|
sniffing.WithDialTLS(dialTLS),
|
|
sniffing.WithBypass(h.options.Bypass),
|
|
sniffing.WithRecorderObject(ro),
|
|
sniffing.WithLog(log),
|
|
)
|
|
case sniffing.ProtoTLS:
|
|
ro.Time = time.Time{}
|
|
return sniffer.HandleTLS(ctx, "tcp", conn,
|
|
sniffing.WithService(h.options.Service),
|
|
sniffing.WithDial(dial),
|
|
sniffing.WithDialTLS(dialTLS),
|
|
sniffing.WithBypass(h.options.Bypass),
|
|
sniffing.WithRecorderObject(ro),
|
|
sniffing.WithLog(log),
|
|
)
|
|
}
|
|
}
|
|
|
|
t := time.Now()
|
|
log.Infof("%s <-> %s", conn.RemoteAddr(), ro.Host)
|
|
// xnet.Transport(conn, cc)
|
|
xnet.Pipe(ctx, conn, cc)
|
|
log.WithFields(map[string]any{
|
|
"duration": time.Since(t),
|
|
}).Infof("%s >-< %s", conn.RemoteAddr(), ro.Host)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *ssHandler) checkRateLimit(addr net.Addr) bool {
|
|
if h.options.RateLimiter == nil {
|
|
return true
|
|
}
|
|
host, _, _ := net.SplitHostPort(addr.String())
|
|
if limiter := h.options.RateLimiter.Limiter(host); limiter != nil {
|
|
return limiter.Allow(1)
|
|
}
|
|
|
|
return true
|
|
}
|