Files
x/handler/sni/metadata.go
T
ginuerzh b01f5d065f refactor(handler/sni): extract helpers, add SnifferBuilder, comprehensive tests
- Extract newRecorderObject, checkRateLimit, sniffingDial, and
  handleSniffedProtocol from the Handle method into dedicated files
  (sniffing.go, util.go) to reduce Handle from ~100 to ~65 lines.
- Introduce SnifferBuilder: populated once during Init, reused via
  Build() per connection. Avoids reconstructing a 9-field sniffing.Sniffer
  literal on every inbound connection.
- Hoist router nil check from the lazy dial closure into Handle so
  misconfigured handlers fail immediately instead of silently dropping
  unrecognised traffic with no error.
- Add nil-addr guard in checkRateLimit (missing from the old inline
  version — would panic if the remote address were somehow nil).
- Add comprehensive unit tests (44 tests across 5 test files) covering
  handler creation, Init, metadata parsing (defaults, custom, MITM,
  error paths), protocol dispatch (HTTP, TLS, unknown, empty), rate
  limiting, recorder objects, dial plumbing, and SnifferBuilder.
- Add package-level godoc explaining the handler scope, limitations
  vs tcp/forward, and the connection processing flow.
- Fix metadata comment: s/SnifferBuilder/sniffing.Sniffer/.
2026-06-25 14:51:13 +08:00

62 lines
1.7 KiB
Go

package sni
import (
"crypto"
"crypto/tls"
"crypto/x509"
"time"
"github.com/go-gost/core/bypass"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/registry"
)
type metadata struct {
// readTimeout controls two behaviors:
// 1. A read deadline set on the client connection before sniffing the
// TLS ClientHello / HTTP request (see handler.go Handle).
// 2. Passed to [sniffing.Sniffer] as the timeout for reading upstream
// response headers during HTTP/TLS sniffing.
// 0 or negative defaults to 15s.
readTimeout time.Duration
hash string
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate
privateKey crypto.PrivateKey
alpn string
mitmBypass bypass.Bypass
}
func (h *sniHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.readTimeout = mdutil.GetDuration(md, "readTimeout")
if h.md.readTimeout <= 0 {
h.md.readTimeout = 15 * time.Second
}
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")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
if certFile != "" && keyFile != "" {
tlsCert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return err
}
h.md.certificate, err = x509.ParseCertificate(tlsCert.Certificate[0])
if err != nil {
return err
}
h.md.privateKey = tlsCert.PrivateKey
}
h.md.alpn = mdutil.GetString(md, "mitm.alpn")
h.md.mitmBypass = registry.BypassRegistry().Get(mdutil.GetString(md, "mitm.bypass"))
return
}