c246a1d4fa
Knock previously accepted a single hostname. Now it accepts multiple comma-separated hostnames; probe resistance is bypassed if the request hostname matches any entry in the list. Matching is case-insensitive and whitespace around entries is trimmed.
153 lines
5.8 KiB
Go
153 lines
5.8 KiB
Go
package http
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"net/http"
|
|
"strings"
|
|
"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"
|
|
)
|
|
|
|
const (
|
|
defaultRealm = "gost"
|
|
defaultProxyAgent = "gost/3.0"
|
|
)
|
|
|
|
// metadata holds all configuration parsed from the handler's metadata map.
|
|
// Fields are populated by parseMetadata and read throughout the handler's
|
|
// request-processing methods.
|
|
type metadata struct {
|
|
readTimeout time.Duration // read timeout for upstream responses; default 15s, negative disables
|
|
idleTimeout time.Duration // idle timeout for pipe forwarding; 0 means disabled
|
|
keepalive bool // enable HTTP keep-alive on the upstream transport
|
|
compression bool // enable HTTP compression on the upstream transport
|
|
|
|
probeResistance *probeResistance // decoy response config for auth failures (nil = disabled)
|
|
enableUDP bool // allow UDP relay over HTTP
|
|
udpBufferSize int // UDP relay buffer size in bytes
|
|
|
|
header http.Header // additional response headers added to every proxy response
|
|
hash string // hash strategy for hop selection ("host" or empty)
|
|
authBasicRealm string // custom realm for Basic auth 407 responses
|
|
proxyAgent string // Proxy-Agent header value; default "gost/3.0"
|
|
|
|
observerPeriod time.Duration // stats reporting interval; default 5s, min 1s
|
|
observerResetTraffic bool // reset traffic counters after each observation
|
|
|
|
sniffing bool // enable protocol sniffing on CONNECT tunnels
|
|
sniffingTimeout time.Duration // deadline for the initial sniff read
|
|
sniffingWebsocket bool // enable WebSocket frame recording
|
|
sniffingWebsocketSampleRate float64 // max frames recorded per second
|
|
|
|
certificate *x509.Certificate // MITM CA certificate (for TLS decryption)
|
|
privateKey crypto.PrivateKey // MITM CA private key
|
|
alpn string // ALPN protocol to negotiate during MITM
|
|
mitmBypass bypass.Bypass // bypass rules that skip MITM decryption
|
|
|
|
limiterRefreshInterval time.Duration // traffic limiter cache refresh interval
|
|
limiterCleanupInterval time.Duration // traffic limiter cache cleanup interval
|
|
}
|
|
|
|
// parseMetadata reads all configuration values from the metadata map into
|
|
// the handler's metadata struct. It applies defaults for readTimeout (15s),
|
|
// observerPeriod (5s, min 1s), and proxyAgent ("gost/3.0"). MITM TLS is
|
|
// enabled when both mitm.certFile and mitm.keyFile are provided.
|
|
func (h *httpHandler) parseMetadata(md mdata.Metadata) error {
|
|
h.md.readTimeout = mdutil.GetDuration(md, "readTimeout")
|
|
if h.md.readTimeout == 0 {
|
|
h.md.readTimeout = 15 * time.Second
|
|
}
|
|
if h.md.readTimeout < 0 {
|
|
h.md.readTimeout = 0
|
|
}
|
|
|
|
h.md.idleTimeout = mdutil.GetDuration(md, "idleTimeout")
|
|
if h.md.idleTimeout < 0 {
|
|
h.md.idleTimeout = 0
|
|
}
|
|
|
|
if m := mdutil.GetStringMapString(md, "http.header", "header"); len(m) > 0 {
|
|
hd := http.Header{}
|
|
for k, v := range m {
|
|
hd.Add(k, v)
|
|
}
|
|
h.md.header = hd
|
|
}
|
|
|
|
h.md.keepalive = mdutil.GetBool(md, "http.keepalive", "keepalive")
|
|
h.md.compression = mdutil.GetBool(md, "http.compression", "compression")
|
|
|
|
// probeResist format: "type:value" (e.g. "code:404", "web:example.com",
|
|
// "host:192.168.1.1:80", "file:/var/www/index.html").
|
|
if pr := mdutil.GetString(md, "probeResist", "probe_resist"); pr != "" {
|
|
if ss := strings.SplitN(pr, ":", 2); len(ss) == 2 {
|
|
h.md.probeResistance = &probeResistance{
|
|
Type: ss[0],
|
|
Value: ss[1],
|
|
Knock: mdutil.GetString(md, "knock"),
|
|
}
|
|
}
|
|
}
|
|
h.md.enableUDP = mdutil.GetBool(md, "udp")
|
|
h.md.udpBufferSize = mdutil.GetInt(md, "udp.bufferSize", "udpBufferSize")
|
|
h.md.hash = mdutil.GetString(md, "hash")
|
|
h.md.authBasicRealm = mdutil.GetString(md, "authBasicRealm")
|
|
|
|
h.md.observerPeriod = mdutil.GetDuration(md, "observePeriod", "observer.period", "observer.observePeriod")
|
|
if h.md.observerPeriod == 0 {
|
|
h.md.observerPeriod = 5 * time.Second
|
|
}
|
|
if h.md.observerPeriod < time.Second {
|
|
h.md.observerPeriod = time.Second
|
|
}
|
|
|
|
h.md.observerResetTraffic = mdutil.GetBool(md, "observer.resetTraffic")
|
|
|
|
h.md.proxyAgent = mdutil.GetString(md, "http.proxyAgent", "proxyAgent")
|
|
if h.md.proxyAgent == "" {
|
|
h.md.proxyAgent = defaultProxyAgent
|
|
}
|
|
|
|
h.md.sniffing = mdutil.GetBool(md, "sniffing")
|
|
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")
|
|
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"))
|
|
|
|
h.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
|
|
h.md.limiterCleanupInterval = mdutil.GetDuration(md, "limiter.cleanupInterval")
|
|
|
|
return nil
|
|
}
|
|
|
|
// probeResistance configures a decoy response that hides the proxy from
|
|
// unauthorised clients. When authentication fails, instead of returning
|
|
// 407 Proxy-Auth-Required, the handler returns a fake response that makes
|
|
// the port appear to run a different service.
|
|
type probeResistance struct {
|
|
Type string // strategy: "code", "web", "host", or "file"
|
|
Value string // strategy-specific parameter (status code, URL, address, path)
|
|
Knock string // optional comma-separated hostnames; probe resistance only fires when the request hostname matches none of them
|
|
}
|